🔴 P03: Genius Game (Simon Says)
Este é o seu projeto de Transição da Fase 2: A Ponte para o Moderno! 🌉 Você continuará utilizando o desenho de tela tradicional em arquivos XML, mas escreverá toda a lógica de programação na linguagem Kotlin. É aqui que você sentirá o poder de uma linguagem moderna com menos linhas de código, segurança a nulos e facilidades assíncronas!
✅ Pré-requisitos
Antes de começar, certifique-se de já ter estudado:
- 📘 Cap 03: O Mundo dos Apps e a Lógica Básica
- 📘 Cap 04: Cálculo de Dano (Operadores)
- 📘 Cap 05: Decisões e Ranks
- 📘 Cap 06: Poderes e Funções
- 🏗️ Projeto anterior: P03: Genius Game (Simon Says) - Demo
🎯 Objetivo do Jogo
Criar o clássico console de brinquedo Genius (Simon Says). O jogo sorteia uma sequência aleatória de cores e sons. A cada rodada, a sequência aumenta. O jogador deve observar os flashes luminosos e repetir a sequência exata de botões clicando nas cores.
- A cada acerto da sequência: O jogador sobe de nível (Level) e uma nova cor é somada ao desafio.
- Ao errar a cor: O jogo exibe a tela de “Game Over”, calcula se o recorde pessoal foi superado e salva o “Best Score” na memória permanente do aparelho.
graph TD
A[Início / App Pronto] --> B[Clique em INICIAR DESAFIO]
B --> C[Limpar dados & Prepare-se]
C --> D[Iniciar Nova Rodada]
D --> E[Incrementar Nível & Sorteia nova cor]
E --> F[Tocar Sequência de Cores Computador]
F --> G[Aguardar clique do Jogador]
G --> H[Clique na cor do Jogador]
H --> I{Cor clicada é igual à da sequência?}
I -- Sim --> J[Avançar índice do jogador]
I -- Não --> K[Game Over!🏆 Salvar High Score]
J --> L{Jogador completou toda a sequência?}
L -- Sim --> M[Aguardar 1s & Iniciar Nova Rodada]
L -- Não --> G
M --> D
K --> N[Exibir Botão Start com Tentar Novamente]
📖 Dicionário do Projeto
- ConstraintLayout: Um gerenciador de layout do Android flexível e extremamente performático que permite criar telas complexas baseadas em restrições e âncoras entre os elementos.
- ViewBinding: Recurso moderno do Android que gera classes de ligação automáticas para acessar com segurança de tipo (
null-safety) os componentes do XML direto no Kotlin, aposentando de vez o antigo e perigosofindViewById. - Kotlin Coroutines: Mecanismo leve do Kotlin para concorrência assíncrona. Ele permite pausar (“suspender”) a execução do código de tela sem travar a interface do usuário.
- delay(): Função suspensa das Coroutines que cria pausas precisas de tempo (delays) de forma não-bloqueante na tela.
- lifecycleScope: O escopo de vida da tela (
Activity). Garante que qualquer tarefa assíncrona ou animação seja encerrada automaticamente caso o app seja fechado, evitando vazamento de memória. - SharedPreferences: Pequeno banco de dados local do Android estilo “chave-valor”, usado para persistir configurações leves, moedas ou pontuações recordes (
High Score). - Radial Gradient XML: Recurso de desenho do XML que cria um gradiente circular simulando profundidade 3D (sensação de botão plástico ou brilhando).
📥 Download de Assets (Sons Originais)
Para que a imersão de fliperama clássico fique perfeita, baixe os efeitos de áudio originais do console Simon:
📂 Como usar os áudios no projeto:
- No painel esquerdo do Android Studio, clique com o botão direito na pasta
res. - Vá em New > Android Resource Directory.
- Em Resource type, selecione raw e clique em OK.
- Extraia o arquivo zip que baixou e copie os 4 arquivos
.wav(blue.wav,green.wav,red.waveyellow.wav) para dentro da pastares/raw.
🛠️ Passo 1: Configurando o Projeto no Android Studio
- Abra o Android Studio e selecione
New Project. - Escolha o template Empty Views Activity.
- Ajuste as configurações:
- Name:
Genius Simon Game - Package Name:
br.com.curso.genius - Language: Kotlin (A linguagem oficial do Android).
- Minimum SDK: API 24 (Android 7.0) ou superior.
- Name:
- Aguarde o Gradle carregar e indexar os arquivos de dependência.
🛠️ Passo 2: Habilitando viewBinding e Coroutines
Abra o arquivo de configuração do app: Gradle Scripts > build.gradle (Module :app) e adicione a configuração do viewBinding dentro do bloco android:
android {
...
buildFeatures {
viewBinding true
}
}
Após alterar, clique em Sync Now no topo direito da tela.
🎨 Passo 3: Criando os Quadrantes Coloridos 3D (Drawables XML)
Faremos o console clássico desenhando quatro setores esféricos usando shapes do Android XML. Cada botão colorido terá cantos assimétricos e luz radial interna.
- Vá em
app > src > main > res > drawablee crie um arquivo chamadoquadrant_green.xml: ```xml <?xml version=”1.0” encoding=”utf-8”?>
2. Crie os outros três arquivos na pasta `drawable` alterando apenas as cores e os cantos correspondentes para formar o círculo central (conforme os códigos completos disponíveis na seção **Gabarito** ao final do guia).
---
## 🎨 Passo 4: O Console de Jogo (XML)
Desenharemos a mesa preta esférica do console combinando `ConstraintLayout` para o cabeçalho e `FrameLayout` empilhando a borda redonda externa e o núcleo de texto central.
Abra `app > src > main > res > layout > activity_main.xml` e cole o código de design completo fornecido no **Gabarito**.
---
## 🧠 Passo 5: Lógica Principal com Coroutines (Kotlin)
A grande estrela da lógica deste app é o uso de **suspend functions** e o temporizador `delay()` de coroutines para piscar os botões sequencialmente sem bloquear a tela do celular.
Abra o arquivo de lógica em Kotlin: `app > src > main > java > br > com > curso > genius > MainActivity.kt` e codifique o fluxo com base no **Gabarito Oficial**.
---
## 🛠️ Requisitos de Configuração e Solução de Problemas
### 1. Suporte ao Kotlin e ViewBinding
Se o compilador reclamar que a classe `ActivityMainBinding` não existe, clique no menu superior `Build > Clean Project` e depois em `Build > Rebuild Project` para forçar o Android Studio a gerá-la.
---
## 📸 Resultado Esperado
Veja como sua tela deve ficar ao final deste projeto:





---
## 🏆 Desafios para você (Upgrade!)
* **Velocidade Dinâmica**: Faça com que o intervalo da piscada de cores diminua proporcionalmente à medida que o jogador avança de Level, tornando o jogo frenético em níveis altos!
---
# 📖 Gabarito Oficial de Código (Para Conferência)
### 📄 Layout Completo (`activity_main.xml`)
Disponível em: `app/src/main/res/layout/activity_main.xml`
```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#050505"
android:padding="16dp">
<!-- Cabeçalho com Placar Moderno -->
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"
android:background="#1A1A1A"
android:padding="12dp">
<TextView
android:id="@+id/txt_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LEVEL: 0"
android:textColor="#00E676"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginEnd="40dp"/>
<TextView
android:id="@+id/txt_high_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BEST: 0"
android:textColor="#FFC107"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/txt_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OBSERVE A SEQUÊNCIA"
android:textColor="#888888"
android:textSize="16sp"
android:letterSpacing="0.1"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- CONSOLE SIMON PRO (Base com profundidade) -->
<FrameLayout
android:id="@+id/console_outer"
android:layout_width="360dp"
android:layout_height="360dp"
android:background="@drawable/center_circle"
android:backgroundTint="#000000"
android:padding="8dp"
app:layout_constraintBottom_toTopOf="@+id/btn_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txt_status">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/center_circle"
android:backgroundTint="#1A1A1A"
android:padding="15dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_verde"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@drawable/quadrant_green"
app:backgroundTint="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="3dp" />
<Button
android:id="@+id/btn_vermelho"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@drawable/quadrant_red"
app:backgroundTint="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="3dp" />
<Button
android:id="@+id/btn_amarelo"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@drawable/quadrant_yellow"
app:backgroundTint="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="3dp" />
<Button
android:id="@+id/btn_azul"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@drawable/quadrant_blue"
app:backgroundTint="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="3dp" />
<!-- HUB CENTRAL DETALHADO -->
<View
android:layout_width="140dp"
android:layout_height="140dp"
android:background="@drawable/center_circle"
android:backgroundTint="#000000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Botão Fake de Power -->
<View
android:id="@+id/power_btn"
android:layout_width="30dp"
android:layout_height="15dp"
android:layout_marginBottom="45dp"
android:background="@android:drawable/editbox_dropdown_light_frame"
android:backgroundTint="#333333"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SIMON"
android:textColor="#FFFFFF"
android:textSize="28sp"
android:textStyle="bold"
android:textAllCaps="true"
android:letterSpacing="0.1"
android:fontFamily="sans-serif-black"
app:layout_constraintTop_toBottomOf="@+id/power_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</FrameLayout>
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="65dp"
android:layout_marginBottom="20dp"
android:backgroundTint="#00E676"
android:text="INICIAR DESAFIO"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
📄 Lógica Kotlin Completa (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() {
// ViewBinding para acessar componentes sem findViewById
private lateinit var binding: ActivityMainBinding
// Arrays dinâmicos de jogo
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)
// Inicializa o ViewBinding
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Carrega recorde salvo localmente
highScore = getPreferences(Context.MODE_PRIVATE).getInt("HIGH_SCORE", 0)
atualizarPlacar()
configurarBotoes()
binding.btnStart.setOnClickListener { iniciarJogo() }
}
/**
* Mapeia os listeners de clique dos 4 botões coloridos
*/
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 // Travados até clicar em Start
}
}
/**
* Inicia a sequência de rodadas do jogo
*/
private fun iniciarJogo() {
sequenciaComputador.clear()
score = 0
binding.btnStart.visibility = View.GONE
binding.txtStatus.text = "PREPARE-SE!"
iniciarNovaRodada()
}
/**
* Incrementa o nível e adiciona um novo passo aleatório ao desafio
*/
private fun iniciarNovaRodada() {
passoJogador = 0
score = sequenciaComputador.size
atualizarPlacar()
// Sorteia número de 0 a 3
sequenciaComputador.add((0..3).random())
// Lança uma Coroutine no escopo de vida da tela
lifecycleScope.launch {
tocarSequencia()
}
}
/**
* Executa o piscar das cores sequencialmente com áudio correspondente
*/
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) // Intervalo entre piscadas
}
binding.txtStatus.text = "SUA VEZ!"
jogoAtivo = true
botoes.forEach { it.isEnabled = true }
}
/**
* Altera a transparência temporariamente simulando um piscar físico
*/
private suspend fun piscarBotao(botao: Button, indice: Int) {
tocarSom(indice)
botao.alpha = 0.3f
delay(400) // Tempo que o botão fica aceso
botao.alpha = 1.0f
}
/**
* Toca o áudio correspondente ao botão colorido
*/
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) {
// MediaPlayer.create é prático, mas recriar toda vez consome memória.
// Para resolver isso, usamos o setOnCompletionListener para liberar o áudio do celular ao terminar.
val mp = MediaPlayer.create(this, somResId)
mp.setOnCompletionListener { it.release() }
mp.start()
}
}
/**
* Compara a escolha do usuário com o gabarito do computador
*/
private fun aoClicar(indice: Int) {
val botoes = listOf(binding.btnVerde, binding.btnVermelho, binding.btnAmarelo, binding.btnAzul)
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) {
binding.txtStatus.text = "MUITO BEM!"
lifecycleScope.launch {
delay(1000)
iniciarNovaRodada()
}
}
} else {
gameOver()
}
}
/**
* Executa a regra ao errar a sequência
*/
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()
}
}
📄 Exemplo de Setores Esféricos XML (drawable/)
🟩 Quadrante Verde (app/src/main/res/drawable/quadrant_green.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="radial"
android:gradientRadius="200dp"
android:startColor="#66BB6A"
android:endColor="#1B5E20"
android:centerX="0.3"
android:centerY="0.3" />
<corners
android:topLeftRadius="150dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="40dp"
android:bottomRightRadius="10dp" />
<stroke android:width="2dp" android:color="#00000033" />
</shape>
🟥 Quadrante Vermelho (app/src/main/res/drawable/quadrant_red.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="radial"
android:gradientRadius="200dp"
android:startColor="#EF5350"
android:endColor="#B71C1C"
android:centerX="0.7"
android:centerY="0.3" />
<corners
android:topRightRadius="150dp"
android:topLeftRadius="40dp"
android:bottomRightRadius="40dp"
android:bottomLeftRadius="10dp" />
<stroke android:width="2dp" android:color="#00000033" />
</shape>
🟨 Quadrante Amarelo (app/src/main/res/drawable/quadrant_yellow.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="radial"
android:gradientRadius="200dp"
android:startColor="#FFEE58"
android:endColor="#F57F17"
android:centerX="0.3"
android:centerY="0.7" />
<corners
android:bottomLeftRadius="150dp"
android:topLeftRadius="40dp"
android:bottomRightRadius="40dp"
android:topRightRadius="10dp" />
<stroke android:width="2dp" android:color="#00000033" />
</shape>
🟦 Quadrante Azul (app/src/main/res/drawable/quadrant_blue.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="radial"
android:gradientRadius="200dp"
android:startColor="#42A5F5"
android:endColor="#0D47A1"
android:centerX="0.7"
android:centerY="0.7" />
<corners
android:bottomRightRadius="150dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="40dp"
android:topLeftRadius="10dp" />
<stroke android:width="2dp" android:color="#00000033" />
</shape>
⚫ Círculo Central (app/src/main/res/drawable/center_circle.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#1A1A1A"/>
<stroke android:width="4dp" android:color="#FFFFFF"/>
</shape>