Jogo da Velha com Arduino, OLED e Teclado 4×4 — Animações, Sons e Modo IA - Arduino jogo #07

Jogo 7 : Jogo da Velha: Jogador X Jogador e Jogador X Máquina

Objetivo

O objetivo do jogo é permitir que dois jogadores — ou um jogador contra a IA — disputem partidas de Jogo da Velha usando um Arduino, teclado matricial 4×4, display OLED e um buzzer. O sistema controla o tabuleiro, indica a vez de cada jogador, exibe o placar, evita jogadas inválidas e mostra animações e efeitos sonoros especiais a cada jogada, vitória ou empate.

Além de jogar, o usuário aprende diferentes conceitos de eletrônica e programação enquanto interage com uma interface divertida e intuitiva.

Experiência de Criação

É importante salientar que esse jogo foi criado em parceria criativa com a inteligência do ChatGpt. Começamos de um tabuleiro básico e evoluimos para um sistema completo com IA em três em três níveis (fraca, média e forte), alternância automática de turnos, placar, reinicialização inteligente e efeitos visuais e sonoros. Toda a lógica foi otimizada para rodar de forma fluida no Arduino, incluindo animações, detecção de vitória, minimax ajustado e controles via keypad. 

A cada ajuste, evolução e correção, o projeto foi tomando forma até se transformar num jogo completo, fluido e profissional — resultado de um diálogo constante, criativo e bem-humorado. A inteligência do ChatGPT ajudou a estruturar, otimizar e corrigir o código, enquanto eu guiei todo o processo com ideias, testes práticos e decisões inteligentes de design.

O resultado? Um dos jogos da velha mais completos já feitos em Arduino, construído literalmente a quatro mãos: código, criatividade e tecnologia trabalhando juntos.

Referências

Aplicação

Para fins didáticos e diversão.

Componentes necessários

Referência

Componente

Quantidade

Imagem

Observação

Protoboard Protoboard 400 pontos 1 Resultado de imagem para protoboard 830v

Opcional

Jumpers Kit cabos ligação macho / macho 1  
Teclado Matricial de Membrana

Teclado Matricial de Membrana 4 X 4 (16 teclas)

1

Teclas: 16

Conector: 8 pinos (2,54mm)

Montagem: Auto-Adesivo

Limites de Operação: 35VDC, 100mA

Isolação: 100MΩ, 100V

Tempo de contato: 5ms

Durabilidade: 1 milhão de ciclos por tecla

Temperatura de Funcionamento: 0-70°C

Display OLED

Display OLED SSD1306

1 Tarzan Componentes Eletrônicos | Shield Arduino| Display Oled 1.3 Polegadas  Branco 125x64 I2c - R$ 51,90

 

– Tensão de operação: 3,3-5V
– Controlador: SSD1306
– Cor: Azul e Amarelo
Comunicação: I2C
– Resolução: 128×64
– Dimensões: 30 x 27mm

Você também poderá utilizar na cor azul, branco ou colorido.

A comunicação deverá ser I2C

Buzzer Buzzer passivo 5V 12mm 1

 

Arduino UNO R3 Arduino UNO 1

Você poderá utilizar uma placa Arduino UNO original ou similar

Montagem do Circuito

Atenção

1. Verifique abaixo como devem ser as conexões do Teclado Matricial no Arduino:

Conexões do Teclado Matricial no Arduino

Pinos de conexão do Teclado Matricial

1.1. Tabela de conexão com o Arduino (de acordo com o nosso exemplo)

Pinos do Teclado Matricial Pinos Digitais do Arduino
L1 9
L2 8
L3 7
L4 6
C1 5
C2 4
C3 3
C4 2

2. Para a montagem do display OLED SSD1306 via protocolo I2C, verifique a "pinagem" do módulo com comunicação I2C:

Obs.: Em algumas marcas os pino Vcc e GND estão invertidos. Portanto, preste atenção fazer a conexão correta.

 

 2.1. Faça a conexão dos pinos SCL e SDA com o Arduino da seguinte forma:

 MICROCONTROLADOR  SCL  SDA
 Arduino Uno  A5  A4
 Arduino Nano  A5  A4
 Arduino Mega  21  20
 Leonardo/Micro  3  2

2.2. No nosso projeto utilizamos um display OLED SSD1306 I2C e Arduino Uno. Veja abaixo como fizemos a conexão.

3. O buzzer tem polaridade. Portando, cuidado para não ligar o buzzer invertido. Se você retirar o adesivo superior do buzzzer poderá ver um sinal de positivo (+). Este sinal mostra onde está o pino positivo do componente que deverá estar conectado ao pino digital do Arduino. Para melhor qualidade do som, utilize um buzzer passivo de 5V.

Incluindo a biblioteca Keypad

Para facilitar o uso do Teclado Matricial de Membrana 4x4, utilizaremos a biblioteca Keypad, que automatiza o processo de detecção das teclas pressionadas. Com ela, não é necessário lidar diretamente com os detalhes da varredura multiplexada, pois toda a lógica de leitura da matriz de linhas e colunas é gerenciada internamente pela biblioteca, tornando o desenvolvimento mais simples e eficiente.

Instalação da biblioteca

1. No IDE do Arduino, acesse a aba Sketch (Rascunho), selecione [Incluir Biblioteca] e depois [Gerenciar Bibliotecas...].

2.  No campo de pesquisa digite Keypad. Localizada a biblioteca Keypad (por Mark Stanley, Alexander) clique no botão [Instalar].

3. Após a instalação, observe que aparecerá a informação que a biblioteca foi instalada.

Incluindo as bibliotecas Adafruit SSD1306 e Adafruit GFX

Para utilizar o controlador SSD1306 vamos incluir a biblioteca Adafruit SSD1306. Esta biblioteca foi escrita para resolver a complexidade do controlador SSD1306, oferecendo comandos simples para facilitar o controle de operação do display OLED.

Para obtermos uma experiência completa, precisamos instalar também a biblioteca Adafruit GFX para exibir elementos gráficos primitivos como pontos, linhas, círculos, retângulos, etc.

Instalação das bibliotecas

No IDE do Arduino, acesse a aba Sketch (Rascunho), selecione [Incluir Biblioteca] e depois [Gerenciar Bibliotecas...].

 2. No campo de pesquisa digite ssd1306 adafruit. Localizada a biblioteca Adafruit SSD1306 clique no botão [Instalar].

Na janela "Dependencies for library Adafruit SSD1306..." clique no botão [Install all]. Desta forma, instalaremos as bibliotecas Adafruit SSD1306, Adafruit GFX library e Adafruit BusIO todas juntas automaticamente.

4. Após a instalação, observe que aparecerá a informação que as bibliotecas foram instaladas. (Lembre-se que precisaremos das bibliotecas Adafruit SSD1306 e Adafruit GFX Library para controlar nosso display OLED)

Biblioteca Wire

1. Para realizar a comunicação via protocolo I2C (comunicação com 2 fios: clock (SCL) e dado (SDA) podemos utilizar a biblioteca Wire.h que já vem instalada por padrão no IDE do Arduino.

2. A biblioteca Wire.h é responsável por conter as funções necessárias para gerenciar a comunicação entre os dispositivos através do protocolo I2C.

Código do Projeto (Sketch)

1. Faça o download e abra o arquivo jogo7.ino no IDE do Arduino: DOWNLOAD - jogo7.ino

2. Se preferir, copie e cole o código abaixo no IDE do Arduino:

/*************************************************************
  Jogo da Velha — OLED SSD1306 128x64 + Keypad 4x4 + Buzzer (passivo)
  Versão: alternância e reinício automático
  - Sons estilo 8-bit (melodias)
  - Humano: X (sempre)
  - IA: O (sempre) — níveis IA1 (fraca), IA2 (média), IA3 (forte)
  - Tecla A: PvP -> IA1 -> IA2 -> IA3 -> PvP
  - Tecla B: muda nível IA (quando em IA)
  - Tecla *: reinicia partida (mantém placar, alterna iniciador)
  - Tecla #: zera placar e reinicia (iniciador volta para X)
  - Vez e Modo no header; placar X= / O=
  - POP animation, risco/pisca no trio vencedor, reinício automático

  Autor: Angelo Luis Ferreira com auxílio do ChatGPT
  05/12/2025
*************************************************************/

#include <Keypad.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 oled(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);

// Keypad
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
byte rowPins[ROWS] = {9,8,7,6};
byte colPins[COLS] = {5,4,3,2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// Buzzer (use buzzer passivo)
#define BUZZER 10

// Game state
char board[9];         // ' ' | 'X' | 'O'
bool isPvAI = true;    // current mode; if false => PvP
bool humanTurn = true; // true = X (human) turn
bool nextStartsX = true; // alternator for who starts next match
int scoreX = 0;
int scoreO = 0;

// IA difficulty: 1 = IA1 (weak), 2 = IA2 (medium), 3 = IA3 (strong)
int aiLevel = 3;

// Layout
const int headerHeight = 16;
const int boardTop = headerHeight;
int cellW, cellH;

// redraw control
bool needRedraw = true;
bool gameActive = false; // NEW: only true when board is drawn and ready to accept moves/IA

// Winning combos
const int WINS[8][3] = {
  {0,1,2},{3,4,5},{6,7,8},
  {0,3,6},{1,4,7},{2,5,8},
  {0,4,8},{2,4,6}
};

// Prototypes
void initBoard();
void drawScreen();
void drawHeader();
void drawBoardLines();
void drawPieces();
void popupAnimate(int pos, char p);
void popupAnimateCore(int pos, char p);
int evaluateBoard(const char b[9]);
bool movesLeft(const char b[9]);
int minimax(char b[9], bool isMax, int depth);
int bestMoveMinimax();
int aiMove();
int aiMoveMedium();
int aiMoveWeak();
void victoryBlink(int winIndex, char winnerChar);
void drawWinningLine(int winIndex);
void playStartSound();
void playToggleSound();
void playSoundX();
void playSoundO();
void playErrorSound();
void playVictoryMelody();
void playDrawMelody();
void toneDelay(int freq, int dur);

// ------------------------------------------------------------------

void setup() {
  pinMode(BUZZER, OUTPUT);
  randomSeed(analogRead(A0));

  if(!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (1);
  }
  oled.clearDisplay();
  oled.display();

  int availH = OLED_HEIGHT - headerHeight; // 48
  cellW = OLED_WIDTH / 3; // ~42
  cellH = availH / 3;     // 16

  // initial starter = X
  nextStartsX = true;
  humanTurn = nextStartsX;
  initBoard();

  // initial splash
  oled.setTextSize(1);
  oled.setTextColor(SSD1306_WHITE);
  oled.setCursor(10,20);
  oled.print("Jogo da Velha");
  oled.setCursor(10,34);
  oled.print("Pressione * para iniciar");
  oled.display();
  playStartSound();
}

void loop() {
  // If PvAI and it's AI turn, let AI play immediately (no waiting for key)
  // IMPORTANT: only when gameActive == true (board drawn and ready)
  if (isPvAI && !humanTurn && gameActive) {
    delay(120); // small "thinking"
    int mv = aiMove();
    if (mv >= 0) {
      board[mv] = 'O';
      popupAnimate(mv, 'O');
      playSoundO();
      humanTurn = true;
      needRedraw = true;
      // after a move, leave gameActive true until redraw sets it again
    }
  }

  char k = keypad.getKey();
  if (k) {
    if (k == 'A') {
      // Cycle: PvP -> IA1 -> IA2 -> IA3 -> PvP
      if (!isPvAI) {
        isPvAI = true;
        aiLevel = 1;
      } else {
        aiLevel++;
        if (aiLevel > 3) {
          isPvAI = false;
          aiLevel = 3; // keep last for re-entry
        }
      }
      playToggleSound();
      needRedraw = true;
    }
    else if (k == 'B') {
      // Only change aiLevel if currently IA mode
      if (isPvAI) {
        aiLevel++;
        if (aiLevel > 3) aiLevel = 1;
        playToggleSound();
        needRedraw = true;
      } else {
        playErrorSound();
      }
    }
    else if (k == '*') {
      // alterna quem inicia and restart (keep scores)
      nextStartsX = !nextStartsX;
      humanTurn = nextStartsX; // set who starts BEFORE initBoard
      initBoard();
      playStartSound();
    }
    else if (k == '#') {
      // zero scores and restart (starter back to X)
      scoreX = 0; scoreO = 0;
      nextStartsX = true;
      humanTurn = nextStartsX; // ensure X starts
      initBoard();
      playStartSound();
      needRedraw = true;
    }
    else if (k >= '1' && k <= '9') {
      int pos = k - '1';
      if (pos < 0 || pos > 8) return;

      if (!gameActive) {
        // ignore key presses while transitioning (optional feedback)
      } else {
        if (humanTurn) {
          if (board[pos] == ' ') {
            board[pos] = 'X';
            popupAnimate(pos, 'X');
            playSoundX();
            humanTurn = false;
            needRedraw = true;
          } else {
            playErrorSound();
          }
        } else {
          // if PvP and it's O human's turn, accept move
          if (!isPvAI) {
            if (board[pos] == ' ') {
              board[pos] = 'O';
              popupAnimate(pos, 'O');
              playSoundO();
              humanTurn = true;
              needRedraw = true;
            } else {
              playErrorSound();
            }
          }
        }
      }
    }
  }

  if (needRedraw) {
    // Draw and mark gameActive true AFTER drawing so AI can act properly
    drawScreen();
    needRedraw = false;
    gameActive = true;

    // after drawing, check winner/draw instantly so UI is coherent
    int winIndex = -1;
    for (int i=0;i<8;i++) {
      int a = WINS[i][0], b = WINS[i][1], c = WINS[i][2];
      if (board[a] != ' ' && board[a] == board[b] && board[b] == board[c]) {
        winIndex = i;
        break;
      }
    }

    if (winIndex != -1) {
      // freeze input while showing sequence
      gameActive = false;
      char winnerChar = board[WINS[winIndex][0]]; // 'X' or 'O'
      if (winnerChar == 'X') scoreX++; else scoreO++;
      playVictoryMelody();
      victoryBlink(winIndex, winnerChar);

      // prepare next match: toggle starter, set humanTurn accordingly, then initBoard()
      nextStartsX = !nextStartsX;
      humanTurn = nextStartsX; // set who will start next
      initBoard();             // clears board and marks needRedraw true
      // we intentionally keep needRedraw true to trigger drawScreen in next loop
      return;
    }

    // check draw
    bool full = true;
    for (int i=0;i<9;i++) if (board[i] == ' ') { full = false; break; }
    if (full) {
      gameActive = false;
      playDrawMelody();
      // blink whole board to show draw
      for (int t=0;t<3;t++) {
        oled.clearDisplay();
        oled.display();
        delay(160);
        drawScreen();
        delay(160);
      }
      // next match: toggle starter, set humanTurn, initBoard
      nextStartsX = !nextStartsX;
      humanTurn = nextStartsX;
      initBoard();
      return;
    }
  }

  delay(8);
}

// -------------------------- AUX --------------------------------

void initBoard() {
  for (int i=0;i<9;i++) board[i] = ' ';
  // IMPORTANT: do NOT overwrite humanTurn here (set by caller)
  gameActive = false;   // not active until board is drawn
  needRedraw = true;    // request redraw; after drawScreen we'll set gameActive=true
}

void drawScreen() {
  oled.clearDisplay();
  drawHeader();
  drawBoardLines();
  drawPieces();
  oled.display();
}

void drawHeader() {
  // Line 0: X=score (left) and O=score (right)
  // Line 1: Vez:X/O    Modo: PvP / IA1 / IA2 / IA3
  oled.setTextSize(1);
  oled.setTextColor(SSD1306_WHITE);

  // X score left
  oled.setCursor(0,0);
  oled.print("X=");
  oled.print(scoreX);

  // O score right
  oled.setCursor(96,0);
  oled.print("O=");
  oled.print(scoreO);

  // Second line: Vez and Modo
  oled.setCursor(0,8);
  oled.print("Vez:");
  oled.print(humanTurn ? "X" : "O");

  // Modo to the right
  oled.setCursor(68,8);
  oled.print("Modo:");
  oled.print(" ");
  if (!isPvAI) {
    oled.print("PvP");
  } else {
    if (aiLevel == 1) oled.print("IA1");
    else if (aiLevel == 2) oled.print("IA2");
    else oled.print("IA3");
  }
}

void drawBoardLines() {
  int y1 = boardTop + cellH;
  int y2 = boardTop + 2*cellH;
  oled.drawLine(0, y1, OLED_WIDTH, y1, SSD1306_WHITE);
  oled.drawLine(0, y2, OLED_WIDTH, y2, SSD1306_WHITE);

  int x1 = cellW;
  int x2 = 2*cellW;
  oled.drawLine(x1, boardTop, x1, OLED_HEIGHT, SSD1306_WHITE);
  oled.drawLine(x2, boardTop, x2, OLED_HEIGHT, SSD1306_WHITE);
}

void drawPieces() {
  for (int i=0;i<9;i++) {
    int row = i / 3;
    int col = i % 3;
    int cx = col * cellW + cellW/2;
    int cy = boardTop + row * cellH + cellH/2;
    int r = min(cellW, cellH) / 3;
    if (board[i] == 'X') {
      oled.drawLine(cx - r, cy - r, cx + r, cy + r, SSD1306_WHITE);
      oled.drawLine(cx + r, cy - r, cx - r, cy + r, SSD1306_WHITE);
    } else if (board[i] == 'O') {
      oled.drawCircle(cx, cy, r, SSD1306_WHITE);
    }
  }
}

// ---------------------- POP animation ------------------------

void popupAnimate(int pos, char p) {
  popupAnimateCore(pos, p);
  needRedraw = true;
}

void popupAnimateCore(int pos, char p) {
  int row = pos / 3;
  int col = pos % 3;
  int cx = col * cellW + cellW/2;
  int cy = boardTop + row * cellH + cellH/2;
  int rmax = min(cellW, cellH) / 3;

  for (int r = 2; r <= rmax; r += 2) {
    oled.clearDisplay();
    drawHeader();
    drawBoardLines();

    // draw existing pieces except the animating one
    for (int i = 0; i < 9; i++) {
      if (i == pos) continue;
      int rowi = i / 3, coli = i % 3;
      int cxi = coli * cellW + cellW/2;
      int cyi = boardTop + rowi * cellH + cellH/2;
      int rr = min(cellW, cellH) / 3;
      if (board[i] == 'X') {
        oled.drawLine(cxi - rr, cyi - rr, cxi + rr, cyi + rr, SSD1306_WHITE);
        oled.drawLine(cxi + rr, cyi - rr, cxi - rr, cyi + rr, SSD1306_WHITE);
      } else if (board[i] == 'O') {
        oled.drawCircle(cxi, cyi, rr, SSD1306_WHITE);
      }
    }

    // draw growing piece
    if (p == 'X') {
      oled.drawLine(cx - r, cy - r, cx + r, cy + r, SSD1306_WHITE);
      oled.drawLine(cx + r, cy - r, cx - r, cy + r, SSD1306_WHITE);
    } else {
      oled.drawCircle(cx, cy, r, SSD1306_WHITE);
    }

    oled.display();
    delay(36);
  }
}

// ---------------------- AI (3 níveis) ------------------------

int evaluateBoard(const char b[9]) {
  for (int i=0;i<8;i++) {
    int a=WINS[i][0], bb=WINS[i][1], c=WINS[i][2];
    if (b[a] != ' ' && b[a] == b[bb] && b[bb] == b[c]) {
      if (b[a] == 'O') return +10;
      if (b[a] == 'X') return -10;
    }
  }
  return 0;
}

bool movesLeft(const char b[9]) {
  for (int i=0;i<9;i++) if (b[i] == ' ') return true;
  return false;
}

int minimax(char b[9], bool isMax, int depth, int alpha, int beta) {
  int score = evaluateBoard(b);
  if (score == 10) return score - depth;
  if (score == -10) return score + depth;
  if (!movesLeft(b)) return 0;

  if (isMax) { // O (AI)
    int best = -1000;

    for (int i = 0; i < 9; i++) {
      if (b[i] == ' ') {
        b[i] = 'O';
        int val = minimax(b, false, depth + 1, alpha, beta);
        b[i] = ' ';
        best = max(best, val);
        alpha = max(alpha, best);
        if (beta <= alpha) break; // poda
      }
    }
    return best;
  } else { // X (humano)
    int best = 1000;

    for (int i = 0; i < 9; i++) {
      if (b[i] == ' ') {
        b[i] = 'X';
        int val = minimax(b, true, depth + 1, alpha, beta);
        b[i] = ' ';
        best = min(best, val);
        beta = min(beta, best);
        if (beta <= alpha) break; // poda
      }
    }
    return best;
  }
}

int bestMoveMinimax() {
  int bestVal = -1000;
  int bestMove = -1;

  for (int i = 0; i < 9; i++) {
    if (board[i] == ' ') {
      board[i] = 'O';
      int moveVal = minimax(board, false, 0, -1000, 1000); // agora com alpha/beta
      board[i] = ' ';
      if (moveVal > bestVal) {
        bestVal = moveVal;
        bestMove = i;
      }
    }
  }

  return bestMove;
}

// IA média: heuristic (win/block/center/opposite corner/corner/side)
int aiMoveMedium() {
  // 1 - win immediate
  for (int i=0;i<9;i++) if (board[i]==' ') {
    board[i] = 'O';
    for (int w=0; w<8; w++) {
      int a=WINS[w][0], b=WINS[w][1], c=WINS[w][2];
      if (board[a] != ' ' && board[a]==board[b] && board[b]==board[c]) {
        board[i] = ' ';
        return i;
      }
    }
    board[i] = ' ';
  }
  // 2 - block opponent
  for (int i=0;i<9;i++) if (board[i]==' ') {
    board[i] = 'X';
    for (int w=0; w<8; w++) {
      int a=WINS[w][0], b=WINS[w][1], c=WINS[w][2];
      if (board[a] != ' ' && board[a]==board[b] && board[b]==board[c]) {
        board[i] = ' ';
        return i;
      }
    }
    board[i] = ' ';
  }
  // 3 - center
  if (board[4] == ' ') return 4;
  // 4 - opposite corner
  int corners[4] = {0,2,6,8};
  for (int i=0;i<4;i++) {
    int c = corners[i];
    int opp = 8 - c;
    if (board[c]=='X' && board[opp]==' ') return opp;
  }
  // 5 - empty corner
  for (int i=0;i<4;i++) if (board[corners[i]]==' ') return corners[i];
  // 6 - side
  int sides[4] = {1,3,5,7};
  for (int i=0;i<4;i++) if (board[sides[i]]==' ') return sides[i];
  // fallback
  for (int i=0;i<9;i++) if (board[i]==' ') return i;
  return -1;
}

// IA fraca: random available
int aiMoveWeak() {
  int empties[9], n=0;
  for (int i=0;i<9;i++) if (board[i]==' ') empties[n++]=i;
  if (n==0) return -1;
  return empties[random(0,n)];
}

int aiMove() {
  if (aiLevel == 1) return aiMoveWeak();
  if (aiLevel == 2) return aiMoveMedium();
  return bestMoveMinimax(); // strong
}

// ------------------ victory blink / line --------------------

void victoryBlink(int winIndex, char winnerChar) {
  // blink 3 times: show thick line overlay then clear (pieces remain)
  for (int t = 0; t < 3; t++) {
    // show line overlay
    drawScreen();
    drawWinningLine(winIndex);
    oled.display();
    // short tone bursts while showing
    if (winnerChar == 'X') {
      toneDelay(1200 + t*80, 110);
    } else {
      toneDelay(800 + t*60, 110);
    }
    delay(160);
    // clear overlay (just redraw)
    drawScreen();
    oled.display();
    delay(120);
  }
}

void drawWinningLine(int winIndex) {
  int a = WINS[winIndex][0];
  int c = WINS[winIndex][2];

  int ar = a / 3, ac = a % 3;
  int cr = c / 3, cc = c % 3;

  int ax = ac * cellW + cellW/2;
  int ay = boardTop + ar * cellH + cellH/2;
  int cx = cc * cellW + cellW/2;
  int cy = boardTop + cr * cellH + cellH/2;

  // thicker line
  for (int w = -2; w <= 2; w++) {
    oled.drawLine(ax + w, ay + w, cx + w, cy + w, SSD1306_WHITE);
  }
}

// ------------------ SOUND (8-bit style) --------------------
// Small helper: play tone and block for dur ms (for chiptune feel)
void toneDelay(int freq, int dur) {
  tone(BUZZER, freq);
  delay(dur);
  noTone(BUZZER);
  delay(8); // tiny gap for chiptune articulation
}

// Start game melody (short fanfare)
void playStartSound() {
  toneDelay(520, 80);
  toneDelay(780, 80);
  toneDelay(1040, 120);
}

// Toggle / change mode sound (short chirp)
void playToggleSound() {
  toneDelay(480, 40);
  toneDelay(720, 40);
}

// Sound for player X (arpeggio, 8-bit)
void playSoundX() {
  toneDelay(900, 60);
  toneDelay(1200, 50);
  toneDelay(1500, 70);
}

// Sound for player O (IA) (lower arpeggio)
void playSoundO() {
  toneDelay(650, 70);
  toneDelay(850, 60);
  toneDelay(1050, 70);
}

// Error / occupied position (vibrato-ish)
void playErrorSound() {
  for (int i=0;i<3;i++){
    tone(BUZZER, 220 + i*20);
    delay(60);
  }
  noTone(BUZZER);
  delay(30);
}

// Victory melody (more elaborate ~700ms)
void playVictoryMelody() {
  // a short 8-bit celebratory phrase
  toneDelay(880, 80);
  toneDelay(1320, 80);
  toneDelay(1760, 100);
  toneDelay(1320, 80);
  toneDelay(1040, 80);
  toneDelay(880, 120);
}

// Draw/Empate melody (short)
void playDrawMelody() {
  toneDelay(600, 80);
  toneDelay(500, 80);
  toneDelay(400, 120);
}

Vídeo

Como o jogo funciona — Regras, Fluxo e Modos

O Jogo da Velha foi projetado para rodar em um módulo OLED SSD1306, receber comandos via teclado matricial 4×4 e emitir sons retrô via buzzer passivo.

Ele inclui placar permanente, animações, IA com três níveis e alternância automática de quem começa a partida.

A seguir está a descrição completa do funcionamento:

1. Tela inicial

Ao ligar o sistema, a tela exibe:

  • Título “Jogo da Velha”

  • Toque musical de abertura (8-bit style)

2. Estrutura da tela durante a partida

O OLED é organizado da seguinte forma:

Cabeçalho (2 linhas superiores)

Exibe:

  • Placar:
    X=0 (à esquerda)
    O=0 (à direita)

  • Vez atual:
    Ex.: Vez:X ou Vez:O

  • Modo do jogo:
    Modo: PvP
    Modo: IA1
    Modo: IA2
    Modo: IA3

Área do tabuleiro (restante da tela)

Um tabuleiro 3×3 é desenhado com linhas finas e peças bem proporcionadas:

  • X é representado com um X estilizado

  • O é representado com um círculo

3. Como jogar

Jogador Humano

Sempre joga com X.

Para jogar:

  • Pressione uma tecla de 1 a 9.

  • Cada número corresponde à posição do tabuleiro:

Se a posição estiver vazia:

  • É desenhado um efeito POP animado

  • Um som 8-bit próprio da peça X é tocado

Se estiver ocupada:

  • Um som de erro é emitido

  • Nada é alterado

4. Modos de jogo (tecla A)

Pressionar A alterna ciclicamente entre:

1️⃣ PvP (Jogador vs Jogador)
2️⃣ IA1 (IA fraca)
3️⃣ IA2 (IA média)
4️⃣ IA3 (IA forte)
⬅ Volta para PvP

Um som curto de alternância é emitido ao mudar o modo.

5. Ajuste de nível da IA (tecla B)

Disponível somente quando o modo é IA.

Ao pressionar B, o nível muda:

  • IA1 → IA2 → IA3 → IA1…

Se o modo atual for PvP:

  • É emitido um som de erro

  • O nível não muda

6. Funcionamento da IA

A IA usa sempre a peça O e joga automaticamente assim que chega sua vez — sem necessidade de pressionar teclas.

Os níveis funcionam assim:

IA1 – Fraca

  • Jogadas randômicas

  • Fácil de vencer

  • Ideal para iniciantes

IA2 – Média

  • Heurística inteligente:

    • Tenta ganhar

    • Bloqueia o jogador

  • Boa para aprendizado

IA3 – Forte

  • IA perfeita usando Minimax

  • Impossível de derrotar

  • Otimizada para responder mais rápido no Arduino

7. Alternância do primeiro jogador

Após qualquer partida, vencida ou empatada:

  • O jogo reinicia automaticamente

  • O início alterna entre:

    • X (Humano)

    • O (IA)

  • Sem necessidade de pressionar teclas

Quando O deve começar:

  • A IA joga imediatamente

  • Nenhuma espera do usuário

8. Reinício manual

Tecla *

Reinicia a partida:

  • Mantém o placar

  • Alterna quem inicia

  • Toca um som especial de início

Tecla #

Zera o placar:

  • Placar X=0 / O=0

  • Reinicia a partida

  • O iniciador volta para X

  • Toca som especial de reset

9. Sistema de Vitória (animação e som)

Quando um trio é formado:

  1. Uma melodia retrô de vitória toca

  2. O trio vencedor pisca com uma linha grossa desenhada sobre as 3 peças

  3. A tela volta automaticamente ao tabuleiro limpo

  4. O próximo jogador inicial é alternado

  5. O jogo continua sem interrupções

O placar é atualizado automaticamente:

  • Vitória de X: X++

  • Vitória de O: O++

10. Empate

Ao preencher o tabuleiro sem vitória:

  1. Toca-se uma melodia de empate

  2. O tabuleiro pisca algumas vezes

  3. Inicia-se nova partida automaticamente

  4. Alternando quem começa

11. Sons 8-bit

O jogo possui uma trilha de feedback sonoro completa:

  • POP X

  • POP O

  • Erro

  • Modo alterado

  • Início da partida

  • Reinício total (#)

  • Vitória

  • Empate

Todos os sons são estilosos e tem “cara de videogame antigo”.

12. Velocidade e desempenho

O jogo é completamente otimizado:

  • Sem piscadas no teclado

  • IA sempre responsiva

  • IA forte com otimização especial (Minimax rápido)

  • Renderização limpa e sem flicker

  • POP animation é rápida e fluida

Objetivo Didático

Este projeto foi desenvolvido para ajudar programadores iniciantes e intermediários a dominarem, de forma prática e completa, vários conceitos fundamentais de eletrônica, lógica de programação, interação homem-máquina e inteligência artificial aplicada em microcontroladores.

Interface gráfica em OLED (SSD1306)

O projeto mostra a manipular a tela OLED 128×64 usando a biblioteca Adafruit, incluindo:

    • Desenho de linhas finas e proporcionais

    • Construção de tabuleiros e grades

    • Impressão de textos compactos e alinhados

    • Animações visuais (efeito POP e piscagem do trio vencedor)

    • Renderização otimizada para evitar flicker (cintilação) e maximizar performance

Leitura e varredura de teclado matricial 4×4

O projeto mostra:

    • Como ler teclas sem travar o loop principal

    • Como filtrar comandos inválidos

    • Como mapear números para posições do tabuleiro

    • Como criar teclas especiais:

      • A → alterna modos (PvP / IA1 / IA2 / IA3)

      • B → ajusta dificuldade da IA

      • * → reinicia partida mantendo placar e alternando quem começa

      • # → zera placar e reinicia completamente

Estruturação de lógica de jogo Completa

O leitor entende como construir toda a lógica interna:

    • Representação do tabuleiro em array linear de 9 posições

    • Alternância correta e automática entre jogadores

    • detecção de vitórias, incluindo índice do trio

    • detecção de empate e reação visual/sonora apropriada

    • validação de jogadas ocupadas

    • reinício automático com alternância do jogador inicial

Essa lógica é a base de praticamente qualquer jogo 2D em grid.

Sistema de Inteligência Artificial — 3 níveis de dificuldade

O projeto apresenta três abordagens distintas de IA:

IA1 — Fraca

    • Escolhas aleatórias controladas

    • Jogadas rápidas, imprevisíveis e simples

IA2 — Média

    • Heurística:

      • tenta ganhar

      • tenta bloquear

      • ocupa o centro

      • explora cantos favoráveis

    • Excelente nível para treinar jogadores iniciantes

IA3 — Forte (Minimax otimizado)

    • Implementação técnica de algoritmo Minimax (algoritmo de tomada de decisão usado na teoria dos jogos e na inteligência artificial)

    • Previsão de jogadas completas

    • Ajuste de desempenho para microcontroladores

    • IA impossível de derrotar

A experiência demonstra ao aluno três estilos completamente diferentes de IA, do mais simples ao mais avançado.

Efeitos sonoros 8-bit (buzzer passivo)

O leitor aprende a:

    • Criar melodias estilo retrô

    • Tocar arpejos diferentes para X e O

    • Criar sons de erro

    • Criar melodias de vitória e empate

    • Usar pequenas pausas para simular "expressão sonora" (chiptune)

Isso torna o jogo muito mais imersivo.

Animações (POP + linha de vitória piscando)

O projeto demonstra técnicas de animação:

    • Desenho incremental para criar o efeito POP

    • Desenho de linha grossa sobre o trio vencedor

    • Piscagem rápida com limpeza e redesenho

    • Sincronização entre animação e som

Essas técnicas são úteis para qualquer aplicativo ou jogo baseado em OLED.

Organização modular do código

O código é dividido em funções claras:

    • Entrada do usuário

    • Regras de jogo

    • IA

    • Sons

    • Renderização

    • Animações

    • Reinicialização e alternância automática

Demonstrando:

    • Modularização

    • Controle de estado

    • Fluxo entre hardware e lógica

    • Manutenção e expansão de projetos complexos

Funções avançadas implementadas no projeto final

    • Alternância automática entre quem inicia cada nova partida

    • Reinício automático após vitória ou empate

    • Ajuste inteligente do tempo de resposta da IA

    • Cabeçalho informativo: placar + modo + vez

    • Zero de flicker / tela fluida

    • IA inicia imediatamente sem esperar input humano

    • Pisca e risco apenas nos três campos vencedores

    • Teclado sempre pronto, sem travamentos

    • Sons totalmente estilizados em 8-bit

Este projeto é um pacote completo de aprendizado real, cobrindo hardware, lógica, IA, UX, som, interface e programação estruturada.

 

 

O anúncio abaixo ajuda a manter o Squids Arduino funcionando

Comentários

×

Infomações do site / SEO








×

Adicionar Marcadores