Construa o BRINK GAMER: um console de jogos portátil ESP32
Publicado em 10 de Janeiro de 2026 às 17:18
Categoria:
BRINK GAMER é um console de jogos portátil "faça você mesmo" alimentado por um ESP32.
Este projeto combina eletrônica, programação e jogos em um dispositivo compacto e educativo.
O objetivo é demonstrar como o ESP32 pode ser usado para lidar com gráficos, botões e som, tornando-o ideal para projetos de jogos embarcados.
Materiais Necessários
- Placa de desenvolvimento ESP32
- Bateria de íon-lítio 18650 (3,7 V)
- Módulo carregador de bateria 03962A
- Conversor elevador de tensão (3,7 V para 5 V)
- Display (LCD ST7567)
- Push Button (para cima, para baixo, para a esquerda, para a direita, ação)
- Interruptor Liga/Desliga
- Fios de ligação
- Placa perfurada (placa protótipo)
- Placa de MDF (base tipo prancheta)
- Cola instantânea
- Tinta
Ferramentas Necessárias
- Ferramentas:
- Estilete (cortador)
- Lixa
Etapas
Etapa 1: Por onde começar?
-
Antes de partir para a versão final do BRINK GAMER, o primeiro passo é montar todo o circuito em uma protoboard.
Essa etapa é essencial para testar conexões, funcionamento do display, botões e alimentação, evitando erros quando o projeto for definitivo.
Comece posicionando a placa ESP32 na protoboard e, em seguida, conecte os principais componentes que fazem parte do console.
🔧 O que montar nesta etapa
Conecte o ESP32 na protoboard
Ligue o display gráfico ST7567 para testar imagem e comunicação
Conecte os botões de controle (direções e ação)
Monte o circuito de alimentação com bateria e carregador
Utilize fios de ligação apenas para testes
O objetivo aqui é validar o funcionamento eletrônico e lógico do projeto, antes de avançar para soldagem, caixa ou montagem final.
Etapa 2: Cortar e preparar a base de madeira
-
Com a parte eletrônica já testada na protoboard, é hora de pensar na estrutura física do BRINK GAMER.
Nesta etapa, você irá escolher, medir, cortar e lixar as peças de madeira que formarão o corpo do console.
Não existe um único padrão obrigatório: as dimensões podem ser adaptadas de acordo com suas preferências, levando em conta o tamanho do display, posição dos botões e conforto ao segurar o console.
🪵 O que fazer nesta etapa
Escolha o tipo de madeira que preferir (MDF, compensado, pinus, etc.)
Defina o formato e tamanho do console
Meça os espaços para:
Display
Botões
Interruptor liga/desliga
Acesso à carga da bateria
Faça os cortes das peças
Lixe bem todas as bordas para um melhor acabamento
O ideal é montar tudo a seco, apenas encaixando as peças, para verificar se as medidas estão corretas antes da montagem final.
Etapa 3: Soldando os pinos de conexão na placa perfurada
-
Nesta etapa, o objetivo é preparar uma placa perfurada para facilitar a conexão e o teste do ESP32 e do display .
Primeiro, solde pinos de conexão (fêmea ou macho, dependendo da sua configuração) na placa perfurada. Esses pinos permitirão que o ESP32 e o módulo de exibição sejam conectados e removidos facilmente durante os testes.
Certifique-se de que os pinos estejam devidamente alinhados antes de soldar para garantir um encaixe perfeito e conexões confiáveis.
Após a soldagem, coloque o ESP32 e o display nos conectores.
Em seguida, conecte os fios do ESP32 ao display conforme mostrado na figura , certificando-se de que todas as conexões de sinal e energia estejam corretas.
Após a fiação, realize testes básicos de conexão para confirmar se o visor está funcionando corretamente. Essa configuração ajuda a verificar a fiação e a funcionalidade antes de prosseguir para a montagem final.
Essa abordagem facilita a depuração e evita danos aos componentes.
Etapa 4: Conexões e mapeamento de pinos
-
Nesta etapa, todas as conexões eletrônicas são explicadas com base na configuração de firmware utilizada no projeto.
📟 Conexões de exibição (ST7567 – SPI)
O visor gráfico comunica-se com o ESP32 usando SPI .
Os seguintes pinos estão definidos no código:
CS (Chip Select) → ESP32 GPIO 5
DC (Dados/Comando) → GPIO 4 do ESP32
RST (Reset) → ESP32 GPIO 15
A comunicação SPI utiliza os pinos SPI de hardware do ESP32 internamente (MOSI e SCK), enquanto CS, DC e RST são definidos manualmente.
Certifique-se de que o visor esteja alimentado de acordo com suas especificações (3,3 V ou 5 V, dependendo do módulo).
🎮 Conexões de botões
Todos os botões são configurados usando INPUT_PULLUP , o que significa que cada botão está conectado entre o pino do ESP32 e o GND .
Botões direcionais:
PARA CIMA → GPIO 25
PARA BAIXO → GPIO 26
ESQUERDA → GPIO 27
DIREITA → GPIO 14
Botões do menu:
SELECIONAR → GPIO 32
OK / INICIAR → GPIO 33
Como os resistores de pull-up internos estão habilitados, não são necessários resistores externos.
🔊 Conexão do Buzzer
Sinal da campainha → GPIO 13
Buzzer GND → GND
A campainha é controlada usando a função tone() para gerar efeitos sonoros e música de inicialização.
🔋 Sistema de energia
A bateria 18650 (3,7 V) alimenta o sistema.
A bateria está conectada ao módulo de carregamento 03962A.
Um conversor elevador (3,7 V para 5 V) é usado para fornecer tensão estável.
Um interruptor liga/desliga é colocado entre a bateria e o circuito.
Essa configuração permite carregamento seguro e operação portátil.
🧪 Verificações finais
Antes de prosseguir para a etapa final:
Verifique cuidadosamente todas as conexões.
Certifique-se de que não haja curto-circuito.
Teste de entrada de botões e inicialização da tela
Assim que tudo estiver funcionando corretamente, você poderá prosseguir para a etapa final.
Etapa 5: Firmware e Código
-
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
// Arquivos.h
#include "cobra.h"
#include "pong.h"
#include "shooter.h"
#include "racer.h"
#include "tetris.h"
// ================== PINOS ==================
#define PIN_CS 5
#define PIN_DC 4
#define PIN_RST 15
#define PIN_BTN_UP 25
#define PIN_BTN_DOWN 26
#define PIN_BTN_LEFT 27
#define PIN_BTN_RIGHT 14
#define PIN_BTN_SEL 32
#define PIN_BTN_OK 33
#define PIN_BUZZER 13
// ================== DISPLAY ==================
U8G2_ST7567_JLX12864_F_4W_HW_SPI u8g2(
U8G2_R0, PIN_CS, PIN_DC, PIN_RST
);
// ================== TELAS ==================
enum Tela {
TELA_START,
TELA_MENU,
TELA_COBRA,
TELA_PONG,
TELA_SHOOTER,
TELA_RACER,
TELA_TETRIS
};
Tela telaAtual = TELA_START;
// ================== MENU ==================
int jogoSelecionado = 0;
const int TOTAL_JOGOS = 5;
// ================== START CONTROLE ==================
unsigned long startBlinkTimer = 0;
bool startBlink = true;
// 🎵 controle da música start
unsigned long startMusicTimer = 0;
int startMusicCount = 0;
bool startMusicFinished = false;
// ================== BUZZER ==================
void beep(int f = 2000, int d = 60) {
tone(PIN_BUZZER, f, d);
}
// ================== BOTÕES ==================
void setupButtons() {
pinMode(PIN_BTN_UP, INPUT_PULLUP);
pinMode(PIN_BTN_DOWN, INPUT_PULLUP);
pinMode(PIN_BTN_LEFT, INPUT_PULLUP);
pinMode(PIN_BTN_RIGHT, INPUT_PULLUP);
pinMode(PIN_BTN_SEL, INPUT_PULLUP);
pinMode(PIN_BTN_OK, INPUT_PULLUP);
}
// ================== START MUSIC ==================
void playStartSound() {
tone(PIN_BUZZER, 1200, 120); delay(150);
tone(PIN_BUZZER, 1600, 120); delay(150);
tone(PIN_BUZZER, 2000, 200);
}
// ================== START SCREEN ==================
void drawStartScreen() {
if (millis() - startBlinkTimer > 400) {
startBlinkTimer = millis();
startBlink = !startBlink;
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_8x13B_tf);
u8g2.drawStr(18, 16, "GAMER MODE");
// ÍCONE CONTROLE
u8g2.drawRFrame(28, 24, 72, 28, 6);
u8g2.drawDisc(42, 38, 3);
u8g2.drawDisc(86, 38, 3);
u8g2.drawBox(60, 34, 6, 2);
u8g2.setFont(u8g2_font_5x8_tf);
if (startBlink)
u8g2.drawStr(30, 62, "PRESS START");
u8g2.sendBuffer();
}
// ================== ÍCONES MENU ==================
void drawCobraIcon() {
for (int i = 0; i < 5; i++)
u8g2.drawBox(20 + i * 12, 32, 10, 10);
u8g2.drawFrame(80, 32, 10, 10);
}
void drawPongIcon() {
u8g2.drawBox(10, 26, 4, 24);
u8g2.drawBox(114, 26, 4, 24);
u8g2.drawDisc(62, 38, 3);
}
void drawShooterIcon() {
u8g2.drawBox(56, 50, 16, 4);
u8g2.drawBox(60, 46, 8, 4);
u8g2.drawDisc(64, 40, 2);
for (int i = 0; i < 4; i++)
u8g2.drawFrame(20 + i * 20, 26, 12, 6);
}
void drawRacerIcon() {
for (int i = 0; i < 64; i += 8) {
u8g2.drawVLine(20, i, 4);
u8g2.drawVLine(108, i, 4);
}
int px = 56, py = 50;
u8g2.drawBox(px, py, 3, 3);
u8g2.drawBox(px + 8, py, 3, 3);
u8g2.drawBox(px + 4, py + 4, 3, 3);
u8g2.drawBox(px, py + 8, 3, 3);
u8g2.drawBox(px + 8, py + 8, 3, 3);
}
void drawTetrisIcon() {
int x = 52, y = 26, s = 6;
u8g2.drawBox(x, y, s, s);
u8g2.drawBox(x + s + 2, y, s, s);
u8g2.drawBox(x + s / 2 + 1, y + s + 2, s, s);
u8g2.drawBox(x, y + 2 * s + 4, s, s);
u8g2.drawBox(x + s + 2, y + 2 * s + 4, s, s);
}
// ================== MENU ==================
void drawMenu() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x12_tf);
if (jogoSelecionado == 0) { u8g2.drawStr(30, 12, "COBRINHA"); drawCobraIcon(); }
else if (jogoSelecionado == 1) { u8g2.drawStr(46, 12, "PONG"); drawPongIcon(); }
else if (jogoSelecionado == 2) { u8g2.drawStr(26, 12, "SHOOTER"); drawShooterIcon(); }
else if (jogoSelecionado == 3) { u8g2.drawStr(34, 12, "RACER"); drawRacerIcon(); }
else if (jogoSelecionado == 4) { u8g2.drawStr(40, 12, "TETRIS"); drawTetrisIcon(); }
u8g2.setFont(u8g2_font_5x8_tf);
u8g2.drawStr(18, 62, "< SEL OK >");
u8g2.sendBuffer();
}
void handleMenuButtons() {
static unsigned long debounce = 0;
if (millis() - debounce < 200) return;
if (!digitalRead(PIN_BTN_SEL)) {
jogoSelecionado = (jogoSelecionado + 1) % TOTAL_JOGOS;
beep(1400);
debounce = millis();
}
if (!digitalRead(PIN_BTN_OK)) {
beep(2200, 100);
if (jogoSelecionado == 0) { cobraReset(); telaAtual = TELA_COBRA; }
else if (jogoSelecionado == 1) { pongReset(); telaAtual = TELA_PONG; }
else if (jogoSelecionado == 2) { shooterReset(); telaAtual = TELA_SHOOTER; }
else if (jogoSelecionado == 3) { racerReset(); telaAtual = TELA_RACER; }
else if (jogoSelecionado == 4) { tetrisReset(); telaAtual = TELA_TETRIS; }
debounce = millis();
}
}
// ================== SETUP ==================
void setup() {
u8g2.begin();
u8g2.setContrast(120);
setupButtons();
cobraInit(&u8g2, PIN_BUZZER);
pongInit(&u8g2, PIN_BUZZER);
shooterInit(&u8g2, PIN_BUZZER);
racerInit(&u8g2, PIN_BUZZER);
tetrisInit(&u8g2, PIN_BUZZER);
}
// ================== LOOP ==================
void loop() {
// ===== START =====
if (telaAtual == TELA_START) {
// 🎵 Música START 3x
if (!startMusicFinished) {
if (millis() - startMusicTimer > 700) {
startMusicTimer = millis();
playStartSound();
startMusicCount++;
if (startMusicCount >= 3) {
startMusicFinished = true;
}
}
}
drawStartScreen();
if (!digitalRead(PIN_BTN_OK)) {
beep(2000, 120);
delay(300);
// reset música para próxima vez
startMusicCount = 0;
startMusicFinished = false;
telaAtual = TELA_MENU;
}
return;
}
// ===== MENU =====
if (telaAtual == TELA_MENU) {
handleMenuButtons();
drawMenu();
}
else if (telaAtual == TELA_COBRA) {
cobraLoop();
if (!digitalRead(PIN_BTN_SEL)) { beep(900); delay(300); telaAtual = TELA_MENU; }
}
else if (telaAtual == TELA_PONG) {
pongLoop();
if (!digitalRead(PIN_BTN_SEL)) { beep(900); delay(300); telaAtual = TELA_MENU; }
}994955555555555
else if (telaAtual == TELA_SHOOTER) {
shooterLoop();
if (!digitalRead(PIN_BTN_SEL)) { beep(900); delay(300); telaAtual = TELA_MENU; }
}
else if (telaAtual == TELA_RACER) {
racerLoop();
if (!digitalRead(PIN_BTN_SEL)) { beep(900); delay(300); telaAtual = TELA_MENU; }
}
else if (telaAtual == TELA_TETRIS) {
tetrisLoop();
if (!digitalRead(PIN_BTN_SEL)) { beep(900); delay(300); telaAtual = TELA_MENU; }
}
}
Etapa 6: Cobra.h
-
#ifndef COBRA_H
#define COBRA_H
// ================= CONFIG =================
#define WIDTH 16
#define HEIGHT 8
#define BLOCK_SIZE 8
enum Dir {UP, DOWN, LEFT, RIGHT, NONE};
struct Point {
int x;
int y;
};
// ================= VARIÁVEIS =================
U8G2 *display;
int buzzerPin;
Point snake[128];
int snakeLength = 2;
Point fruit;
bool fruitExists = false;
Dir direction = NONE;
unsigned long lastMove = 0;
int moveInterval = 300;
int score = 0;
bool gameStarted = false;
// ================= BUZZER =================
void cobraBeep(int freq=2000, int dur=80) {
tone(buzzerPin, freq, dur);
}
// ================= INIT =================
void cobraInit(U8G2 *u8, int buzzer) {
display = u8;
buzzerPin = buzzer;
randomSeed(analogRead(0) + micros());
}
// ================= RESET =================
void cobraReset() {
snakeLength = 2;
score = 0;
direction = NONE;
gameStarted = false;
moveInterval = 300;
snake[0] = {1, 0};
snake[1] = {0, 0};
fruitExists = false;
}
// ================= FRUTA =================
void spawnFruit() {
while (true) {
fruit.x = random(0, WIDTH);
fruit.y = random(0, HEIGHT);
bool ok = true;
for (int i = 0; i < snakeLength; i++) {
if (snake[i].x == fruit.x && snake[i].y == fruit.y) {
ok = false;
break;
}
}
if (ok) {
fruitExists = true;
return;
}
}
}
// ================= COLISÃO =================
bool checkCollision(int x, int y) {
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return true;
for (int i = 1; i < snakeLength; i++)
if (snake[i].x == x && snake[i].y == y) return true;
return false;
}
// ================= BOTÕES =================
void cobraButtons() {
static unsigned long debounce = 0;
if (millis() - debounce < 150) return;
if (digitalRead(25) == LOW && direction != DOWN) { direction = UP; gameStarted = true; debounce = millis(); }
if (digitalRead(26) == LOW && direction != UP) { direction = DOWN; gameStarted = true; debounce = millis(); }
if (digitalRead(27) == LOW && direction != RIGHT){ direction = LEFT; gameStarted = true; debounce = millis(); }
if (digitalRead(14) == LOW && direction != LEFT) { direction = RIGHT; gameStarted = true; debounce = millis(); }
}
// ================= MOVIMENTO =================
void moveSnake() {
if (!gameStarted || direction == NONE) return;
Point head = snake[0];
if (direction == UP) head.y--;
if (direction == DOWN) head.y++;
if (direction == LEFT) head.x--;
if (direction == RIGHT) head.x++;
if (checkCollision(head.x, head.y)) {
cobraBeep(400, 300);
cobraReset();
return;
}
for (int i = snakeLength; i > 0; i--)
snake[i] = snake[i - 1];
snake[0] = head;
if (!fruitExists) spawnFruit();
if (head.x == fruit.x && head.y == fruit.y) {
snakeLength++;
score++;
fruitExists = false;
cobraBeep(2500, 100);
}
}
// ================= DRAW =================
void drawSnake() {
display->clearBuffer();
display->setFont(u8g2_font_5x8_tf);
char s[16];
sprintf(s, "Score:%d", score);
display->drawStr(0, 8, s);
if (!gameStarted)
display->drawStr(40, 8, "START");
if (fruitExists)
display->drawBox(fruit.x * BLOCK_SIZE, fruit.y * BLOCK_SIZE + 10, BLOCK_SIZE, BLOCK_SIZE);
for (int i = 0; i < snakeLength; i++) {
display->drawBox(
snake[i].x * BLOCK_SIZE,
snake[i].y * BLOCK_SIZE + 10,
BLOCK_SIZE,
BLOCK_SIZE
);
}
display->sendBuffer();
}
// ================= LOOP =================
void cobraLoop() {
cobraButtons();
if (millis() - lastMove > moveInterval) {
lastMove = millis();
moveSnake();
drawSnake();
}
}
#endif
Etapa 7: Tetris.h
-
#ifndef TETRIS_H
#define TETRIS_H
#include <U8g2lib.h>
// ================= PROTÓTIPOS =================
void tetrisInit(U8G2 *u8g2, int buzzerPin);
void tetrisReset();
void tetrisLoop();
// ================= DISPLAY / BUZZER =================
static U8G2 *tetrisDisplay;
static int tetrisBuzzer;
// ================= PINOS =================
#define TETRIS_BTN_LEFT 27
#define TETRIS_BTN_RIGHT 14
#define TETRIS_BTN_ROTATE 25
#define TETRIS_BTN_START 33
// ================= DIMENSÕES =================
#define TETRIS_CELL 4
#define TETRIS_COLS 22
#define TETRIS_ROWS 16
#define TETRIS_OFFSET_X 20
static byte tetrisGrid[TETRIS_ROWS][TETRIS_COLS];
// ================= ESTADO =================
enum TetrisState { TETRIS_START, TETRIS_PLAY, TETRIS_GAMEOVER };
static TetrisState tetrisState = TETRIS_START;
// ================= PEÇA =================
struct TetrisPiece {
int x, y;
byte shape[4][4];
};
static TetrisPiece tetrisPiece;
// ================= CONTROLE =================
static unsigned long tetrisLastFall = 0;
static unsigned long tetrisBlinkTimer = 0;
static unsigned long tetrisMusicTimer = 0;
static bool tetrisBlinkOn = true;
static bool tetrisEsperandoSoltar = true;
static bool tetrisRotSolto = true;
static int tetrisPontos = 0;
static int tetrisMusicIndex = 0;
// ================= PEÇAS =================
static const byte tetrisPieces[7][4][4] = {
{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
{{1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}}
};
// ================= MÚSICA =================
static const int tetrisStartMusic[][2] = {
{900,80},{1200,80},{1500,120}
};
static const int tetrisStartMusicLen = 3;
static const int tetrisGameOverMusic[][2] = {
{800,120},{500,200}
};
static const int tetrisGameOverMusicLen = 2;
// ================= SOM =================
static void tetrisBeep(int f,int d){ tone(tetrisBuzzer,f,d); }
// ================= ÍCONE =================
void tetrisDrawIcon(int x,int y){
int s=6;
tetrisDisplay->drawBox(x,y,s,s);
tetrisDisplay->drawBox(x+s+2,y,s,s);
tetrisDisplay->drawBox(x+s/2+1,y+s+2,s,s);
tetrisDisplay->drawBox(x,y+2*s+4,s,s);
tetrisDisplay->drawBox(x+s+2,y+2*s+4,s,s);
}
// ================= DESENHO =================
void tetrisDrawCell(int gx,int gy){
tetrisDisplay->drawBox(
TETRIS_OFFSET_X + gx*TETRIS_CELL,
gy*TETRIS_CELL,
TETRIS_CELL-1,
TETRIS_CELL-1
);
}
void tetrisDrawGrid(){
for(int y=0;y<TETRIS_ROWS;y++)
for(int x=0;x<TETRIS_COLS;x++)
if(tetrisGrid[y][x]) tetrisDrawCell(x,y);
}
void tetrisDrawPiece(){
for(int r=0;r<4;r++)
for(int c=0;c<4;c++)
if(tetrisPiece.shape[r][c]){
int gx=tetrisPiece.x+c;
int gy=tetrisPiece.y+r;
if(gy>=0) tetrisDrawCell(gx,gy);
}
}
void tetrisDrawBorders(){
int l=TETRIS_OFFSET_X-1;
int r=TETRIS_OFFSET_X+TETRIS_COLS*TETRIS_CELL;
tetrisDisplay->drawVLine(l,0,64);
tetrisDisplay->drawVLine(r,0,64);
}
// ================= COLISÃO =================
bool tetrisCollision(int nx,int ny){
for(int r=0;r<4;r++)
for(int c=0;c<4;c++)
if(tetrisPiece.shape[r][c]){
int gx=nx+c, gy=ny+r;
if(gx<0||gx>=TETRIS_COLS||gy>=TETRIS_ROWS) return true;
if(gy>=0 && tetrisGrid[gy][gx]) return true;
}
return false;
}
// ================= ROTAÇÃO =================
void tetrisRotatePiece(){
byte tmp[4][4];
for(int r=0;r<4;r++)
for(int c=0;c<4;c++)
tmp[c][3-r]=tetrisPiece.shape[r][c];
for(int r=0;r<4;r++)
for(int c=0;c<4;c++)
if(tmp[r][c]){
int gx=tetrisPiece.x+c, gy=tetrisPiece.y+r;
if(gx<0||gx>=TETRIS_COLS||gy>=TETRIS_ROWS) return;
if(gy>=0 && tetrisGrid[gy][gx]) return;
}
memcpy(tetrisPiece.shape,tmp,16);
tetrisBeep(900,40);
}
// ================= FIXAR =================
void tetrisFixPiece(){
for(int r=0;r<4;r++)
for(int c=0;c<4;c++)
if(tetrisPiece.shape[r][c]){
int gy=tetrisPiece.y+r;
if(gy>=0) tetrisGrid[gy][tetrisPiece.x+c]=1;
}
}
// ================= LIMPAR LINHAS =================
void tetrisClearLines(){
for(int y=TETRIS_ROWS-1;y>=0;y--){
bool full=true;
for(int x=0;x<TETRIS_COLS;x++) if(!tetrisGrid[y][x]) full=false;
if(full){
for(int yy=y;yy>0;yy--) memcpy(tetrisGrid[yy],tetrisGrid[yy-1],TETRIS_COLS);
memset(tetrisGrid[0],0,TETRIS_COLS);
tetrisPontos++;
tetrisBeep(1200,80);
y++;
}
}
}
// ================= NOVA PEÇA =================
void tetrisNewPiece(){
memcpy(tetrisPiece.shape,tetrisPieces[random(0,7)],16);
tetrisPiece.x=(TETRIS_COLS/2)-2;
tetrisPiece.y=-1;
if(tetrisCollision(tetrisPiece.x,tetrisPiece.y))
tetrisState=TETRIS_GAMEOVER;
}
// ================= INIT =================
void tetrisInit(U8G2 *u8g2,int buzzerPin){
tetrisDisplay=u8g2;
tetrisBuzzer=buzzerPin;
pinMode(TETRIS_BTN_LEFT,INPUT_PULLUP);
pinMode(TETRIS_BTN_RIGHT,INPUT_PULLUP);
pinMode(TETRIS_BTN_ROTATE,INPUT_PULLUP);
pinMode(TETRIS_BTN_START,INPUT_PULLUP);
randomSeed(millis());
tetrisReset();
}
// ================= RESET =================
void tetrisReset(){
memset(tetrisGrid,0,sizeof(tetrisGrid));
tetrisPontos=0;
tetrisBlinkTimer=millis();
tetrisMusicTimer=millis();
tetrisMusicIndex=0;
tetrisBlinkOn=true;
tetrisEsperandoSoltar=true;
tetrisState=TETRIS_START;
tetrisNewPiece();
}
// ================= LOOP =================
void tetrisLoop(){
// ===== START =====
if(tetrisState==TETRIS_START){
if(millis()-tetrisBlinkTimer>400){
tetrisBlinkTimer=millis();
tetrisBlinkOn=!tetrisBlinkOn;
}
if(millis()-tetrisMusicTimer>300){
tetrisMusicTimer=millis();
tetrisBeep(
tetrisStartMusic[tetrisMusicIndex][0],
tetrisStartMusic[tetrisMusicIndex][1]
);
tetrisMusicIndex=(tetrisMusicIndex+1)%tetrisStartMusicLen;
}
if(tetrisEsperandoSoltar){
if(digitalRead(TETRIS_BTN_START)==HIGH)
tetrisEsperandoSoltar=false;
} else if(digitalRead(TETRIS_BTN_START)==LOW){
noTone(tetrisBuzzer);
tetrisBeep(1000,150);
tetrisState=TETRIS_PLAY;
delay(200);
}
tetrisDisplay->clearBuffer();
tetrisDisplay->setFont(u8g2_font_6x12_tf);
tetrisDisplay->drawStr(42,12,"TETRIS");
tetrisDrawIcon(54,18);
if(tetrisBlinkOn)
tetrisDisplay->drawStr(18,56,"PRESSIONE START");
tetrisDisplay->sendBuffer();
return;
}
// ===== PLAY =====
if(tetrisState==TETRIS_PLAY){
if(!digitalRead(TETRIS_BTN_LEFT)&&!tetrisCollision(tetrisPiece.x-1,tetrisPiece.y)){
tetrisPiece.x--; delay(70);
}
if(!digitalRead(TETRIS_BTN_RIGHT)&&!tetrisCollision(tetrisPiece.x+1,tetrisPiece.y)){
tetrisPiece.x++; delay(70);
}
if(tetrisRotSolto && !digitalRead(TETRIS_BTN_ROTATE)){
tetrisRotatePiece(); tetrisRotSolto=false;
}
if(digitalRead(TETRIS_BTN_ROTATE)) tetrisRotSolto=true;
if(millis()-tetrisLastFall>500){
tetrisLastFall=millis();
if(!tetrisCollision(tetrisPiece.x,tetrisPiece.y+1))
tetrisPiece.y++;
else{
tetrisFixPiece();
tetrisClearLines();
tetrisNewPiece();
}
}
tetrisDisplay->clearBuffer();
tetrisDrawBorders();
tetrisDisplay->setFont(u8g2_font_5x8_tf);
tetrisDisplay->setCursor(0,8);
tetrisDisplay->print("PTS:");
tetrisDisplay->print(tetrisPontos);
tetrisDrawGrid();
tetrisDrawPiece();
tetrisDisplay->sendBuffer();
return;
}
// ===== GAME OVER =====
if(tetrisState==TETRIS_GAMEOVER){
static bool tocou=false;
if(!tocou){
for(int i=0;i<tetrisGameOverMusicLen;i++){
tetrisBeep(tetrisGameOverMusic[i][0],tetrisGameOverMusic[i][1]);
delay(220);
}
tocou=true;
}
if(millis()-tetrisBlinkTimer>400){
tetrisBlinkTimer=millis();
tetrisBlinkOn=!tetrisBlinkOn;
}
tetrisDisplay->clearBuffer();
tetrisDisplay->setFont(u8g2_font_6x12_tf);
tetrisDisplay->drawStr(32,22,"GAME OVER");
if(tetrisBlinkOn)
tetrisDisplay->drawStr(18,52,"PRESSIONE START");
tetrisDisplay->sendBuffer();
if(!digitalRead(TETRIS_BTN_START)){
tocou=false;
tetrisReset();
delay(300);
}
}
}
#endif
Etapa 8: Pong.h
-
#ifndef PONG_H
#define PONG_H
#include <U8g2lib.h>
// ================= PROTÓTIPOS =================
void pongInit(U8G2 *u8g2, int buzzerPin);
void pongReset();
void pongLoop();
// ================= DISPLAY / BUZZER =================
static U8G2 *pongDisplay;
static int pongBuzzer;
// ================= PINOS =================
#define PIN_BTN_LEFT 27
#define PIN_BTN_RIGHT 14
#define PIN_BTN_OK 33
// ================= ESTADOS =================
enum PongEstado {
PONG_TELA_INICIO,
PONG_JOGANDO,
PONG_GAME_OVER
};
static PongEstado pongEstado = PONG_TELA_INICIO;
// ================= VARIÁVEIS =================
static int paddleX;
static const int paddleW = 24;
static const int paddleY = 58;
static int ballX, ballY;
static int ballDX, ballDY;
static int pontos = 0;
// ================= TEMPO =================
static unsigned long lastBallMove = 0;
static unsigned long lastPaddleMove = 0;
static unsigned long blinkTimer = 0;
static unsigned long musicTimer = 0;
// ================= CONTROLE =================
static bool blinkOn = true;
static bool aguardandoSoltarBotao = true;
static int musicIndex = 0;
// ================= BEEP =================
static void pongBeep(int f, int d) {
tone(pongBuzzer, f, d);
}
static void pongStopSound() {
noTone(pongBuzzer);
}
// ================= MUSIQUINHA =================
static const int introMusic[][2] = {
{1200, 120},
{1500, 120},
{1800, 120},
{1500, 120}
};
static const int introMusicLen = 4;
// ================= DESENHO CONTROLE =================
static void drawGamerIcon(int x, int y) {
// Corpo
pongDisplay->drawRFrame(x, y, 40, 18, 4);
// Direcional
pongDisplay->drawBox(x + 6, y + 7, 6, 2);
pongDisplay->drawBox(x + 8, y + 5, 2, 6);
// Botões
pongDisplay->drawDisc(x + 30, y + 7, 2);
pongDisplay->drawDisc(x + 34, y + 10, 2);
}
// ================= INIT =================
void pongInit(U8G2 *u8g2, int buzzerPin) {
pongDisplay = u8g2;
pongBuzzer = buzzerPin;
pongReset();
}
// ================= RESET =================
void pongReset() {
paddleX = 52;
ballX = 64;
ballY = 40;
ballDX = 1;
ballDY = -1;
pontos = 0;
pongEstado = PONG_TELA_INICIO;
aguardandoSoltarBotao = true;
blinkTimer = millis();
musicTimer = millis();
musicIndex = 0;
blinkOn = true;
}
// ================= LOOP =================
void pongLoop() {
// ======= TELA START =======
if (pongEstado == PONG_TELA_INICIO) {
if (millis() - blinkTimer > 400) {
blinkTimer = millis();
blinkOn = !blinkOn;
}
if (millis() - musicTimer > 200) {
musicTimer = millis();
pongBeep(introMusic[musicIndex][0], introMusic[musicIndex][1]);
musicIndex++;
if (musicIndex >= introMusicLen) musicIndex = 0;
}
if (aguardandoSoltarBotao) {
if (digitalRead(PIN_BTN_OK) == HIGH)
aguardandoSoltarBotao = false;
} else {
if (digitalRead(PIN_BTN_OK) == LOW) {
pongStopSound();
pongBeep(2500, 120);
pongEstado = PONG_JOGANDO;
delay(200);
}
}
pongDisplay->clearBuffer();
pongDisplay->setFont(u8g2_font_6x12_tf);
pongDisplay->drawStr(42, 12, "PONG");
drawGamerIcon(44, 18);
if (blinkOn)
pongDisplay->drawStr(18, 55, "PRESSIONE START");
pongDisplay->sendBuffer();
return;
}
// ======= GAME OVER =======
if (pongEstado == PONG_GAME_OVER) {
if (digitalRead(PIN_BTN_OK) == LOW) {
pongReset();
delay(300);
}
pongDisplay->clearBuffer();
pongDisplay->setFont(u8g2_font_6x12_tf);
pongDisplay->drawStr(28, 20, "GAME OVER");
char txt[20];
sprintf(txt, "PONTOS: %d", pontos);
pongDisplay->drawStr(24, 38, txt);
pongDisplay->drawStr(10, 58, "OK = REINICIAR");
pongDisplay->sendBuffer();
return;
}
// ======= JOGANDO =======
if (millis() - lastPaddleMove > 20) {
lastPaddleMove = millis();
if (digitalRead(PIN_BTN_LEFT) == LOW && paddleX > 0)
paddleX -= 1;
if (digitalRead(PIN_BTN_RIGHT) == LOW && paddleX < 128 - paddleW)
paddleX += 1;
}
if (millis() - lastBallMove > 35) {
lastBallMove = millis();
ballX += ballDX;
ballY += ballDY;
if (ballX <= 0 || ballX >= 125) {
ballDX = -ballDX;
pongBeep(1200, 20);
}
if (ballY <= 0) {
ballDY = -ballDY;
pongBeep(1200, 20);
}
if (ballY >= paddleY - 2 &&
ballX >= paddleX &&
ballX <= paddleX + paddleW) {
ballDY = -ballDY;
pontos++;
pongBeep(2000, 30);
}
if (ballY > 63) {
pongBeep(400, 300);
pongEstado = PONG_GAME_OVER;
delay(200);
}
}
pongDisplay->clearBuffer();
pongDisplay->drawBox(paddleX, paddleY, paddleW, 4);
pongDisplay->drawBox(ballX, ballY, 3, 3);
pongDisplay->setFont(u8g2_font_5x8_tf);
char s[15];
sprintf(s, "PONTOS:%d", pontos);
pongDisplay->drawStr(2, 8, s);
pongDisplay->sendBuffer();
}
#endif
Etapa 9: Shooter.h
-
#ifndef SHOOTER_H
#define SHOOTER_H
#include <U8g2lib.h>
// ================= PROTÓTIPOS =================
void shooterInit(U8G2 *u8g2, int buzzerPin);
void shooterReset();
void shooterLoop();
// ================= DISPLAY / BUZZER =================
static U8G2 *shooterDisplay;
static int shooterBuzzer;
// ================= PINOS =================
#define PIN_BTN_LEFT 27
#define PIN_BTN_RIGHT 14
#define PIN_BTN_UP 25 // ATIRAR / START
#define PIN_BTN_OK 33
// ================= ESTADO DO JOGO =================
enum ShooterState { SHOOTER_START, SHOOTER_PLAY, SHOOTER_GAMEOVER };
static ShooterState shooterState = SHOOTER_START;
// ================= NAVE =================
static int naveX;
static const int naveY = 54;
static const int naveW = 20;
static const int naveH = 6;
// ================= TIROS =================
#define MAX_TIROS 12
struct Tiro { bool ativo; int x, y; };
static Tiro tiros[MAX_TIROS];
// ================= INIMIGOS =================
#define MAX_INIMIGOS 5
static int inimigoX[MAX_INIMIGOS];
static int inimigoY[MAX_INIMIGOS];
// ================= CONTROLE =================
static int shooterPontos = 0;
static unsigned long shooterLastMove = 0;
static unsigned long shooterLastEnemy = 0;
static unsigned long shooterLastShot = 0;
static unsigned long shooterBlinkTimer = 0;
static bool shooterBlinkOn = true;
static bool shooterAguardandoSoltarBotao = true;
static unsigned long shooterMusicTimer = 0;
static int shooterMusicIndex = 0;
// ================= MUSIQUINHA =================
// Start: notas suaves e ascendentes
static const int shooterIntroMusic[][2] = {
{600,120}, {800,120}, {1000,120}, {1200,120}, {1400,150}
};
// Game Over: notas descendentes suaves
static const int shooterGameOverMusic[][2] = {
{800,150}, {700,150}, {600,150}, {500,200}, {400,250}
};
static const int shooterIntroMusicLen = 5;
static const int shooterGameOverMusicLen = 5;
// ================= SOM =================
static void sBeep(int f,int d){tone(shooterBuzzer,f,d);}
static void shooterStopSound(){noTone(shooterBuzzer);}
// ================= DESENHOS =================
static void drawShooterGamerIcon(int x,int y){
shooterDisplay->drawRFrame(x,y,40,18,4);
shooterDisplay->drawBox(x+6,y+7,6,2);
shooterDisplay->drawBox(x+8,y+5,2,6);
shooterDisplay->drawDisc(x+30,y+7,2);
shooterDisplay->drawDisc(x+34,y+10,2);
}
static void drawNave(int x,int y){
shooterDisplay->drawBox(x,y,naveW,naveH);
shooterDisplay->drawBox(x+naveW/2-2,y-6,4,6);
}
// ===== Tiros grandes e lentos =====
static void drawTiro(int x,int y){
shooterDisplay->drawDisc(x,y,6);
}
static void drawInimigo(int x,int y){
shooterDisplay->drawFrame(x,y,12,8);
shooterDisplay->drawBox(x+2,y+2,8,3);
}
// ================= INIT =================
void shooterInit(U8G2 *u8g2,int buzzerPin){
shooterDisplay = u8g2;
shooterBuzzer = buzzerPin;
pinMode(PIN_BTN_LEFT,INPUT_PULLUP);
pinMode(PIN_BTN_RIGHT,INPUT_PULLUP);
pinMode(PIN_BTN_UP,INPUT_PULLUP);
pinMode(PIN_BTN_OK,INPUT_PULLUP);
shooterReset();
}
// ================= RESET =================
void shooterReset(){
shooterState = SHOOTER_START;
naveX = (128-naveW)/2;
shooterPontos = 0;
for(int i=0;i<MAX_INIMIGOS;i++){
inimigoX[i]=random(4,118);
inimigoY[i]=random(-80,-10);
}
for(int i=0;i<MAX_TIROS;i++) tiros[i].ativo=false;
shooterBlinkTimer=millis();
shooterBlinkOn=true;
shooterAguardandoSoltarBotao=true;
shooterMusicTimer=millis();
shooterMusicIndex=0;
shooterLastShot=0;
}
// ================= LOOP =================
void shooterLoop(){
// ===== START =====
if(shooterState==SHOOTER_START){
if(millis()-shooterBlinkTimer>400){shooterBlinkTimer=millis();shooterBlinkOn=!shooterBlinkOn;}
if(millis()-shooterMusicTimer>300){
shooterMusicTimer=millis();
sBeep(shooterIntroMusic[shooterMusicIndex][0],shooterIntroMusic[shooterMusicIndex][1]);
shooterMusicIndex=(shooterMusicIndex+1)%shooterIntroMusicLen;
}
if(shooterAguardandoSoltarBotao){
if(digitalRead(PIN_BTN_OK)==HIGH) shooterAguardandoSoltarBotao=false;
} else if(digitalRead(PIN_BTN_OK)==LOW){
shooterStopSound();
sBeep(1000,150);
shooterState=SHOOTER_PLAY;
delay(200);
}
shooterDisplay->clearBuffer();
shooterDisplay->setFont(u8g2_font_6x12_tf);
shooterDisplay->drawStr(28,12,"SHOOTER");
drawShooterGamerIcon(44,18);
if(shooterBlinkOn) shooterDisplay->drawStr(18,55,"PRESSIONE START");
shooterDisplay->sendBuffer();
return;
}
// ===== PLAY =====
if(shooterState==SHOOTER_PLAY){
// MOVIMENTO NAVE
if(millis()-shooterLastMove>15){shooterLastMove=millis();
if(!digitalRead(PIN_BTN_LEFT) && naveX>0) naveX-=2;
if(!digitalRead(PIN_BTN_RIGHT) && naveX<128-naveW) naveX+=2;
}
// DISPARO: ainda mais lento
if(!digitalRead(PIN_BTN_UP) && millis()-shooterLastShot>400){
for(int i=0;i<MAX_TIROS;i++){
if(!tiros[i].ativo){
tiros[i].ativo = true;
tiros[i].x = naveX + naveW/2;
tiros[i].y = naveY - 4;
sBeep(2000,40);
shooterLastShot = millis();
break;
}
}
}
// MOVIMENTO TIROS: mais lento
for(int i=0;i<MAX_TIROS;i++){
if(tiros[i].ativo){
tiros[i].y -= 1;
if(tiros[i].y < 0) tiros[i].ativo = false;
}
}
// INIMIGOS
if(millis()-shooterLastEnemy>350){shooterLastEnemy=millis();
for(int i=0;i<MAX_INIMIGOS;i++){
inimigoY[i]+=2;
if(inimigoY[i]>=naveY){sBeep(300,500); shooterState=SHOOTER_GAMEOVER;}
}
}
// COLISÃO
for(int i=0;i<MAX_INIMIGOS;i++){
for(int j=0;j<MAX_TIROS;j++){
if(tiros[j].ativo &&
tiros[j].x>=inimigoX[i] &&
tiros[j].x<=inimigoX[i]+12 &&
tiros[j].y>=inimigoY[i] &&
tiros[j].y<=inimigoY[i]+8){
tiros[j].ativo=false;
shooterPontos++;
sBeep(1500,80);
inimigoX[i]=random(4,118);
inimigoY[i]=random(-80,-10);
}
}
}
// DESENHO
shooterDisplay->clearBuffer();
drawNave(naveX,naveY);
for(int i=0;i<MAX_TIROS;i++) if(tiros[i].ativo) drawTiro(tiros[i].x,tiros[i].y);
for(int i=0;i<MAX_INIMIGOS;i++) drawInimigo(inimigoX[i],inimigoY[i]);
shooterDisplay->setFont(u8g2_font_5x8_tf);
char txt[16]; sprintf(txt,"PONTOS:%d",shooterPontos);
shooterDisplay->drawStr(2,8,txt);
shooterDisplay->sendBuffer();
return;
}
// ===== GAME OVER =====
if(shooterState==SHOOTER_GAMEOVER){
if(millis()-shooterBlinkTimer>400){shooterBlinkTimer=millis(); shooterBlinkOn=!shooterBlinkOn;}
if(millis()-shooterMusicTimer>400){ // música Game Over
shooterMusicTimer=millis();
sBeep(shooterGameOverMusic[shooterMusicIndex][0], shooterGameOverMusic[shooterMusicIndex][1]);
shooterMusicIndex=(shooterMusicIndex+1)%shooterGameOverMusicLen;
}
shooterDisplay->clearBuffer();
shooterDisplay->setFont(u8g2_font_6x12_tf);
shooterDisplay->drawStr(26,20,"GAME OVER");
char txt[20]; sprintf(txt,"PONTOS:%d",shooterPontos);
shooterDisplay->drawStr(28,38,txt);
if(shooterBlinkOn) shooterDisplay->drawStr(12,58,"OK = REINICIAR");
shooterDisplay->sendBuffer();
if(!digitalRead(PIN_BTN_UP)){ shooterReset(); delay(300);}
}
}
#endif
Etapa 10: Racer.h
-
#ifndef RACER_H
#define RACER_H
#include <U8g2lib.h>
// ================= PROTÓTIPOS =================
void racerInit(U8G2 *u8g2, int buzzerPin);
void racerReset();
void racerLoop();
// ================= DISPLAY / BUZZER =================
static U8G2 *racerDisplay;
static int racerBuzzer;
// ================= PINOS =================
#define RACER_PIN_LEFT 27
#define RACER_PIN_RIGHT 14
#define RACER_PIN_OK 33 // START / REINICIAR
// ================= ESTADO DO JOGO =================
enum RacerState { RACER_START, RACER_PLAY, RACER_GAMEOVER };
static RacerState racerState = RACER_START;
// ================= JOGADOR =================
static int racerPlayerX;
static const int racerPlayerY = 50;
static const int racerPlayerW = 11;
static const int racerPlayerH = 11;
// ================= INIMIGOS =================
#define RACER_MAX_ENEMIES 4
struct RacerEnemy { bool ativo; int x, y; int speed; };
static RacerEnemy racerEnemies[RACER_MAX_ENEMIES];
// ================= CONTROLE =================
static int racerPontos = 0;
static unsigned long racerLastMove = 0;
static unsigned long racerLastEnemyFall = 0;
static unsigned long racerBlinkTimer = 0;
static bool racerBlinkOn = true;
static bool racerEsperandoBotao = true;
static unsigned long racerMusicTimer = 0;
static int racerMusicIndex = 0;
static unsigned long racerEngineTimer = 0;
// ================= MUSIQUINHA =================
static const int racerStartMusic[][2] = {{600,120},{800,120},{1000,120},{1200,150}};
static const int racerGameOverMusic[][2] = {{800,150},{700,150},{600,150},{500,200}};
static const int racerStartMusicLen = 4;
static const int racerGameOverMusicLen = 4;
// ================= SOM =================
static void racerBeep(int f,int d){tone(racerBuzzer,f,d);}
static void racerStopSound(){noTone(racerBuzzer);}
static void racerEngineSound() {
static bool toggle = false;
int freq = toggle ? 700 : 900;
toggle = !toggle;
tone(racerBuzzer, freq, 50);
}
// ================= DESENHOS =================
static void racerDrawPlayer(int x,int y){
racerDisplay->drawBox(x, y, 3, 3);
racerDisplay->drawBox(x + 8, y, 3, 3);
racerDisplay->drawBox(x + 4, y + 4, 3, 3);
racerDisplay->drawBox(x, y + 8, 3, 3);
racerDisplay->drawBox(x + 8, y + 8, 3, 3);
}
static void racerDrawEnemy(int x,int y){
racerDrawPlayer(x, y);
}
static void racerDrawTrack(){
for(int i=0;i<64;i+=8){
racerDisplay->drawVLine(20,i,4);
racerDisplay->drawVLine(108,i,4);
}
}
// ================= INIT =================
void racerInit(U8G2 *u8g2,int buzzerPin){
racerDisplay = u8g2;
racerBuzzer = buzzerPin;
pinMode(RACER_PIN_LEFT,INPUT_PULLUP);
pinMode(RACER_PIN_RIGHT,INPUT_PULLUP);
pinMode(RACER_PIN_OK,INPUT_PULLUP);
racerReset();
}
// ================= RESET =================
void racerReset(){
racerState = RACER_START;
racerPlayerX = 64;
racerPontos = 0;
for(int i=0;i<RACER_MAX_ENEMIES;i++){
racerEnemies[i].ativo = false;
racerEnemies[i].x = 0;
racerEnemies[i].y = -30 * i; // espaçamento inicial
racerEnemies[i].speed = random(2,5);
}
racerBlinkTimer = millis();
racerBlinkOn = true;
racerEsperandoBotao = true;
racerMusicTimer = millis();
racerMusicIndex = 0;
racerLastMove = 0;
racerLastEnemyFall = 0;
racerEngineTimer = 0;
}
// ================= LOOP =================
void racerLoop(){
// ===== START =====
if(racerState==RACER_START){
if(millis()-racerBlinkTimer>400){racerBlinkTimer=millis(); racerBlinkOn=!racerBlinkOn;}
if(millis()-racerMusicTimer>300){
racerMusicTimer=millis();
racerBeep(racerStartMusic[racerMusicIndex][0], racerStartMusic[racerMusicIndex][1]);
racerMusicIndex=(racerMusicIndex+1)%racerStartMusicLen;
}
// aguarda soltar o botão
if(racerEsperandoBotao){
if(digitalRead(RACER_PIN_OK)==HIGH) racerEsperandoBotao=false;
} else if(digitalRead(RACER_PIN_OK)==LOW){
racerStopSound();
racerBeep(1000,150);
racerState=RACER_PLAY;
delay(200);
}
// Desenho da tela de START
racerDisplay->clearBuffer();
racerDisplay->setFont(u8g2_font_6x12_tf);
racerDisplay->drawStr(30,12,"RACER GAME");
// ================= ÍCONE / CARRO MAIOR =================
int iconeX = 54;
int iconeY = 20;
racerDisplay->drawBox(iconeX, iconeY, 6, 6); // topo esquerda
racerDisplay->drawBox(iconeX + 12, iconeY, 6, 6); // topo direita
racerDisplay->drawBox(iconeX + 6, iconeY + 6, 6, 6);// meio
racerDisplay->drawBox(iconeX, iconeY + 12, 6, 6); // base esquerda
racerDisplay->drawBox(iconeX + 12, iconeY + 12, 6, 6);// base direita
// Mensagem piscando
if(racerBlinkOn) racerDisplay->drawStr(18,55,"PRESSIONE START");
racerDisplay->sendBuffer();
return;
}
// ===== PLAY =====
if(racerState==RACER_PLAY){
// MOVIMENTO JOGADOR
if(millis()-racerLastMove>15){ racerLastMove=millis();
if(!digitalRead(RACER_PIN_LEFT) && racerPlayerX>24) racerPlayerX-=2;
if(!digitalRead(RACER_PIN_RIGHT) && racerPlayerX<108-racerPlayerW) racerPlayerX+=2;
}
// INIMIGOS DESCENDO
if(millis()-racerLastEnemyFall>100){ racerLastEnemyFall=millis();
for(int i=0;i<RACER_MAX_ENEMIES;i++){
if(racerEnemies[i].ativo){
racerEnemies[i].y += racerEnemies[i].speed;
if(racerEnemies[i].y >= 64){
racerEnemies[i].ativo=false;
racerPontos++;
}
// COLISÃO
if(racerEnemies[i].y + 11 >= racerPlayerY && racerEnemies[i].y <= racerPlayerY + racerPlayerH &&
racerEnemies[i].x + 11 >= racerPlayerX && racerEnemies[i].x <= racerPlayerX + racerPlayerW){
racerState=RACER_GAMEOVER;
racerBeep(300,500);
}
} else {
// novo inimigo só aparece se houver espaço vertical livre
bool podeSpawn = true;
for(int j=0;j<RACER_MAX_ENEMIES;j++){
if(racerEnemies[j].ativo && abs(racerEnemies[j].y)<20) podeSpawn=false;
}
if(podeSpawn){
racerEnemies[i].ativo = true;
racerEnemies[i].x = random(28, 94);
racerEnemies[i].y = random(-50,-10);
racerEnemies[i].speed = random(2,5);
}
}
}
}
// SOM MOTOR
if(millis() - racerEngineTimer > 60){
racerEngineTimer = millis();
racerEngineSound();
}
// DESENHO
racerDisplay->clearBuffer();
racerDrawTrack();
racerDrawPlayer(racerPlayerX,racerPlayerY);
for(int i=0;i<RACER_MAX_ENEMIES;i++) if(racerEnemies[i].ativo) racerDrawEnemy(racerEnemies[i].x,racerEnemies[i].y);
racerDisplay->setFont(u8g2_font_5x8_tf);
char txt[16]; sprintf(txt,"PONTOS:%d",racerPontos);
racerDisplay->drawStr(2,8,txt);
racerDisplay->sendBuffer();
return;
}
// ===== GAME OVER =====
if(racerState==RACER_GAMEOVER){
if(millis()-racerBlinkTimer>400){racerBlinkTimer=millis(); racerBlinkOn=!racerBlinkOn;}
if(millis()-racerMusicTimer>400){
racerMusicTimer=millis();
racerBeep(racerGameOverMusic[racerMusicIndex][0], racerGameOverMusic[racerMusicIndex][1]);
racerMusicIndex=(racerMusicIndex+1)%racerGameOverMusicLen;
}
racerDisplay->clearBuffer();
racerDisplay->setFont(u8g2_font_6x12_tf);
racerDisplay->drawStr(26,20,"GAME OVER");
char txt[20]; sprintf(txt,"PONTOS:%d",racerPontos);
racerDisplay->drawStr(28,38,txt);
if(racerBlinkOn) racerDisplay->drawStr(12,58,"OK = REINICIAR");
racerDisplay->sendBuffer();
if(!digitalRead(RACER_PIN_OK)) { racerReset(); delay(300);}
}
}
#endif
Etapa 11: Spacetrasher.h
-
#ifndef SPACETRASH_H
#define SPACETRASH_H
#include <U8g2lib.h>
#include <Arduino.h>
// ================== CONFIGURAÇÕES ==================
#define ST_OBJ_CNT 30
#define ST_FP 4
#define ST_AREA_HEIGHT 56
#define ST_AREA_WIDTH 128
// ================== ESTRUTURAS ==================
typedef struct {
uint8_t ot; // tipo do objeto
int8_t tmp; // valor temporário
int16_t x, y; // posição
int8_t x0, y0, x1, y1; // bounding box
} st_obj;
// ================== VARIÁVEIS GLOBAIS ==================
static U8G2 *st_u8g2 = nullptr;
static uint8_t st_buzzerPin = 0;
static uint8_t st_player_pos = 28;
static uint16_t st_player_points = 0;
static uint16_t st_player_points_delayed = 0;
static uint16_t st_highscore = 0;
static uint8_t st_state = 0;
static uint8_t st_difficulty = 1;
static uint16_t st_to_diff_cnt = 0;
static st_obj st_objects[ST_OBJ_CNT];
static uint8_t st_fire_player = 0;
static uint8_t st_is_fire_last_value = 1;
// Estados
#define ST_STATE_PREPARE 0
#define ST_STATE_IPREPARE 1
#define ST_STATE_GAME 2
#define ST_STATE_END 3
#define ST_STATE_IEND 4
// Tipos de objetos
#define ST_OT_TRASH1 4
#define ST_OT_PLAYER 5
#define ST_OT_MISSLE 3
#define ST_OT_BIG_TRASH 2
#define ST_OT_TRASH2 9
// Bitmaps
static const uint8_t st_bitmap_player1[] = { 0x060, 0x0f8, 0x07e, 0x0f8, 0x060 };
static const uint8_t st_bitmap_trash_5x5_1[] = { 0x070, 0x0f0, 0x0f8, 0x078, 0x030 };
static const uint8_t st_bitmap_trash_5x5_2[] = { 0x030, 0x0f8, 0x0f8, 0x0f0, 0x070 };
static const uint8_t st_bitmap_trash_7x7[] = { 0x038, 0x07c, 0x0fc, 0x0fe, 0x0fe, 0x07e, 0x078 };
// ================== FUNÇÕES INTERNAS ==================
static void st_beep(int f = 2000, int d = 60) {
if(st_buzzerPin) tone(st_buzzerPin, f, d);
}
static void st_ClrObjs() {
for(int i = 0; i < ST_OBJ_CNT; i++) {
st_objects[i].ot = 0;
}
}
static int8_t st_NewObj() {
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot == 0) return i;
}
return -1;
}
static void st_SetXY(st_obj *o, uint8_t x, uint8_t y) {
o->x = ((int16_t)x) << ST_FP;
o->y = ((int16_t)y) << ST_FP;
}
static void st_InitTrash(uint8_t x, uint8_t y) {
int8_t objnr = st_NewObj();
if(objnr < 0) return;
st_obj *o = &st_objects[objnr];
o->ot = (rand() & 1) ? ST_OT_TRASH1 : ST_OT_TRASH2;
o->tmp = (rand() & 1) ? 1 : -1;
st_SetXY(o, x, y);
o->x0 = -3; o->x1 = 1;
o->y0 = -2; o->y1 = 2;
// 25% de chance de ser lixo grande em dificuldade alta
if(st_difficulty >= 5 && (rand() & 3) == 0) {
o->ot = ST_OT_BIG_TRASH;
o->y0--; o->y1++;
o->x0--; o->x1++;
}
}
static void st_NewPlayerMissle(uint8_t x, uint8_t y) {
int8_t objnr = st_NewObj();
if(objnr < 0) return;
st_obj *o = &st_objects[objnr];
o->ot = ST_OT_MISSLE;
st_SetXY(o, x, y);
o->x0 = -4; o->x1 = 1;
o->y0 = 0; o->y1 = 0;
}
static void st_NewPlayer() {
int8_t objnr = st_NewObj();
if(objnr < 0) return;
st_obj *o = &st_objects[objnr];
o->x = 6 << ST_FP;
o->y = (ST_AREA_HEIGHT / 2) << ST_FP;
o->x0 = -6; o->x1 = 0;
o->y0 = -2; o->y1 = 2;
o->ot = ST_OT_PLAYER;
}
static void st_FireStep(uint8_t is_fire) {
if(st_fire_player < 20) {
st_fire_player++;
} else {
if(st_is_fire_last_value && !is_fire) {
st_fire_player = 0;
}
}
st_is_fire_last_value = is_fire;
}
static void st_InitDeltaTrash() {
// Contar lixo atual
uint8_t trash_cnt = 0;
uint8_t max_x = 0;
for(int i = 0; i < ST_OBJ_CNT; i++) {
uint8_t ot = st_objects[i].ot;
if(ot == ST_OT_TRASH1 || ot == ST_OT_TRASH2 || ot == ST_OT_BIG_TRASH) {
trash_cnt++;
uint8_t x = st_objects[i].x >> ST_FP;
if(x > max_x) max_x = x;
}
}
// Criar novo lixo se necessário
if(trash_cnt < 20 && max_x < (ST_AREA_WIDTH - 20)) {
st_InitTrash(ST_AREA_WIDTH - 1, rand() % ST_AREA_HEIGHT);
}
}
static void st_DrawBitmap(uint8_t x, uint8_t y, const uint8_t *bm, uint8_t w, uint8_t h) {
uint8_t screen_y = st_u8g2->getHeight() - 1 - y;
if(screen_y < h) return; // Ajuste para não desenhar fora da tela
st_u8g2->drawBitmap(x, screen_y - h + 1, (w + 7) / 8, h, bm);
}
static void st_DrawObjects() {
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot == 0) continue;
uint8_t x = st_objects[i].x >> ST_FP;
uint8_t y = st_objects[i].y >> ST_FP;
switch(st_objects[i].ot) {
case ST_OT_PLAYER:
st_DrawBitmap(x, y, st_bitmap_player1, 7, 5);
break;
case ST_OT_TRASH1:
st_DrawBitmap(x, y, st_bitmap_trash_5x5_1, 5, 5);
break;
case ST_OT_TRASH2:
st_DrawBitmap(x, y, st_bitmap_trash_5x5_2, 5, 5);
break;
case ST_OT_BIG_TRASH:
st_DrawBitmap(x, y, st_bitmap_trash_7x7, 7, 7);
break;
case ST_OT_MISSLE:
st_u8g2->drawBox(x, st_u8g2->getHeight() - 1 - y, 3, 1);
break;
}
}
}
static void st_DrawHUD() {
char buf[16];
// Pontuação
st_u8g2->setFont(u8g2_font_5x7_tf);
sprintf(buf, "%d", st_player_points_delayed);
st_u8g2->drawStr(ST_AREA_WIDTH - strlen(buf) * 5 - 2, 6, buf);
// Dificuldade
sprintf(buf, "Lvl:%d", st_difficulty);
st_u8g2->drawStr(2, 6, buf);
// Barra de progresso da dificuldade
uint8_t progress = map(st_to_diff_cnt, 0, 1500, 0, ST_AREA_WIDTH - 40);
st_u8g2->drawFrame(40, 0, ST_AREA_WIDTH - 40, 3);
st_u8g2->drawBox(40, 0, progress, 3);
}
static void st_DrawStartScreen() {
st_u8g2->setFont(u8g2_font_6x10_tf);
st_u8g2->drawStr(30, 20, "SPACE TRASH");
st_u8g2->setFont(u8g2_font_5x8_tf);
st_u8g2->drawStr(25, 40, "PRESS FIRE");
// Desenha nave do jogador
st_DrawBitmap(60, 50, st_bitmap_player1, 7, 5);
}
static void st_DrawGameOver() {
st_u8g2->setFont(u8g2_font_6x10_tf);
st_u8g2->drawStr(30, 20, "GAME OVER");
st_u8g2->setFont(u8g2_font_5x8_tf);
char buf[32];
sprintf(buf, "SCORE: %d", st_player_points);
st_u8g2->drawStr(30, 35, buf);
if(st_player_points > st_highscore) {
st_highscore = st_player_points;
st_u8g2->drawStr(25, 45, "NEW HIGHSCORE!");
} else {
sprintf(buf, "HIGH: %d", st_highscore);
st_u8g2->drawStr(35, 45, buf);
}
st_u8g2->drawStr(20, 55, "FIRE TO RESTART");
}
// ================== FUNÇÕES PÚBLICAS ==================
void spacetrashInit(U8G2 *display, uint8_t buzzerPin) {
st_u8g2 = display;
st_buzzerPin = buzzerPin;
}
void spacetrashReset() {
st_player_points = 0;
st_player_points_delayed = 0;
st_difficulty = 1;
st_to_diff_cnt = 0;
st_ClrObjs();
st_NewPlayer();
st_state = ST_STATE_PREPARE;
st_beep(1500, 100);
}
void spacetrashLoop() {
if(!st_u8g2) return;
// Limpar buffer
st_u8g2->clearBuffer();
// Desenhar de acordo com o estado
switch(st_state) {
case ST_STATE_PREPARE:
st_DrawStartScreen();
break;
case ST_STATE_GAME:
// Desenhar objetos
st_DrawObjects();
// Desenhar HUD
st_DrawHUD();
// Atualizar lógica do jogo
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot) {
// Mover para esquerda
st_objects[i].x -= (1 << ST_FP) / 8 + st_difficulty;
// Remover se saiu da tela
if((st_objects[i].x >> ST_FP) < -10) {
st_objects[i].ot = 0;
}
}
}
// Gerar novo lixo
if(rand() % (30 - st_difficulty) == 0) {
st_InitDeltaTrash();
}
// Atualizar pontuação
if(st_player_points_delayed < st_player_points) {
st_player_points_delayed++;
}
// Aumentar dificuldade
st_to_diff_cnt++;
if(st_to_diff_cnt >= 1500) {
st_to_diff_cnt = 0;
st_difficulty++;
st_player_points += 25;
st_beep(1200, 50);
}
// Verificar colisões (simplificado)
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot >= ST_OT_TRASH1 && st_objects[i].ot <= ST_OT_BIG_TRASH) {
uint8_t trash_x = st_objects[i].x >> ST_FP;
uint8_t trash_y = st_objects[i].y >> ST_FP;
// Colisão com jogador
if(trash_x < 10 && trash_x > 0 &&
abs(trash_y - st_player_pos) < 5) {
st_state = ST_STATE_END;
st_beep(300, 200);
}
// Colisão com mísseis
for(int j = 0; j < ST_OBJ_CNT; j++) {
if(st_objects[j].ot == ST_OT_MISSLE) {
uint8_t missle_x = st_objects[j].x >> ST_FP;
uint8_t missle_y = st_objects[j].y >> ST_FP;
if(abs(missle_x - trash_x) < 5 && abs(missle_y - trash_y) < 5) {
st_objects[i].ot = 0;
st_objects[j].ot = 0;
st_player_points += st_difficulty * 5;
st_beep(1000, 30);
}
}
}
}
}
break;
case ST_STATE_END:
st_DrawGameOver();
break;
}
// Enviar buffer para o display
st_u8g2->sendBuffer();
}
// Função para ser chamada no loop principal com os botões
void spacetrashHandleInput(uint8_t up, uint8_t down, uint8_t fire) {
static uint8_t lastFire = 1;
switch(st_state) {
case ST_STATE_PREPARE:
if(!fire && lastFire) {
st_state = ST_STATE_GAME;
spacetrashReset();
st_beep(2000, 100);
}
break;
case ST_STATE_GAME:
// Movimento
if(up && st_player_pos > 5) st_player_pos--;
if(down && st_player_pos < ST_AREA_HEIGHT - 5) st_player_pos++;
// Atualizar posição do jogador
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot == ST_OT_PLAYER) {
st_objects[i].y = st_player_pos << ST_FP;
break;
}
}
// Tiro
st_FireStep(fire);
if(st_fire_player == 0) {
for(int i = 0; i < ST_OBJ_CNT; i++) {
if(st_objects[i].ot == ST_OT_PLAYER) {
uint8_t x = (st_objects[i].x >> ST_FP) + 6;
uint8_t y = st_objects[i].y >> ST_FP;
st_NewPlayerMissle(x, y);
st_beep(4000, 20);
break;
}
}
}
break;
case ST_STATE_END:
if(!fire && lastFire) {
st_state = ST_STATE_PREPARE;
st_beep(1800, 100);
}
break;
}
lastFire = fire;
}
#endif

Comentários
0