📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.

🔴 P03: Genius Game (Simon Says) - Demo

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!


✅ Pré-requisitos

Antes de começar, certifique-se de já ter estudado:


🎯 Objetivo do Projeto

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_sw e _03_sw, refatorando para MVC).


📖 Dicionário do Projeto


🏗️ Passo a Passo (Wizard do Android Studio)

  1. Template: Empty Views Activity.

  2. Name: AppGeniusGamer_SeuNome.

  3. Package Name: br.com.curso.genius.

  4. Language: Kotlin.


🎨 Parte 1: As Cores (XML)

Crie 4 botões grandes na tela (Verde, Vermelho, Amarelo e Azul).

🎨 Padrão de Cores (Hexadecimal)


⚙️ Parte 2: A Lógica em Kotlin

O Diferencial do Kotlin:

Em Kotlin, você pode criar uma lista de botões e sorteá-los de forma muito simples:

val sorteado = listaDeBotoes.random()


📸 Resultado Esperado

Veja como sua tela deve ficar ao final deste projeto:

Tela inicial do Genius Gamer


🏆 Desafios para você (Upgrade!)

Se você terminou de programar a mecânica base do jogo e tudo está funcionando, experimente estes upgrades:


📖 Gabarito Oficial de Código (Para Conferência)

📄 Código Completo (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 = false antes do delay de “MUITO BEM!”? Entre o jogador completar a sequência e iniciarNovaRodada() reiniciar passoJogador, há 1 segundo de espera. Sem travar jogoAtivo, um clique nesse intervalo chama aoClicar() com passoJogador == sequenciaComputador.size, e sequenciaComputador[passoJogador] lança IndexOutOfBoundsException — o app crasha. A mesma proteção já existe em gameOver() e em tocarSequencia() (enquanto a sequência pisca).


📐 Organização de Pastas

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á).


🛠️ Requisitos de Configuração e Solução de Problemas

Se você encontrar erros ao tentar rodar este projeto, verifique os pontos abaixo:

1. Sintaxe do XML (Para projetos com XML)

No Android, dimensões não aceitam pontos decimais.

2. Suporte ao AndroidX

O arquivo gradle.properties na raiz do projeto deve conter:

android.useAndroidX=true

android.enableJetifier=true

3. Estrutura de Pastas

O código fonte deve estar dentro de um módulo chamado app:

NomeDoProjeto/app/src/main/…

4. Recursos Obrigatórios

Certifique-se de ter os arquivos em res/values: colors.xml, strings.xml, themes.xml.

5. Configuração do Gradle

O arquivo settings.gradle na raiz deve conter include ‘:app’.

—`

🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd android_p03_genius_demo

2. Execute a aplicação e os testes

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