🔴 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:


🎯 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.

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


📥 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:

  1. No painel esquerdo do Android Studio, clique com o botão direito na pasta res.
  2. Vá em New > Android Resource Directory.
  3. Em Resource type, selecione raw e clique em OK.
  4. Extraia o arquivo zip que baixou e copie os 4 arquivos .wav (blue.wav, green.wav, red.wav e yellow.wav) para dentro da pasta res/raw.

🛠️ Passo 1: Configurando o Projeto no Android Studio

  1. Abra o Android Studio e selecione New Project.
  2. Escolha o template Empty Views Activity.
  3. 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.
  4. 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.

  1. Vá em app > src > main > res > drawable e crie um arquivo chamado quadrant_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:

![Tela inicial do Genius Gamer](./screenshot_p03.png)
![Tela inicial do desafio Simon](./screenshot_genius_pro.png)
![Fase "Observe a sequência" em andamento](./screenshot_genius_classic.png)
![Fase "Sua vez!" — repita a sequência](./screenshot_genius_final.png)
![Tela de Game Over](./screenshot_genius_ultra.png)

---

## 🏆 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>