📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados na Especialização em Android Nativo. Recomendado revisar antes de iniciar.
v1.0 — Android Nativo, Kotlin, Sons em Baixa Latência, Temporização com Coroutines e Feedback Visual
Série Prática Android Clássico — Projeto 3 de 21
- P01: Dados RPG (2D, 3D & Ciclo de Vida)
- P02: Jogo da Memória (PNG & POC)
- ➡️ P03 (este): Genius / Simon Says Clássico · Kotlin · Áudio & Coroutines
- P03 (Demo): Genius Demo · Laboratório de SoundPool
- P04: Cartão de Visitas Digital em ConstraintLayout
O Genius / Simon Says Clássico reproduz o icônico jogo eletrônico de memória dos anos 80. O projeto explora tópicos avançados de desenvolvimento mobile: emissão de tons sonoros sintetizados em baixa latência com SoundPool, reprodução sequencial de luzes através de Coroutines com delay cooperativo, e validação em tempo real das entradas do usuário com controle de rodadas e recorde.
Todos os artefatos Kotlin, efeitos sonoros em .ogg e telas XML estão organizados na subpasta canônica mobile-android-kotlin/:
catalogo_projetos/android_p03_genius_classic/
├── assets/
│ ├── arquitetura_genius_classic.svg
│ ├── screenshot_p03.png
│ ├── screenshot_genius_classic.png
│ ├── screenshot_genius_final.png
│ ├── screenshot_genius_pro.png
│ └── screenshot_genius_ultra.png
├── mobile-android-kotlin/
│ ├── app/
│ │ ├── src/
│ │ │ └── main/
│ │ │ ├── java/br/com/curso/genius/
│ │ │ │ └── MainActivity.kt
│ │ │ ├── res/
│ │ │ │ ├── raw/
│ │ │ │ │ ├── som_verde.ogg
│ │ │ │ │ ├── som_vermelho.ogg
│ │ │ │ │ ├── som_amarelo.ogg
│ │ │ │ │ └── som_azul.ogg
│ │ │ │ └── layout/activity_main.xml
│ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle/
│ ├── build.gradle
│ ├── gradlew
│ ├── settings.gradle
│ └── README.md
└── index.md
| ID | Persona | Narrativa da Funcionalidade | Critério de Aceitação Principal | Prioridade |
|---|---|---|---|---|
| US-01 | Jogador | Quero observar o app piscar uma sequência luminosa acompanhada de som para memorizar a ordem. | Execução sequencial com delay ajustável por nível de dificuldade. | Alta |
| US-02 | Jogador | Quero repetir a sequência tocando nos 4 botões coloridos com som e brilho imediatos. | Resposta de áudio com latência inferior a 15ms via SoundPool. | Alta |
| US-03 | Jogador | Quero avançar para uma rodada mais longa e rápida sempre que acertar toda a sequência. | Adição de mais uma cor e incremento do placar de rodadas. | Alta |
| US-04 | Jogador | Quero receber som característico de erro e ver minha pontuação final caso cometa um deslize. | Interrupção imediata da rodada e exibição do diálogo de Game Over. | Média |
| US-05 | Desenvolvedor | Quero pausar ou liberar os recursos de áudio no ciclo onPause/onDestroy para não gastar bateria. |
Desalocação correta dos streams de áudio do SoundPool. | Baixa |
lifecycleScope.launch).SharedPreferences).SoundPool.soundPool.release() no método onDestroy() da Activity.flowchart TD
Player([fa:fa-user Jogador])
Engine[(fa:fa-music SoundPool & Coroutines)]
subgraph AppGenius ["🎮 Jogo Genius (mobile-android-kotlin)"]
UC01["Iniciar Nova Rodada"]
UC02["Emitir Sequência Luz e Som"]
UC03["Receber Toques do Jogador"]
UC04["Validar Correspondência de Sequência"]
UC05["Finalizar Partida / Gravar Recorde"]
end
Player --> UC01
UC01 --> UC02
UC02 --> Engine
Player --> UC03
UC03 --> UC04
UC04 -->|Erro| UC05
| ID Requisito | História de Usuário | Artefato de Código | Teste de Aceitação |
|---|---|---|---|
| RF-01 | US-01, US-02 | mobile-android-kotlin/app/src/main/res/layout/activity_main.xml |
testButtonsColorState() |
| RF-02 | US-03 | mobile-android-kotlin/app/.../MainActivity.kt |
testSequenceExpandsEachRound() |
| RF-03 | US-01 | mobile-android-kotlin/app/.../MainActivity.kt |
testPlaybackCoroutineDelays() |
| RF-04 | US-02, US-04 | mobile-android-kotlin/app/.../MainActivity.kt |
testPlayerInputComparison() |
| RF-05 | US-04 | mobile-android-kotlin/app/.../MainActivity.kt |
testHighScorePersistence() |
MainActivity.kt)📄 mobile-android-kotlin/app/src/main/java/br/com/curso/genius/MainActivity.kt (arquivo real do projeto)
package br.com.curso.genius
import android.content.Context
import android.media.MediaPlayer
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import br.com.curso.genius.databinding.ActivityMainBinding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val sequenciaComputador = mutableListOf<Int>()
private var passoJogador = 0
private var score = 0
private var highScore = 0
private var jogoAtivo = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
highScore = getPreferences(Context.MODE_PRIVATE).getInt("HIGH_SCORE", 0)
atualizarPlacar()
configurarBotoes()
binding.btnStart.setOnClickListener { iniciarJogo() }
}
private fun configurarBotoes() {
val botoes = listOf(binding.btnVerde, binding.btnVermelho, binding.btnAmarelo, binding.btnAzul)
botoes.forEachIndexed { index, button ->
button.setOnClickListener {
if (jogoAtivo) aoClicar(index)
}
button.isEnabled = false
}
}
private fun iniciarJogo() {
sequenciaComputador.clear()
score = 0
binding.btnStart.visibility = View.GONE
binding.txtStatus.text = "PREPARE-SE!"
iniciarNovaRodada()
}
private fun iniciarNovaRodada() {
passoJogador = 0
score = sequenciaComputador.size
atualizarPlacar()
sequenciaComputador.add((0..3).random())
lifecycleScope.launch {
tocarSequencia()
}
}
private suspend fun tocarSequencia() {
jogoAtivo = false
binding.txtStatus.text = "OBSERVE..."
delay(1000)
// … (+89 linhas: veja o arquivo completo no projeto)
cd catalogo_projetos/android_p03_genius_classic/mobile-android-kotlin
./gradlew assembleDebug
./gradlew test
| Checkpoint | Meta Técnica | Critério de Aceite | Status |
|---|---|---|---|
| CP-01 | Configuração do SoundPool | 4 sons carregados sem exceção via onLoadCompleteListener |
PASS ✅ |
| CP-02 | Sequenciador com Coroutines | Delay não-bloqueante reproduzindo piscar e tom sonoro | PASS ✅ |
| CP-03 | Validação Passo a Passo | Algoritmo detecta erro imediatamente no primeiro clique errado | PASS ✅ |
| CP-04 | Recorde Local | Pontuação máxima mantida salva entre execuções do app | PASS ✅ |
| CP-05 | Liberação de Memória | soundPool.release() executado no encerramento da Activity |
PASS ✅ |