🧠 P02: Jogo da Memória Gamer POC (Guia Completo)

Neste projeto, você vai subir de nível! O desafio é criar um Jogo da Memória Gamer clássico simplificado em formato de Prova de Conceito (POC) com 8 cartas (4 pares). Você aprenderá a lidar com lógica de comparação de estados e controle de tempo com atraso programado sem precisar de nenhuma imagem ou recurso externo.

Usaremos números (1, 2, 3, 4) para representar as cartas e o símbolo ? para representar as cartas viradas para baixo!


✅ Pré-requisitos

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


🎯 Objetivo do Jogo

Criar uma grade de 4 linhas por 2 colunas com botões exibindo o símbolo ? (verso da carta). O jogador deve clicar em dois botões:

graph TD
    A[Início / Embaralhar] --> B[Clique na Carta 1]
    B --> C[Revelar Número 1]
    C --> D[Clique na Carta 2]
    D --> E[Revelar Número 2]
    E --> F{Os números são iguais?}
    F -- Sim --> G[Travar as cartas & Somar Ponto]
    F -- Não --> H[Aguardar 800ms & Voltar para ?]
    G --> I{Todos os 4 pares encontrados?}
    H --> B
    I -- Sim --> J[🏆 Vitória Gamer!]
    I -- Não --> B

📖 Dicionário do Projeto


🛠️ Passo 1: Configurando o Projeto no Android Studio

  1. Abra o Android Studio e clique em New Project.
  2. Escolha o template Empty Views Activity.
  3. Configure as informações do projeto:
    • Name: Memoria Gamer POC
    • Package Name: br.com.curso.memoria
    • Language: Java (O clássico).
    • Minimum SDK: API 24 (Android 7.0) ou superior.
  4. Clique em Finish e aguarde a sincronização inicial do Gradle.

🎨 Passo 2: Desenhando o Tabuleiro (XML)

Nossa interface usará um elegante tema escuro gamer (#1A1A1A), textos em ciano neon (#03DAC5) e uma grade de botões cinzas estilizados.

  1. Abra o arquivo app > src > main > res > layout > activity_main.xml.
  2. Substitua o conteúdo pelo código XML a seguir:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="8dp"
    android:background="#1A1A1A">

    <!-- Título Gamer -->
    <TextView
        android:id="@+id/txt_titulo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MEMÓRIA GAMER"
        android:textColor="#03DAC5"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="4dp" />

    <!-- Texto de Status das Ações -->
    <TextView
        android:id="@+id/txt_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Encontre os pares!"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:layout_marginBottom="12dp" />

    <!-- Grade 4x2 para 8 cartas -->
    <GridLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnCount="2"
        android:rowCount="4"
        android:layout_marginBottom="20dp">

        <Button android:id="@+id/card1" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card2" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card3" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card4" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card5" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card6" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card7" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card8" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />

    </GridLayout>

    <!-- Botão de Reiniciar -->
    <Button
        android:id="@+id/btn_reiniciar"
        android:layout_width="180dp"
        android:layout_height="50dp"
        android:text="REINICIAR JOGO"
        android:backgroundTint="#03DAC5"
        android:textColor="#1A1A1A"
        android:textStyle="bold" />

</LinearLayout>

🧠 Passo 3: Programando as Regras e o Jogo (Java)

Vamos implementar toda a mecânica de pareamento. A estrutura lógica funciona assim:

  1. Declaração de Listas: Criamos uma lista de Button para mapear os botões da tela e uma lista de String (tags) para armazenar os valores correspondentes (números 1, 2, 3, 4 duplicados).
  2. Inicialização e Embaralhamento: Fazemos as conexões via findViewById, preenchemos a lista com as tags numéricas e chamamos Collections.shuffle(tags) para misturar as cartas aleatoriamente.
  3. Lógica do Clique:
    • Exibe o número contido na tag da carta selecionada.
    • Se for a primeira carta, guarda a referência.
    • Se for a segunda carta, realiza a verificação de igualdade.
    • Se forem iguais: Desabilita ambas as cartas (setEnabled(false)) e incrementa o total de acertos.
    • Se forem diferentes: Ativa a flag de bloqueio (aguardando = true), aguarda 800ms usando um Handler para voltar os textos para ? e libera novos toques.

🛠️ Requisitos Críticos de Configuração

1. Flag do AndroidX

Confirme se o arquivo gradle.properties na raiz do seu projeto possui estas duas propriedades de compatibilidade ativas:

android.useAndroidX=true
android.enableJetifier=true

2. Formato de Dimensões em XML

Tamanhos de margens e padding (dp) no Android não aceitam casas decimais.


📸 Resultado Esperado

Veja como sua tela deve ficar ao final deste projeto:

Tabuleiro 2x2 (4 cartas) com uma carta revelada Tabuleiro com 8 cartas (4 pares), todas viradas para baixo Tabuleiro com 8 cartas — uma carta revelada (Pikachu)


🏆 Desafios para você (Upgrade!)

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


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

Use os códigos completos abaixo para validar sua implementação ou corrigir problemas de compilação.

📄 Layout Completo (activity_main.xml)

Disponível em: app/src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="8dp"
    android:background="#1A1A1A">

    <TextView
        android:id="@+id/txt_titulo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MEMÓRIA GAMER"
        android:textColor="#03DAC5"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="4dp" />

    <TextView
        android:id="@+id/txt_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Encontre os pares!"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:layout_marginBottom="12dp" />

    <!-- Grade 4x2 para 8 cartas -->
    <GridLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnCount="2"
        android:rowCount="4"
        android:layout_marginBottom="20dp">

        <Button android:id="@+id/card1" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card2" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card3" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card4" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card5" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card6" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card7" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />
        <Button android:id="@+id/card8" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:backgroundTint="#2C2C2C" android:textColor="#03DAC5" android:textSize="32sp" android:textStyle="bold" android:text="?" />

    </GridLayout>

    <Button
        android:id="@+id/btn_reiniciar"
        android:layout_width="180dp"
        android:layout_height="50dp"
        android:text="REINICIAR JOGO"
        android:backgroundTint="#03DAC5"
        android:textColor="#1A1A1A" />

</LinearLayout>

📄 Lógica Java Completa (MainActivity.java)

Localizado em: app/src/main/java/br/com/curso/memoria/MainActivity.java

package br.com.curso.memoria;

import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    // Controles de estado das seleções de cartas
    private Button primeiraCarta = null;
    private Button segundaCarta = null;
    private boolean aguardando = false; // Bloqueia cliques rápidos durante o delay
    private int paresEncontrados = 0;
    
    private TextView txtStatus;
    
    // Listas para gerenciar dinamicamente as referências de tela e valores
    private final List<Button> cards = new ArrayList<>();
    private final List<String> tags = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Vinculando componentes de texto e ações
        txtStatus = findViewById(R.id.txt_status);
        Button btnReiniciar = findViewById(R.id.btn_reiniciar);
        btnReiniciar.setOnClickListener(v -> reiniciarJogo());

        // 1. Inicializando as referências dos 8 botões (cartas) do tabuleiro
        cards.add(findViewById(R.id.card1));
        cards.add(findViewById(R.id.card2));
        cards.add(findViewById(R.id.card3));
        cards.add(findViewById(R.id.card4));
        cards.add(findViewById(R.id.card5));
        cards.add(findViewById(R.id.card6));
        cards.add(findViewById(R.id.card7));
        cards.add(findViewById(R.id.card8));

        // 2. Definindo a lista de números (4 pares = 8 elementos)
        tags.add("1");
        tags.add("2");
        tags.add("3");
        tags.add("4");
        tags.add("1");
        tags.add("2");
        tags.add("3");
        tags.add("4");

        // Dá o pontapé inicial no jogo
        iniciarJogo();
    }

    /**
     * Reseta as pontuações, embaralha o tabuleiro e redefine as cartas
     */
    private void iniciarJogo() {
        paresEncontrados = 0;
        txtStatus.setText("Encontre os pares!");
        
        // Embaralha os números aleatoriamente
        Collections.shuffle(tags);

        // Configura cada Button individualmente
        for (int i = 0; i < cards.size(); i++) {
            Button card = cards.get(i);
            
            // Atribui o número (tag) para a carta
            card.setTag(tags.get(i));
            
            card.setEnabled(true); // Garante que a carta possa ser clicada
            card.setText("?"); // Mostra a interrogação (verso)
            
            // Configura o ouvinte de clique
            card.setOnClickListener(v -> aoClicarNaCarta(card));
        }
    }

    /**
     * Executa a regra ao clicar em uma carta
     */
    private void aoClicarNaCarta(Button carta) {
        // Ignora cliques se o jogo estiver aguardando o delay de erro
        // ou se o usuário clicar duas vezes seguidas na mesma carta
        if (aguardando || carta == primeiraCarta) return;

        revelarCarta(carta);

        if (primeiraCarta == null) {
            // Primeiro clique da jogada
            primeiraCarta = carta;
        } else {
            // Segundo clique da jogada: realiza a verificação
            segundaCarta = carta;
            verificarPar();
        }
    }

    /**
     * Revela o número do Button lendo-o a partir da tag associada
     */
    private void revelarCarta(Button carta) {
        String tag = (String) carta.getTag();
        carta.setText(tag);
    }

    /**
     * Verifica se os dois cards revelados possuem a mesma tag numérica
     */
    private void verificarPar() {
        if (primeiraCarta.getTag().equals(segundaCarta.getTag())) {
            // SUCESSO: Par encontrado!
            paresEncontrados++;
            
            // Trava as cartas para não receberem mais cliques
            primeiraCarta.setEnabled(false);
            segundaCarta.setEnabled(false);
            
            Toast.makeText(this, "Par encontrado! 🎉", Toast.LENGTH_SHORT).show();
            
            resetarSelecao();
            verificarVitoria();
        } else {
            // ERRO: Cartas diferentes. Aguarda 800ms antes de virá-las de volta
            aguardando = true;
            
            new Handler().postDelayed(() -> {
                // Vira de volta para a interrogação
                primeiraCarta.setText("?");
                segundaCarta.setText("?");
                
                resetarSelecao();
                aguardando = false; // Libera novos cliques
            }, 800);
        }
    }

    /**
     * Valida se todos os pares foram achados
     */
    private void verificarVitoria() {
        if (paresEncontrados == 4) {
            txtStatus.setText("🏆 VOCÊ É UM MESTRE!");
            Toast.makeText(this, "Incrível! Você encontrou todos os pares! 🎮", Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Limpa as referências das cartas selecionadas na rodada
     */
    private void resetarSelecao() {
        primeiraCarta = null;
        segundaCarta = null;
    }

    /**
     * Reinicia a partida do zero
     */
    private void reiniciarJogo() {
        resetarSelecao();
        aguardando = false;
        iniciarJogo();
    }
}