📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.
Este é o seu projeto de Transição. Você continuará usando o desenho em XML, mas escreverá toda a lógica em Kotlin. É aqui que você sentirá o poder da linguagem moderna!
Antes de começar, certifique-se de já ter estudado:
Criar o clássico jogo de memória das cores. O app pisca uma sequência de botões coloridos e o jogador deve repetir a sequência exata para ganhar pontos.
💡 O mesmo conceito em outra stack: o clássico Genius/Simon também é construído do zero em Java Swing no curso Sistemas com Java (com as evoluções
_02_swe_03_sw, refatorando para MVC).
ViewBinding: É a forma moderna de conectar o XML ao Kotlin. Você não precisa mais de findViewById! O Kotlin cria uma “ponte” automática para você.
List / ArrayList: É uma “fila” de itens. Usaremos uma lista para guardar a sequência de cores que o computador sorteou.
Coroutine (Delay): Usaremos o delay do Kotlin para fazer as cores piscarem de forma suave.
Template: Empty Views Activity.
Name: AppGeniusGamer_SeuNome.
Package Name: br.com.curso.genius.
Language: Kotlin.
Crie 4 botões grandes na tela (Verde, Vermelho, Amarelo e Azul).
Verde: #4CAF50
Vermelho: #F44336
Amarelo: #FFEB3B
Azul: #2196F3
Em Kotlin, você pode criar uma lista de botões e sorteá-los de forma muito simples:
val sorteado = listaDeBotoes.random()
Veja como sua tela deve ficar ao final deste projeto:

Se você terminou de programar a mecânica base do jogo e tudo está funcionando, experimente estes upgrades:
SoundPool ou MediaPlayer.MainActivity.kt)Disponível em: app/src/main/java/br/com/curso/genius/MainActivity.kt
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)
val botoes = listOf(binding.btnVerde, binding.btnVermelho, binding.btnAmarelo, binding.btnAzul)
sequenciaComputador.forEach { corIndex ->
val botao = botoes[corIndex]
piscarBotao(botao, corIndex)
delay(800)
}
binding.txtStatus.text = "SUA VEZ!"
jogoAtivo = true
botoes.forEach { it.isEnabled = true }
}
private suspend fun piscarBotao(botao: Button, indice: Int) {
// Toca o som correspondente
tocarSom(indice)
botao.alpha = 0.3f
delay(400)
botao.alpha = 1.0f
}
private fun tocarSom(indice: Int) {
val somResId = when (indice) {
0 -> R.raw.green
1 -> R.raw.red
2 -> R.raw.yellow
3 -> R.raw.blue
else -> 0
}
if (somResId != 0) {
val mp = MediaPlayer.create(this, somResId)
mp.setOnCompletionListener { it.release() }
mp.start()
}
}
private fun aoClicar(indice: Int) {
val botoes = listOf(binding.btnVerde, binding.btnVermelho, binding.btnAmarelo, binding.btnAzul)
// Toca som e efeito visual no clique
tocarSom(indice)
lifecycleScope.launch {
botoes[indice].alpha = 0.3f
delay(100)
botoes[indice].alpha = 1.0f
}
if (indice == sequenciaComputador[passoJogador]) {
passoJogador++
if (passoJogador == sequenciaComputador.size) {
jogoAtivo = false // trava os botões durante o delay, evita IndexOutOfBoundsException se clicar de novo
binding.txtStatus.text = "MUITO BEM!"
lifecycleScope.launch {
delay(1000)
iniciarNovaRodada()
}
}
} else {
gameOver()
}
}
private fun gameOver() {
jogoAtivo = false
binding.txtStatus.text = "GAME OVER!"
if (score > highScore) {
highScore = score
salvarHighScore()
Toast.makeText(this, "NOVO RECORDE! 🏆", Toast.LENGTH_LONG).show()
}
binding.btnStart.text = "TENTAR NOVAMENTE"
binding.btnStart.visibility = View.VISIBLE
val botoes = listOf(binding.btnVerde, binding.btnVermelho, binding.btnAmarelo, binding.btnAzul)
botoes.forEach { it.isEnabled = false }
}
private fun atualizarPlacar() {
binding.txtScore.text = "LEVEL: $score" binding.txtHighScore.text = "BEST:$highScore"
}
private fun salvarHighScore() {
val pref = getPreferences(Context.MODE_PRIVATE)
pref.edit().putInt("HIGH_SCORE", highScore).apply()
atualizarPlacar()
}
}
Por que
jogoAtivo = falseantes do delay de “MUITO BEM!”? Entre o jogador completar a sequência einiciarNovaRodada()reiniciarpassoJogador, há 1 segundo de espera. Sem travarjogoAtivo, um clique nesse intervalo chamaaoClicar()compassoJogador == sequenciaComputador.size, esequenciaComputador[passoJogador]lançaIndexOutOfBoundsException— o app crasha. A mesma proteção já existe emgameOver()e emtocarSequencia()(enquanto a sequência pisca).
Mesmo sendo XML, já comece a organizar:
br.com.curso.genius.ui.MainActivity
br.com.curso.genius.logic.GameEngine (Opcional: mover a lógica para cá).
Se você encontrar erros ao tentar rodar este projeto, verifique os pontos abaixo:
No Android, dimensões não aceitam pontos decimais.
❌ Errado: android:padding=”16.dp”
✅ Correto: android:padding=”16dp”
O arquivo gradle.properties na raiz do projeto deve conter:
android.useAndroidX=true
android.enableJetifier=true
O código fonte deve estar dentro de um módulo chamado app:
NomeDoProjeto/app/src/main/…
Certifique-se de ter os arquivos em res/values: colors.xml, strings.xml, themes.xml.
O arquivo settings.gradle na raiz deve conter include ‘:app’.
—`
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd android_p03_genius_demo
./gradlew build
./gradlew test
# ou abrir no Android Studio e clicar em Run (Shift+F10)
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta navegar até a pasta antes de executar: cd proj_aplicacoes_full_stack/projetos/android_p03_genius_demo