Início
/
Projetos Arduino
/
Jogos
/
Jogo da Velha com Arduino, OLED e Teclado 4×4 — Animações, Sons e Modo IA - Arduino jogo #07
Jogo da Velha com Arduino, OLED e Teclado 4×4 — Animações, Sons e Modo IA - Arduino jogo #07
Angelo Luis Ferreira | 20/11/2025
Acessos: 131
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 |
 |
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 |
 |
– 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:

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:
| 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:
✔ 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:
✔ 3. Como jogar
Jogador Humano
Sempre joga com X.
Para jogar:
Se a posição estiver vazia:
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:
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
✔ 7. Alternância do primeiro jogador
Após qualquer partida, vencida ou empatada:
Quando O deve começar:
✔ 8. Reinício manual
Tecla *
Reinicia a partida:
Tecla #
Zera o placar:
✔ 9. Sistema de Vitória (animação e som)
Quando um trio é formado:
-
Uma melodia retrô de vitória toca
-
O trio vencedor pisca com uma linha grossa desenhada sobre as 3 peças
-
A tela volta automaticamente ao tabuleiro limpo
-
O próximo jogador inicial é alternado
-
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:
-
Toca-se uma melodia de empate
-
O tabuleiro pisca algumas vezes
-
Inicia-se nova partida automaticamente
-
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
IA2 — Média
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:
Demonstrando:
✔ 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