Guias · 21 min de leitura

Jogos com Arduino: como fazer Pong, T-Rex, Snake e Simon

Projetos de jogos com Arduino passo a passo: Pong e T-Rex num OLED, Snake numa matriz de LED, Simon, uma corrida no LCD e um teste de reação, mais código de recorde na EEPROM.

Fazer jogos com Arduino é o jeito mais divertido de aprender eletrônica e programação: com um display OLED, alguns botões e um joystick, você mesmo monta clássicos como Pong, a corrida do T-Rex, Snake e o jogo da memória Simon, e depois desafia os amigos a bater o seu recorde. Enquanto escreve um jogo, você também aprende quase sem perceber os temas mais importantes da programação: controle de tempo, máquinas de estados, sistemas de coordenadas, cálculo de colisões, números aleatórios e memória permanente. Todos esses jogos rodam num único Arduino Uno; os 2 KB de RAM dele bastam, desde que você use a memória com cuidado.

Neste guia, montamos seis jogos do começo ao fim: Pong e a corrida do T-Rex num display OLED, Snake numa matriz de LED NeoPixel 8x8 e 16x16, Simon com quatro LEDs e quatro botões, um jogo de corrida num LCD 16x2 e um teste de reação com precisão de milissegundos. Cada jogo vem com lista de componentes, ligações e código que compila. Primeiro aprendemos o esqueleto que todos compartilham, ou seja, o loop do jogo, a detecção de colisão, a pontuação e a gravação do recorde na EEPROM, e depois o reaproveitamos em cada jogo. No fim, você também encontra versões para ESP32 e Raspberry Pi Pico.

Jogo Snake com Arduino numa matriz de LED NeoPixel 8x8
Matriz de LED NeoPixel WS2812B 8x8: 64 pixels coloridos e um único fio de dados, uma minitela ideal para um jogo Snake.

Qual jogo precisa de quais componentes?

Quando você faz um jogo com Arduino, a primeira decisão é o display, porque é ele que define que tipo de jogo dá para fazer. Só com alguns LEDs você já monta o Simon ou um teste de reação, enquanto um OLED de 128×64 pixels torna possíveis o Pong e o T-Rex, com gráficos em movimento e física. A tabela abaixo resume os jogos deste guia, do mais fácil ao mais difícil:

JogoDisplayEntradaOutros componentesDificuldade
Teste de reação1 LED + Monitor Serial1 botãoResistor de 220 Ω, buzzer opcionalFácil
Jogo da memória Simon4 LEDs coloridos4 botões4 resistores de 220 Ω, buzzer passivoFácil–médio
Corrida no LCDLCD I2C 16x21 botãoBuzzer passivoMédio
PongOLED SSD1306 de 0,96"Joystick ou potenciômetro de 10 kΩBuzzer passivoMédio
Corrida do T-RexOLED SSD1306 de 0,96"1 botão ou o botão do joystickBuzzer passivoMédio
Snake (8x8)Matriz NeoPixel 8x8JoystickResistor de 330 Ω, capacitor de 1000 µFMédio–avançado
Snake (16x16)Matriz NeoPixel 16x16JoystickFonte externa de 5 V e 3–4 AAvançado
Arduino Uno
Arduino Uno
OLED SSD1306 de 0,96"
OLED SSD1306 de 0,96"
Joystick
Joystick
LCD 16x2 I2C
LCD 16x2 I2C
Buzzer
Buzzer
Botão de pressão
Botão de pressão

Ligações em comum: um circuito que vira console de jogos

Escolhemos pinos em comum para que todos os jogos, exceto o Simon, rodem no mesmo circuito. O OLED e o LCD I2C compartilham os mesmos dois fios (SDA e SCL); o joystick vai nas entradas analógicas, o botão de ação no D3 e o buzzer no D8. Depois de montar esse “console de jogos”, você troca de jogo só carregando outro código. Você vê o mapa completo de pinos da sua placa na ferramenta Pinagem.

Componente / pinoArduino UnoESP32Raspberry Pi Pico
OLED ou LCD I2C: SDAA4GPIO 21GP4
OLED ou LCD I2C: SCLA5GPIO 22GP5
Joystick VRx (eixo X)A0GPIO 34GP26
Joystick VRy (eixo Y)A1GPIO 35GP27
Joystick SW (botão)D2GPIO 25GP15
Botão de ação (outro terminal no GND)D3GPIO 26GP14
Buzzer passivo (+)D8GPIO 27GP16
NeoPixel DIN (através de 330 Ω)D6GPIO 13GP22
No ESP32 e no Pico, alimente o joystick e o OLED com 3,3 V; um joystick alimentado com 5 V coloca mais de 3,3 V nos pinos analógicos e pode danificar a placa. Se você ligar um módulo LCD I2C de 5 V a um ESP32, coloque um conversor de nível lógico entre os dois. Todos os botões usam o resistor de pull-up interno: um terminal vai no pino, o outro no GND, e o pino lê LOW quando o botão é pressionado.

Como funciona o loop de um jogo?

Todo videogame se baseia num loop de quatro passos que se repete dezenas de vezes por segundo: ler a entrada (botões, joystick), atualizar o mundo (posição, velocidade, pontuação), verificar colisões e desenhar a tela. Cada passagem pelo loop se chama quadro (frame). Trinta quadros por segundo (30 FPS) bastam para parecer fluido, o que significa que cada quadro dura cerca de 33 ms. A função loop() do Arduino já é um loop, mas roda muito rápido e de forma irregular, então você precisa dividi-la em quadros fixos com millis().

Evitar o delay() aqui é essencial. Se você escreve delay(33), o Arduino não faz nada durante esses 33 ms, pode perder um toque rápido no botão, e a velocidade do jogo muda sempre que o desenho leva mais ou menos tempo. Com millis(), o loop() lê a entrada a cada passagem, mas só avança o jogo quando chega a hora de um novo quadro. A diferença entre os dois está explicada em detalhes em millis ou delay no Arduino. O esqueleto abaixo é a base de todos os jogos deste guia:

C++ (Arduino IDE)
1// Oyun döngüsü iskeleti / Game loop skeleton
2enum State { MENU, PLAYING, GAME_OVER };   // oyunun durumları / game states
3State state = MENU;
4
5const byte BUTTON_PIN = 3;                 // aksiyon butonu / action button
6const unsigned long FRAME_MS = 33;         // ~30 kare/sn / ~30 frames per second
7unsigned long lastFrame = 0;
8unsigned int score = 0;
9
10// Sıçramasız basış: yalnız basıldığı an true / bounce-free: true only at the moment of a press
11bool buttonPressed() {
12  static bool stable = HIGH, lastRead = HIGH;
13  static unsigned long changedAt = 0;
14  bool r = digitalRead(BUTTON_PIN);
15  if (r != lastRead) { lastRead = r; changedAt = millis(); }
16  if (millis() - changedAt > 15 && r != stable) {   // 15 ms sabit kaldı mı? / stable for 15 ms?
17    stable = r;
18    return stable == LOW;
19  }
20  return false;
21}
22
23void setup() {
24  pinMode(BUTTON_PIN, INPUT_PULLUP);       // basınca LOW / LOW when pressed
25  Serial.begin(9600);
26}
27
28void loop() {
29  bool pressed = buttonPressed();          // 1) giriş: her turda oku / input: read on every pass
30  if (pressed && state != PLAYING) {       // menüden ya da oyun sonundan başlat / start from menu or game over
31    score = 0;
32    state = PLAYING;
33  }
34  if (millis() - lastFrame < FRAME_MS) return;   // kare zamanı gelmedi / not time for a frame yet
35  lastFrame = millis();
36
37  if (state == PLAYING) {
38    score++;                               // 2) güncelle: konum, hız, puan / update: position, speed, score
39    if (score >= 300) state = GAME_OVER;   // 3) çarpışma ya da bitiş kontrolü / collision or end check
40    Serial.println(score);                 // 4) çiz: burada ekrana çizilir / draw: the screen is drawn here
41  }
42}

A segunda ideia-chave do código é a máquina de estados. A cada momento, o jogo está num de três estados: MENU, PLAYING ou GAME_OVER. Cada estado reage de forma diferente ao mesmo botão: no menu, um toque inicia o jogo; durante a partida, faz o personagem pular; e depois do fim de jogo, começa uma nova rodada. Dividir o código assim elimina blocos if aninhados e facilita acrescentar um novo estado, como PAUSE. Tratamos o tema com mais profundidade em máquina de estados no Arduino.

Leitura de botões e de um joystick

Ligar os botões com INPUT_PULLUP é o método mais limpo: não precisa de resistor externo, o pino lê HIGH em repouso e LOW quando pressionado. Porém, um botão mecânico trepida por alguns milissegundos ao ser pressionado ou solto, e o Arduino entende isso como vários toques seguidos. Num jogo, isso significa pular duas vezes com um toque só, ou o Simon contar uma cor errada. A função buttonPressed() do esqueleto só aceita um novo estado depois que o pino fica igual por 15 ms e retorna true uma única vez, no momento do toque. Para o circuito e outros métodos, veja como ligar um botão ao Arduino.

Um joystick é feito de dois potenciômetros e um botão. No Uno, cada eixo dá um valor de 0 a 1023 e, com a alavanca solta, o centro fica em torno de 512, mas isso varia de módulo para módulo entre aproximadamente 490 e 530. Por isso, jogos em grade usam limites em vez de valores exatos: abaixo de 300 significa esquerda ou cima, acima de 700 significa direita ou baixo. A faixa larga no meio (a zona morta) impede que o personagem se mova sozinho quando você solta a alavanca. Já no Pong, mapeamos o valor analógico direto para a raquete: map() converte a faixa 0–1023 no curso de 0–48 pixels da raquete. A calibração e a tabela de direções estão no guia do módulo joystick.

Como funciona a detecção de colisão?

A detecção de colisão pergunta se dois objetos estão no mesmo lugar ao mesmo tempo. Em jogos com tela, o método mais comum é o teste AABB (caixa delimitadora alinhada aos eixos): você trata cada objeto como um retângulo. Dois retângulos se sobrepõem só se as quatro condições forem verdadeiras ao mesmo tempo: a borda esquerda de A está à esquerda da borda direita de B, a borda direita de A está à direita da borda esquerda de B, e as mesmas duas condições valem no eixo vertical. No código, é uma linha só: ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by. Em jogos em grade (Snake, a corrida no LCD), é ainda mais simples: se duas células têm as mesmas coordenadas, há colisão.

JogoO que colide com o quê?Método
PongBola com as paredes e as raquetesAABB + inversão da velocidade
T-RexDinossauro com os cactosAABB (com uma caixa um pouco menor)
SnakeCabeça com as paredes, o corpo e a comidaIgualdade de células da grade
Corrida no LCDCorredor com um cactoMesma coluna e mesma linha
SimonCor pressionada com a cor da sequênciaComparação de elementos do array
Teste de reaçãoMomento do toque com o momento do sinalComparação de tempos (largada queimada)
Objetos pequenos e rápidos podem atravessar um obstáculo num único quadro e escapar da colisão; isso se chama tunelamento. No Pong, se a bola se move mais de 4 pixels por quadro, ela cruza a zona de verificação de 3 pixels da raquete sem nunca tocá-la. A solução é manter a velocidade do objeto por quadro menor que a largura da zona de verificação, e é exatamente isso que faz o limite MAX_SPEED = 2.8 no código do Pong.

Pontuação e gravação do recorde na EEPROM

A pontuação fica numa variável na RAM e some no instante em que você desconecta o Arduino. Para tornar um recorde permanente, você usa a EEPROM: o ATmega328P do Arduino Uno tem 1024 bytes de EEPROM, que mantêm o conteúdo sem alimentação. Dois bytes do tipo uint16_t bastam para um recorde (0–65535). O único ponto fraco da EEPROM é a resistência à gravação: cada célula pode ser gravada cerca de 100.000 vezes. Parece muito, mas se você gravar a pontuação a cada quadro, 30 vezes por segundo, a célula se desgasta em cerca de 55 minutos. O certo é gravar só quando o jogo termina e o recorde foi batido; mesmo com 10 recordes novos por dia, a célula vai durar 27 anos.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const int HISCORE_ADDR = 0;          // rekor EEPROM'un 0-1. baytında / high score in EEPROM bytes 0-1
4uint16_t hiScore = 0;
5
6uint16_t loadHiScore() {
7  uint16_t v;
8  EEPROM.get(HISCORE_ADDR, v);       // 2 bayt oku / read 2 bytes
9  if (v == 0xFFFF) v = 0;            // hiç yazılmamış EEPROM 0xFF okunur / blank EEPROM reads 0xFF
10  return v;
11}
12
13void saveIfRecord(uint16_t score) {
14  if (score > hiScore) {             // yalnız rekor kırılınca yaz / write only on a new record
15    hiScore = score;
16    EEPROM.put(HISCORE_ADDR, hiScore);   // put() yalnız değişen baytları yazar / put() writes only changed bytes
17  }
18}
19
20void setup() {
21  Serial.begin(9600);
22  hiScore = loadHiScore();
23  Serial.print(F("Rekor / High score: "));
24  Serial.println(hiScore);
25  saveIfRecord(120);                 // örnek: oyun 120 puanla bitti / example: a game ended with 120 points
26}
27
28void loop() {}

O código tem dois pontos sutis. Primeiro, todo byte de uma EEPROM que nunca foi gravada vale 0xFF, então na primeiríssima inicialização o recorde aparece como 65535, e a verificação de 0xFFFF o transforma em 0. No teste de reação é o contrário: 0xFFFF significa “ainda não há recorde”, porque ali um tempo menor é melhor. Segundo, o EEPROM.put() lê cada byte antes de gravar e só grava os bytes que mudaram, o que aumenta ainda mais a vida útil. Para os outros comandos da EEPROM, veja como usar a EEPROM do Arduino. Para que você possa carregar os jogos deste guia no mesmo Uno, um depois do outro, cada jogo tem seu próprio endereço:

JogoEndereço na EEPROMValor gravado
Corrida do T-Rex0–1Maior pontuação
Snake2–3Maior quantidade de comida
Corrida no LCD4–5Maior pontuação
Teste de reação6–7Menor tempo (ms)
Simon8–9Maior nível alcançado

Pong: display OLED e joystick

Pong é o jogo de tênis de mesa lançado em 1972 que deu início à era dos videogames. Na nossa versão, você controla a raquete da esquerda com o eixo Y do joystick (ou com um potenciômetro no A1), e o Arduino joga com a raquete da direita. Componentes: um Arduino Uno, um OLED I2C SSD1306 de 0,96 polegada, um joystick e um buzzer passivo. Ao instalar a biblioteca Adafruit SSD1306 pelo Gerenciador de Bibliotecas, instale também a dependência dela, a Adafruit GFX. As ligações do display e os comandos de desenho estão explicados em detalhes em display OLED SSD1306.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5Adafruit_SSD1306 display(128, 64, &Wire, -1);
6
7const byte JOY_Y_PIN  = A1;          // joystick VRy ya da potansiyometre / joystick VRy or potentiometer
8const byte BUZZER_PIN = 8;           // pasif buzzer / passive buzzer
9const byte PADDLE_H = 16, PADDLE_W = 3;
10const byte WIN_SCORE = 5;            // 5 sayı alan kazanır / first to 5 wins
11const float MAX_SPEED = 2.8;         // raketten kalın olmasın / keep below paddle thickness + ball size
12const unsigned long FRAME_MS = 30;   // ~33 kare/sn / ~33 frames per second
13
14float ballX, ballY, velX, velY;
15float playerY = 24, cpuY = 24;
16byte playerScore = 0, cpuScore = 0;
17unsigned long lastFrame = 0;
18
19void serve(int dir) {                // topu ortadan at / serve from the center
20  ballX = 63; ballY = 31;
21  velX = 1.8 * dir;
22  velY = random(-10, 11) / 10.0;     // -1,0 ile 1,0 arası / between -1.0 and 1.0
23}
24
25// Topun rakete çarptığı nokta yeni açıyı belirler / where the ball hits the paddle sets the new angle
26void hitPaddle(float paddleY) {
27  float offset = (ballY + 1 - (paddleY + PADDLE_H / 2.0)) / (PADDLE_H / 2.0);  // -1 üst, +1 alt / -1 top, +1 bottom
28  velX = constrain(-velX * 1.05, -MAX_SPEED, MAX_SPEED);   // yön değiştir, %5 hızlan / reverse, 5% faster
29  velY = offset * 1.5;
30  tone(BUZZER_PIN, 880, 15);
31}
32
33void setup() {
34  Serial.begin(9600);
35  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
36    Serial.println(F("SSD1306 allocation failed"));
37    for (;;);                        // ekran yoksa dur / halt without a display
38  }
39  display.setTextColor(SSD1306_WHITE);
40  randomSeed(analogRead(A3));        // boştaki analog pin / a floating analog pin
41  serve(1);
42}
43
44void loop() {
45  if (millis() - lastFrame < FRAME_MS) return;
46  lastFrame = millis();
47
48  // 1) Giriş: oyuncu raketi / Input: player paddle
49  playerY = map(analogRead(JOY_Y_PIN), 0, 1023, 0, 64 - PADDLE_H);
50
51  // 2) Basit yapay zekâ: CPU topu sınırlı hızla izler / Simple AI: the CPU follows the ball at limited speed
52  float target = ballY - PADDLE_H / 2.0;
53  cpuY += constrain(target - cpuY, -1.2, 1.2);
54  cpuY = constrain(cpuY, 0, 64 - PADDLE_H);
55
56  // 3) Topu hareket ettir / Move the ball
57  ballX += velX;
58  ballY += velY;
59
60  // 4) Çarpışmalar / Collisions
61  if (ballY <= 0 || ballY >= 62) {   // üst ve alt duvar / top and bottom walls
62    velY = -velY;
63    ballY = constrain(ballY, 0, 62);
64  }
65  if (velX < 0 && ballX <= 4 && ballX > 0 && ballY + 2 >= playerY && ballY <= playerY + PADDLE_H) {
66    ballX = 4;
67    hitPaddle(playerY);
68  }
69  if (velX > 0 && ballX >= 122 && ballX < 126 && ballY + 2 >= cpuY && ballY <= cpuY + PADDLE_H) {
70    ballX = 122;
71    hitPaddle(cpuY);
72  }
73
74  // 5) Sayı / Point
75  if (ballX <= 0)   { cpuScore++;    tone(BUZZER_PIN, 220, 200); serve(1); }
76  if (ballX >= 126) { playerScore++; tone(BUZZER_PIN, 660, 200); serve(-1); }
77
78  // 6) Çiz / Draw
79  display.clearDisplay();
80  for (byte y = 0; y < 64; y += 6) display.drawFastVLine(63, y, 3, SSD1306_WHITE);  // orta çizgi / center line
81  display.fillRect(1, (int)playerY, PADDLE_W, PADDLE_H, SSD1306_WHITE);
82  display.fillRect(124, (int)cpuY, PADDLE_W, PADDLE_H, SSD1306_WHITE);
83  display.fillRect((int)ballX, (int)ballY, 2, 2, SSD1306_WHITE);
84  display.setTextSize(1);
85  display.setCursor(52, 0); display.print(playerScore);
86  display.setCursor(70, 0); display.print(cpuScore);
87  display.display();
88
89  // 7) Oyun bitti mi? / Game over?
90  if (playerScore >= WIN_SCORE || cpuScore >= WIN_SCORE) {
91    display.setTextSize(2);
92    if (playerScore >= WIN_SCORE) { display.setCursor(22, 24); display.print(F("YOU WIN")); }
93    else                          { display.setCursor(10, 24); display.print(F("GAME OVER")); }
94    display.display();
95    delay(3000);
96    playerScore = cpuScore = 0;
97    serve(1);
98  }
99}

O que torna o Pong divertido é a função hitPaddle(). Se a bola bate no meio da raquete, volta quase reta; se bate na borda, ricocheteia num ângulo fechado e pressiona o adversário. Fazemos isso escalando a distância da bola até o centro da raquete para um valor entre -1 e +1 e transformando-o em velocidade vertical. A bola acelera 5% a cada rebatida e é limitada por MAX_SPEED. A raquete do Arduino pode se mover no máximo 1,2 pixel por quadro, então, se você der um ângulo fechado à bola, ela não consegue acompanhar e você marca ponto. Para deixar o jogo mais difícil, aumente esse valor para 1.5; para dois jogadores, apague as linhas da CPU e controle a segunda raquete com um potenciômetro no A2.

Por que um tempo de quadro de 30 ms? O Uno envia o buffer de quadro de 1024 bytes ao OLED pelo I2C de 400 kHz em cerca de 25 ms, então o display não consegue mostrar mais de 35–40 quadros por segundo. Manter o tempo de quadro um pouco acima desse tempo de transferência deixa a velocidade do jogo estável. E não se engane com o relatório do compilador “Variáveis globais usam 28%”: o buffer do SSD1306 é alocado em tempo de execução, então o uso real de RAM sobe para cerca de 78%. Se a memória acabar, você verá o erro SSD1306 allocation failed; reunimos as soluções em memória insuficiente no Arduino.

Corrida do T-Rex: física do pulo e recorde

Vamos levar para o display OLED o jogo do dinossauro que o Chrome mostra quando você está sem internet. O dinossauro corre sem sair do lugar, os cactos passam da direita para a esquerda e você pula sobre eles apertando o botão. Para o pulo parecer realista, usamos uma física simples: apertar o botão define a velocidade vertical em -4,2 pixels por quadro, a cada quadro a gravidade soma 0,3 a essa velocidade, e a posição muda conforme a velocidade. Assim, o dinossauro sobe desacelerando, fica um instante parado no alto e cai cada vez mais rápido; ele sobe cerca de 27 pixels e fica no ar por 27 quadros, cerca de 0,8 segundo. Os componentes são os mesmos do Pong; para pular, você usa o botão no D3 ou o botão do joystick.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <EEPROM.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5
6Adafruit_SSD1306 display(128, 64, &Wire, -1);
7
8const byte JUMP_PIN     = 3;         // aksiyon butonu / action button
9const byte JOY_SW_PIN   = 2;         // joystick butonu da zıplatır / the joystick button jumps too
10const byte BUZZER_PIN   = 8;
11const int  HISCORE_ADDR = 0;         // EEPROM 0-1. bayt / EEPROM bytes 0-1
12const byte GROUND_Y = 56;            // zemin çizgisi / ground line
13const byte DINO_X = 10, DINO_W = 14, DINO_H = 14;
14const float JUMP_V  = -4.2;          // zıplama hızı (piksel/kare) / jump speed (px/frame)
15const float GRAVITY = 0.3;           // yer çekimi (piksel/kare²) / gravity (px/frame²)
16const unsigned long FRAME_MS = 30;   // ~33 kare/sn / ~33 frames per second
17
18enum State { MENU, PLAYING, GAME_OVER };
19State state = MENU;
20
21struct Cactus { float x; byte w, h; };
22Cactus cacti[2];
23
24float dinoY, velY, speed;
25uint16_t score = 0, hiScore = 0;
26unsigned long lastFrame = 0;
27
28bool jumpHeld() {
29  return digitalRead(JUMP_PIN) == LOW || digitalRead(JOY_SW_PIN) == LOW;
30}
31
32uint16_t loadHiScore() {
33  uint16_t v;
34  EEPROM.get(HISCORE_ADDR, v);
35  return (v == 0xFFFF) ? 0 : v;      // boş EEPROM 0xFFFF okunur / blank EEPROM reads 0xFFFF
36}
37
38void placeCactus(byte i, float startX) {
39  cacti[i].x = startX;
40  cacti[i].w = random(4, 9);         // 4-8 piksel genişlik / 4-8 px wide
41  cacti[i].h = random(8, 17);        // 8-16 piksel yükseklik / 8-16 px tall
42}
43
44void resetGame() {
45  dinoY = GROUND_Y - DINO_H;
46  velY = 0;
47  speed = 2.0;
48  score = 0;
49  placeCactus(0, 140);
50  placeCactus(1, 140 + random(70, 140));
51}
52
53// İki dikdörtgen üst üste biniyor mu? (AABB) / Do two rectangles overlap? (AABB)
54bool overlap(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) {
55  return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
56}
57
58void drawDino(int x, int y, bool legA) {
59  display.fillRect(x + 6, y, 8, 5, SSD1306_WHITE);                  // kafa / head
60  display.drawPixel(x + 9, y + 1, SSD1306_BLACK);                   // göz / eye
61  display.fillRect(x, y + 5, 10, 6, SSD1306_WHITE);                 // gövde / body
62  display.fillRect(x + 2, y + 11, 2, legA ? 3 : 1, SSD1306_WHITE);  // sol bacak / left leg
63  display.fillRect(x + 6, y + 11, 2, legA ? 1 : 3, SSD1306_WHITE);  // sağ bacak / right leg
64}
65
66void drawScene() {
67  display.clearDisplay();
68  display.drawFastHLine(0, GROUND_Y, 128, SSD1306_WHITE);
69  bool onGround = dinoY >= GROUND_Y - DINO_H;
70  drawDino(DINO_X, (int)dinoY, onGround ? (score / 4) % 2 : true);  // koşma animasyonu / running animation
71  for (byte i = 0; i < 2; i++) {
72    display.fillRect((int)cacti[i].x, GROUND_Y - cacti[i].h, cacti[i].w, cacti[i].h, SSD1306_WHITE);
73  }
74  display.setTextSize(1);
75  display.setCursor(0, 0);  display.print(score);
76  display.setCursor(80, 0); display.print(F("HI ")); display.print(hiScore);
77}
78
79void showMessage(const __FlashStringHelper *msg) {
80  display.fillRect(14, 18, 100, 28, SSD1306_BLACK);
81  display.drawRect(14, 18, 100, 28, SSD1306_WHITE);
82  display.setCursor(64 - strlen_P((const char *)msg) * 3, 28);   // ortala (karakter 6 piksel) / center (6 px per char)
83  display.print(msg);
84  display.display();
85}
86
87void gameOver() {
88  tone(BUZZER_PIN, 150, 300);
89  if (score > hiScore) {             // yeni rekor: yalnız şimdi yaz / new record: write only now
90    hiScore = score;
91    EEPROM.put(HISCORE_ADDR, hiScore);
92  }
93  drawScene();
94  showMessage(F("GAME OVER"));
95  delay(800);                        // basılı buton hemen yeniden başlatmasın / a held button must not restart at once
96  state = GAME_OVER;
97}
98
99void setup() {
100  pinMode(JUMP_PIN, INPUT_PULLUP);
101  pinMode(JOY_SW_PIN, INPUT_PULLUP);
102  Serial.begin(9600);
103  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
104    Serial.println(F("SSD1306 allocation failed"));
105    for (;;);
106  }
107  display.setTextColor(SSD1306_WHITE);
108  randomSeed(analogRead(A3));
109  hiScore = loadHiScore();
110  if (digitalRead(JUMP_PIN) == LOW) {    // açılışta basılıysa rekoru sıfırla / held at power-up: reset the record
111    hiScore = 0;
112    EEPROM.put(HISCORE_ADDR, hiScore);
113  }
114  resetGame();
115  drawScene();
116  showMessage(F("PRESS START"));
117}
118
119void loop() {
120  if (millis() - lastFrame < FRAME_MS) return;
121  lastFrame = millis();
122
123  if (state != PLAYING) {            // menü ya da oyun sonu: basınca başla / menu or game over: press to start
124    if (jumpHeld()) {
125      while (jumpHeld());            // bırakılmasını bekle / wait for release
126      resetGame();
127      state = PLAYING;
128    }
129    return;
130  }
131
132  // 1) Giriş ve fizik / Input and physics
133  bool onGround = dinoY >= GROUND_Y - DINO_H;
134  if (onGround && jumpHeld()) {
135    velY = JUMP_V;
136    tone(BUZZER_PIN, 1200, 20);
137  }
138  velY += GRAVITY;                   // her karede hız artar / velocity grows every frame
139  dinoY += velY;
140  if (dinoY > GROUND_Y - DINO_H) { dinoY = GROUND_Y - DINO_H; velY = 0; }   // yere indi / landed
141
142  // 2) Engeller ve çarpışma / Obstacles and collision
143  for (byte i = 0; i < 2; i++) {
144    cacti[i].x -= speed;
145    if (cacti[i].x < -(float)cacti[i].w) {            // ekrandan çıktı / left the screen
146      float otherX = cacti[1 - i].x;
147      placeCactus(i, max(128.0, otherX + random(70, 140)));
148    }
149    // biraz küçük çarpışma kutusu daha adil hissettirir / a slightly smaller hitbox feels fairer
150    if (overlap(DINO_X + 2, (int)dinoY + 1, DINO_W - 4, DINO_H - 2,
151                (int)cacti[i].x, GROUND_Y - cacti[i].h, cacti[i].w, cacti[i].h)) {
152      gameOver();
153      return;
154    }
155  }
156
157  // 3) Puan ve zorluk / Score and difficulty
158  score++;
159  if (score % 200 == 0 && speed < 5.0) speed += 0.25;   // yaklaşık 6 sn'de bir hızlan / speed up every ~6 s
160
161  // 4) Çiz / Draw
162  drawScene();
163  display.display();
164}
ParâmetroValorEfeito
JUMP_V-4.2Força do pulo; com -5, o dinossauro sobe cerca de 40 pixels, perto do topo da tela
GRAVITY0.3Um valor maior deixa o pulo mais curto e mais brusco
speed2.0 → 5.0Velocidade dos cactos; aumenta 0,25 a cada 200 quadros
random(70, 140)pixelsDistância entre dois cactos; valores menores deixam o jogo mais difícil
Hitbox10×12 pixelsMenor que o dinossauro desenhado de 14×14, para que quase-colisões não contem como morte

O recorde é gravado na EEPROM no instante em que o jogo termina, e só se tiver sido batido; ele aparece no canto superior direito como HI. Se você segurar o botão D3 ao ligar a placa, o recorde é zerado, o que é prático quando começa um novo torneio com os amigos. A altura e a largura dos cactos vêm de random(). Para que cada partida comece diferente, a linha randomSeed(analogRead(A3)) usa o ruído de um pino analógico desconectado como semente.

Snake numa matriz de LED (8x8 e 16x16)

O jogo Snake, que ficou famoso nos antigos celulares Nokia, fica melhor numa matriz de LED, porque cada LED é uma célula do jogo. Usamos uma matriz colorida WS2812B NeoPixel: a versão 8x8 tem 64 pixels e a 16x16, 256, todos controlados por um único fio de dados. O corpo da cobra fica guardado em dois arrays (snakeX e snakeY), e o elemento 0 é a cabeça. A cada passo, o corpo se desloca uma célula e a nova posição é gravada na cabeça; quando a cobra come, a cauda não é removida, e assim ela cresce uma célula. Para compilar o código, instale a biblioteca Adafruit NeoPixel; os detalhes das ligações estão no guia WS2812B NeoPixel.

C++ (Arduino IDE)
1#include <Adafruit_NeoPixel.h>
2#include <EEPROM.h>
3
4const byte W = 8, H = 8;             // 16x16 matris için 16 yap / set to 16 for a 16x16 matrix
5const bool SERPENTINE = false;       // zikzak dizilimli panelde true / true for zigzag-wired panels
6const byte DATA_PIN = 6;             // DIN, 330 Ω üzerinden / DIN through 330 Ω
7const byte JOY_X = A0, JOY_Y = A1, JOY_SW = 2;
8const byte BUZZER_PIN = 8;
9const int  HISCORE_ADDR = 2;         // EEPROM 2-3. bayt / EEPROM bytes 2-3
10
11Adafruit_NeoPixel matrix(W * H, DATA_PIN, NEO_GRB + NEO_KHZ800);
12
13byte snakeX[W * H], snakeY[W * H];   // [0] baş, sonrası gövde / [0] head, then the body
14int snakeLen;
15int8_t dirX, dirY, nextDX, nextDY;
16byte foodX, foodY;
17unsigned int stepMs;
18unsigned long lastStep = 0;
19uint16_t hiScore;
20bool alive = false;
21
22// (x, y) → LED sırası / (x, y) → LED index
23uint16_t xyToIndex(byte x, byte y) {
24  if (SERPENTINE && (y & 1)) return y * W + (W - 1 - x);   // tek satırlar ters yönde / odd rows run backwards
25  return y * W + x;
26}
27
28bool onSnake(byte x, byte y) {
29  for (int i = 0; i < snakeLen; i++) {
30    if (snakeX[i] == x && snakeY[i] == y) return true;
31  }
32  return false;
33}
34
35void placeFood() {
36  do {                               // yem yılanın üstüne düşmesin / food must not land on the snake
37    foodX = random(W);
38    foodY = random(H);
39  } while (onSnake(foodX, foodY));
40}
41
42void resetGame() {
43  snakeLen = 3;
44  for (int i = 0; i < snakeLen; i++) { snakeX[i] = 3 - i; snakeY[i] = H / 2; }
45  dirX = nextDX = 1;                 // sağa doğru başla / start moving right
46  dirY = nextDY = 0;
47  stepMs = 300;
48  placeFood();
49  alive = true;
50}
51
52void readJoystick() {
53  int x = analogRead(JOY_X), y = analogRead(JOY_Y);
54  // Geri dönüş yasak: kontrol şimdiki yöne göre / no reversing: check against the current direction
55  if      (x < 300 && dirX != 1)  { nextDX = -1; nextDY = 0; }
56  else if (x > 700 && dirX != -1) { nextDX = 1;  nextDY = 0; }
57  else if (y < 300 && dirY != 1)  { nextDX = 0;  nextDY = -1; }
58  else if (y > 700 && dirY != -1) { nextDX = 0;  nextDY = 1; }
59}
60
61void draw() {
62  matrix.clear();
63  matrix.setPixelColor(xyToIndex(foodX, foodY), matrix.Color(255, 0, 0));      // yem kırmızı / food red
64  for (int i = snakeLen - 1; i >= 0; i--) {
65    uint32_t c = (i == 0) ? matrix.Color(255, 200, 0) : matrix.Color(0, 160, 0); // baş sarı / head yellow
66    matrix.setPixelColor(xyToIndex(snakeX[i], snakeY[i]), c);
67  }
68  matrix.show();
69}
70
71void gameOver() {
72  alive = false;
73  uint16_t score = snakeLen - 3;     // yenen yem sayısı / food eaten
74  tone(BUZZER_PIN, 150, 400);
75  if (score > hiScore) { hiScore = score; EEPROM.put(HISCORE_ADDR, hiScore); }
76  for (byte k = 0; k < 3; k++) {     // kırmızı yanıp sön / flash red
77    matrix.fill(matrix.Color(80, 0, 0)); matrix.show(); delay(200);
78    matrix.clear();                      matrix.show(); delay(200);
79  }
80  for (uint16_t i = 0; i < score && i < W * H; i++) matrix.setPixelColor(i, matrix.Color(0, 0, 120));  // puan kadar LED / one LED per point
81  matrix.show();
82  Serial.print(F("Skor / Score: "));  Serial.print(score);
83  Serial.print(F("  Rekor / Best: ")); Serial.println(hiScore);
84}
85
86void step() {
87  dirX = nextDX; dirY = nextDY;
88  int nx = snakeX[0] + dirX, ny = snakeY[0] + dirY;
89  if (nx < 0 || nx >= W || ny < 0 || ny >= H) { gameOver(); return; }   // duvar / wall
90
91  bool eat = (nx == foodX && ny == foodY);
92  // Yem yoksa kuyruk ucu boşalacağı için ona çarpmak sayılmaz / without food the tail tip moves away, so it is not a hit
93  int checkLen = eat ? snakeLen : snakeLen - 1;
94  for (int i = 0; i < checkLen; i++) {
95    if (snakeX[i] == nx && snakeY[i] == ny) { gameOver(); return; }    // kendine çarptı / hit itself
96  }
97
98  if (eat) snakeLen++;
99  for (int i = snakeLen - 1; i > 0; i--) {      // gövdeyi bir hücre kaydır / shift the body one cell
100    snakeX[i] = snakeX[i - 1];
101    snakeY[i] = snakeY[i - 1];
102  }
103  snakeX[0] = nx; snakeY[0] = ny;
104
105  if (eat) {
106    tone(BUZZER_PIN, 1000, 40);
107    if (stepMs > 120) stepMs -= 10;             // her yemde hızlan / faster after every food
108    if (snakeLen == W * H) { gameOver(); return; }   // tahta doldu: kazandın / board full: you win
109    placeFood();
110  }
111}
112
113void setup() {
114  pinMode(JOY_SW, INPUT_PULLUP);
115  Serial.begin(9600);
116  matrix.begin();
117  matrix.setBrightness(30);          // düşük parlaklık = düşük akım / low brightness = low current
118  randomSeed(analogRead(A3));
119  EEPROM.get(HISCORE_ADDR, hiScore);
120  if (hiScore == 0xFFFF) hiScore = 0;
121  resetGame();
122  draw();
123}
124
125void loop() {
126  if (!alive) {                      // oyun bitti: joystick butonuyla yeniden / game over: restart with the joystick button
127    if (digitalRead(JOY_SW) == LOW) { resetGame(); draw(); delay(300); }
128    return;
129  }
130  readJoystick();                    // her turda oku, hiçbir hareketi kaçırma / read on every pass, never miss a move
131  if (millis() - lastStep >= stepMs) {
132    lastStep = millis();
133    step();
134    if (alive) draw();
135  }
136}

Há três regras críticas no código. Sem marcha a ré: se uma cobra que vai para a direita vira de repente para a esquerda, ela bate no próprio pescoço; readJoystick() compara a nova direção com a direção atual da cobra, e não com a que está pendente, então nem dois movimentos rápidos conseguem fazê-la dar meia-volta. Exceção da cauda: num passo sem comida, a ponta da cauda sai do lugar, então mover a cabeça para essa célula não é colisão. Posicionar a comida: placeFood() continua tentando até que a comida não caia sobre a cobra. Cada comida encurta o tempo do passo em 10 ms (de 300 ms até 120 ms); quando o jogo termina, um LED acende em azul para cada comida que você pegou, e o recorde vai para a EEPROM.

Também importa como os LEDs da sua matriz estão ligados. Na maioria das placas rígidas 8x8, os LEDs seguem linha por linha no mesmo sentido (SERPENTINE = false). Os painéis flexíveis 16x16 costumam ser ligados em zigue-zague: uma linha vai da esquerda para a direita e a seguinte, da direita para a esquerda. Se a cobra pular na diagonal ou andar para trás em linhas alternadas, defina SERPENTINE como true; se as direções saírem invertidas, gire o joystick ou troque os limites 300 e 700. Para 16x16, basta definir W e H como 16; os arrays da cobra passam a ocupar 512 bytes, o buffer do NeoPixel 768 bytes, e cerca de 75% da RAM do Uno é usada.

Matriz de LED NeoPixel 16x16
Matriz NeoPixel 16x16: um campo de jogo de 256 pixels significa uma cobra maior e um jogo mais difícil; precisa de uma fonte externa de 5 V.
Um único WS2812B consome cerca de 60 mA no branco máximo; os 256 LEDs de uma matriz 16x16 podem chegar a 15 A no pior caso, e nem o USB nem o pino 5V do Arduino conseguem fornecer isso. Alimente a matriz com uma fonte externa de 5 V e 3–4 A, ligue o GND da fonte ao GND do Arduino e coloque um resistor de 330 Ω na linha de dados e um capacitor de 1000 µF na entrada de alimentação. O setBrightness(30) no código reduz o brilho para cerca de 12%; como no Snake só alguns LEDs ficam acesos ao mesmo tempo, a corrente fica em poucas centenas de mA. Para uma opção de uma só cor que usa menos RAM, veja matriz de LED MAX7219.

Jogo da memória Simon: 4 LEDs e 4 botões

Simon (no Brasil, conhecido como Genius) é o lendário jogo da memória lançado em 1978: a cada rodada, o aparelho acrescenta uma cor à sequência e você repete a sequência inteira na mesma ordem. Componentes: quatro LEDs coloridos (verde, vermelho, amarelo, azul), um resistor de 220 Ω para cada um, quatro botões e um buzzer passivo. Ligue os LEDs no D9–D12, os botões no D2–D5 e o buzzer no D8; este jogo é um circuito separado do console comum. Você pode escolher o resistor certo para os seus LEDs com a calculadora Resistor para LED. Os quatro sons do Simon original são notas de um acorde de lá maior; o código usa as mesmas notas (330, 440, 554 e 659 Hz), então soa agradável mesmo quando você aperta rápido.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const byte LED_PINS[4]    = {9, 10, 11, 12};       // yeşil, kırmızı, sarı, mavi / green, red, yellow, blue
4const byte BUTTON_PINS[4] = {2, 3, 4, 5};          // LED'lerle aynı sırada / same order as the LEDs
5const int  TONES[4]       = {330, 440, 554, 659};  // E4, A4, C#5, E5: A majör akoru / A major chord
6const byte BUZZER_PIN     = 8;
7const int  HISCORE_ADDR   = 8;                     // EEPROM 8-9. bayt / EEPROM bytes 8-9
8const byte MAX_LEVEL      = 50;
9
10byte sequence[MAX_LEVEL];
11byte level = 0;
12uint16_t hiScore;
13
14void playColor(byte c, int ms) {
15  digitalWrite(LED_PINS[c], HIGH);
16  tone(BUZZER_PIN, TONES[c]);
17  delay(ms);
18  digitalWrite(LED_PINS[c], LOW);
19  noTone(BUZZER_PIN);
20}
21
22// Basılan rengi döndürür; 3 sn içinde basılmazsa -1 / returns the pressed colour; -1 after 3 s
23int waitForButton() {
24  unsigned long start = millis();
25  while (millis() - start < 3000) {
26    for (byte i = 0; i < 4; i++) {
27      if (digitalRead(BUTTON_PINS[i]) == LOW) {
28        playColor(i, 200);                              // basılan rengi göster / show the pressed colour
29        while (digitalRead(BUTTON_PINS[i]) == LOW);     // bırakılmasını bekle / wait for release
30        delay(30);                                      // sıçramayı atla / skip the bounce
31        return i;
32      }
33    }
34  }
35  return -1;
36}
37
38// LED'ler döner; ilk basış oyunu başlatır ve rastgeleliği tohumlar
39// LEDs chase until the first press, which starts the game and seeds the randomness
40void waitForStart() {
41  byte i = 0;
42  while (true) {
43    digitalWrite(LED_PINS[i], HIGH);
44    for (byte t = 0; t < 20; t++) {                     // 200 ms boyunca butonlara bak / poll the buttons for 200 ms
45      for (byte b = 0; b < 4; b++) {
46        if (digitalRead(BUTTON_PINS[b]) == LOW) {
47          digitalWrite(LED_PINS[i], LOW);
48          randomSeed(micros());                         // basış anı her seferinde farklı / the press time always differs
49          while (digitalRead(BUTTON_PINS[b]) == LOW);
50          delay(600);
51          return;
52        }
53      }
54      delay(10);
55    }
56    digitalWrite(LED_PINS[i], LOW);
57    i = (i + 1) % 4;
58  }
59}
60
61void endGame(uint16_t score) {
62  tone(BUZZER_PIN, 110, 700);
63  for (byte k = 0; k < 3; k++) {                        // tüm LED'ler yanıp söner / all LEDs flash
64    for (byte i = 0; i < 4; i++) digitalWrite(LED_PINS[i], HIGH);
65    delay(200);
66    for (byte i = 0; i < 4; i++) digitalWrite(LED_PINS[i], LOW);
67    delay(200);
68  }
69  if (score > hiScore) { hiScore = score; EEPROM.put(HISCORE_ADDR, hiScore); }
70  Serial.print(F("Seviye / Level: "));  Serial.print(score);
71  Serial.print(F("  Rekor / Best: ")); Serial.println(hiScore);
72  level = 0;
73  delay(1000);
74}
75
76void setup() {
77  for (byte i = 0; i < 4; i++) {
78    pinMode(LED_PINS[i], OUTPUT);
79    pinMode(BUTTON_PINS[i], INPUT_PULLUP);              // basınca LOW / LOW when pressed
80  }
81  Serial.begin(9600);
82  EEPROM.get(HISCORE_ADDR, hiScore);
83  if (hiScore == 0xFFFF) hiScore = 0;
84}
85
86void loop() {
87  if (level == 0) waitForStart();
88  if (level == MAX_LEVEL) { endGame(MAX_LEVEL); return; }   // hepsini bildin / you completed every level
89
90  sequence[level++] = random(4);                       // diziye yeni bir renk ekle / append a new colour
91  int showMs = max(180, 520 - level * 20);             // seviye arttıkça hızlanır / faster at higher levels
92  for (byte i = 0; i < level; i++) {                   // diziyi baştan göster / replay the whole sequence
93    playColor(sequence[i], showMs);
94    delay(showMs / 2);
95  }
96  for (byte i = 0; i < level; i++) {                   // oyuncu aynı sırayı tekrarlar / the player repeats it
97    if (waitForButton() != sequence[i]) {              // yanlış ya da geç / wrong or too late
98      endGame(level - 1);
99      return;
100    }
101  }
102  delay(700);                                          // tur arası / pause between rounds
103}

No Simon, a aleatoriedade é tudo: se a sequência for igual em toda partida, o jogador simplesmente a decora. O método randomSeed(analogRead(A0)), que lê um pino desconectado, é fraco na prática, porque o pino muitas vezes lê valores parecidos. Este código usa um truque mais robusto: o jogo espera o primeiro toque do jogador e usa o valor de micros() nesse momento como semente. Ninguém consegue repetir o momento de um toque com precisão de microssegundos, então cada partida começa diferente. O tempo de exibição fica 20 ms mais curto a cada nível e se estabiliza em 180 ms; se você não apertar em 3 segundos ou apertar a cor errada, o jogo termina, e o nível alcançado é gravado na EEPROM se for recorde.

Jogo de corrida no LCD: caracteres personalizados num display 16x2

Não dá para desenhar pixels num LCD de caracteres 16x2, mas com createChar() você pode definir até 8 caracteres personalizados de 5×8 pixels cada. Três bastam para o jogo de corrida: duas poses de corrida e um cacto. A linha de baixo do display é o chão; os cactos passam da direita para a esquerda e, quando você aperta o botão, o corredor sobe para a linha de cima e fica no ar por alguns passos. Como este jogo é em grade, a verificação de colisão é uma linha só: se houver um cacto na coluna do corredor enquanto ele está no chão, o jogo acaba. Para a biblioteca e as ligações, veja display LCD I2C.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <EEPROM.h>
3#include <LiquidCrystal_I2C.h>
4
5LiquidCrystal_I2C lcd(0x27, 16, 2);  // adres 0x3F de olabilir / the address may also be 0x3F
6
7const byte JUMP_PIN = 3, BUZZER_PIN = 8;
8const int  HISCORE_ADDR = 4;         // EEPROM 4-5. bayt / EEPROM bytes 4-5
9const byte PLAYER_COL = 1;           // koşucunun sütunu / runner's column
10const byte JUMP_STEPS = 4;           // zıplama süresi (adım) / jump length (steps)
11const byte MIN_GAP = 4;              // kaktüsler arası en az boşluk / minimum gap between cacti
12
13// 5x8 özel karakterler / 5x8 custom characters
14byte runA[8]   = {0b01110, 0b01110, 0b00100, 0b01110, 0b10101, 0b00100, 0b01010, 0b10001};
15byte runB[8]   = {0b01110, 0b01110, 0b00100, 0b01110, 0b10101, 0b00100, 0b01010, 0b01010};
16byte cactus[8] = {0b00100, 0b00101, 0b10101, 0b10101, 0b10110, 0b01100, 0b00100, 0b00100};
17
18byte ground[16];                     // 1 = o sütunda kaktüs var / 1 = cactus in that column
19byte gap = 0, jumpLeft = 0;
20uint16_t score = 0, hiScore = 0;
21unsigned int stepMs = 300;
22unsigned long lastStep = 0;
23bool alive = false;
24
25// Sıçramasız basış: yalnız basıldığı an true / bounce-free: true only at the moment of a press
26bool buttonPressed() {
27  static bool stable = HIGH, lastRead = HIGH;
28  static unsigned long changedAt = 0;
29  bool r = digitalRead(JUMP_PIN);
30  if (r != lastRead) { lastRead = r; changedAt = millis(); }
31  if (millis() - changedAt > 15 && r != stable) {
32    stable = r;
33    return stable == LOW;
34  }
35  return false;
36}
37
38void draw() {
39  byte row = jumpLeft > 0 ? 0 : 1;   // havadaysa üst satır / top row while jumping
40  lcd.setCursor(0, 0);
41  for (byte c = 0; c < 11; c++) lcd.write((uint8_t)(row == 0 && c == PLAYER_COL ? 0 : ' '));
42  char buf[6];
43  sprintf(buf, "%5u", score);        // sağa yaslı skor / right-aligned score
44  lcd.print(buf);
45  lcd.setCursor(0, 1);
46  for (byte c = 0; c < 16; c++) {
47    if (row == 1 && c == PLAYER_COL) lcd.write((uint8_t)(score & 1));   // runA / runB sırayla / alternately
48    else lcd.write((uint8_t)(ground[c] ? 2 : ' '));
49  }
50}
51
52void startGame() {
53  memset(ground, 0, sizeof(ground));
54  gap = 0; jumpLeft = 0; score = 0; stepMs = 300;
55  alive = true;
56  lastStep = millis();
57  draw();
58}
59
60void die() {
61  alive = false;
62  tone(BUZZER_PIN, 150, 400);
63  if (score > hiScore) { hiScore = score; EEPROM.put(HISCORE_ADDR, hiScore); }
64  lcd.clear();
65  lcd.print(F("GAME OVER ")); lcd.print(score);
66  lcd.setCursor(0, 1);
67  lcd.print(F("BEST ")); lcd.print(hiScore);
68}
69
70void setup() {
71  pinMode(JUMP_PIN, INPUT_PULLUP);
72  lcd.init();
73  lcd.backlight();
74  lcd.createChar(0, runA);
75  lcd.createChar(1, runB);
76  lcd.createChar(2, cactus);
77  EEPROM.get(HISCORE_ADDR, hiScore);
78  if (hiScore == 0xFFFF) hiScore = 0;
79  randomSeed(analogRead(A3));
80  lcd.setCursor(0, 0); lcd.print(F("LCD RUNNER"));
81  lcd.setCursor(0, 1); lcd.print(F("BEST ")); lcd.print(hiScore);
82}
83
84void loop() {
85  if (buttonPressed()) {
86    if (!alive) startGame();                       // başlat / start
87    else if (jumpLeft == 0) {                      // yerdeyse zıpla / jump only from the ground
88      jumpLeft = JUMP_STEPS;
89      tone(BUZZER_PIN, 1200, 20);
90      draw();                                      // zıplamayı hemen göster / show the jump at once
91    }
92  }
93  if (!alive || millis() - lastStep < stepMs) return;
94  lastStep = millis();
95
96  for (byte c = 0; c < 15; c++) ground[c] = ground[c + 1];   // dünya sola kayar / the world scrolls left
97  if (gap >= MIN_GAP && random(100) < 35) { ground[15] = 1; gap = 0; }
98  else { ground[15] = 0; gap++; }
99
100  if (jumpLeft > 0) jumpLeft--;                              // havada geçen bir adım / one step in the air
101  if (jumpLeft == 0 && ground[PLAYER_COL]) { die(); return; } // yerdeyken kaktüse çarptı / hit a cactus on the ground
102
103  score++;
104  if (score % 25 == 0 && stepMs > 120) stepMs -= 15;         // giderek hızlan / speed up gradually
105  draw();
106}

Rolar o mundo significa copiar cada elemento do array do chão uma posição para a esquerda; a coluna mais à direita recebe um novo cacto com 35% de chance. A regra MIN_GAP = 4 deixa pelo menos quatro colunas vazias entre dois cactos, para que todo obstáculo possa ser superado. A cada 25 pontos, o tempo do passo cai 15 ms, de 300 ms até 120 ms. Reescrever as duas linhas a cada passo, em vez de apagar o display com lcd.clear(), evita a cintilação; só o comando clear() leva cerca de 2 ms. Se você vê só blocos na tela, gire o trimpot de contraste atrás do módulo; se não vê nada, mude o endereço de 0x27 para 0x3F.

Teste de reação: meça seu tempo de reação em milissegundos

Este é o jogo mais simples e o mais viciante: um LED acende num momento aleatório, você aperta o botão o mais rápido que puder, e o Arduino mostra o tempo decorrido em milissegundos no Monitor Serial. Você só precisa de um LED no D7 com um resistor de 220 Ω, o botão no D3 e um buzzer opcional. O tempo de espera é aleatório, entre 2 e 5 segundos; se você apertar durante a espera, recebe um aviso de “cedo demais”, então não dá para trapacear chutando. O tempo é medido com micros(), sua média é calculada e seu melhor tempo é gravado na EEPROM.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const byte LED_PIN = 7, BUTTON_PIN = 3, BUZZER_PIN = 8;
4const int  BEST_ADDR = 6;            // EEPROM 6-7. bayt / EEPROM bytes 6-7
5const bool USE_SOUND = false;        // true: LED ile birlikte bip / true: beep together with the LED
6
7uint16_t best;                       // en iyi süre (ms); 0xFFFF = kayıt yok / best time (ms); 0xFFFF = none
8unsigned long total = 0;
9unsigned int rounds = 0;
10
11bool pressed() { return digitalRead(BUTTON_PIN) == LOW; }
12
13void waitRelease() {
14  while (pressed());
15  delay(50);                         // bırakma sıçramasını atla / skip the release bounce
16}
17
18void setup() {
19  pinMode(LED_PIN, OUTPUT);
20  pinMode(BUTTON_PIN, INPUT_PULLUP);
21  Serial.begin(9600);
22  EEPROM.get(BEST_ADDR, best);       // boş EEPROM 0xFFFF = kayıt yok / blank EEPROM 0xFFFF = no record
23  Serial.println(F("Baslamak icin butona bas / Press the button to start"));
24}
25
26void loop() {
27  while (!pressed());                // başlatma basışı / start press
28  randomSeed(micros());
29  waitRelease();
30  Serial.println(F("Hazir ol... / Get ready..."));
31
32  unsigned long waitMs = random(2000, 5001);   // 2-5 sn rastgele bekleme / random 2-5 s wait
33  unsigned long start = millis();
34  while (millis() - start < waitMs) {
35    if (pressed()) {                 // erken basış / false start
36      tone(BUZZER_PIN, 150, 500);
37      Serial.println(F("Erken bastin! / Too early!"));
38      waitRelease();
39      return;
40    }
41  }
42
43  unsigned long t0 = micros();
44  digitalWrite(LED_PIN, HIGH);       // işaret / signal
45  if (USE_SOUND) tone(BUZZER_PIN, 2000, 50);
46  while (!pressed() && micros() - t0 < 2000000UL);   // en fazla 2 sn bekle / wait at most 2 s
47  unsigned long ms = (micros() - t0) / 1000;
48  digitalWrite(LED_PIN, LOW);
49
50  if (ms >= 2000) {
51    Serial.println(F("Cok yavas / Too slow"));
52    return;
53  }
54  total += ms; rounds++;
55  Serial.print(F("Sure / Time: ")); Serial.print(ms);
56  Serial.print(F(" ms   Ortalama / Average: ")); Serial.print(total / rounds);
57  Serial.println(F(" ms"));
58  if (ms < best) {                   // daha kısa süre daha iyi / shorter is better
59    best = ms;
60    EEPROM.put(BEST_ADDR, best);
61    tone(BUZZER_PIN, 1500, 150);
62    Serial.println(F("Yeni rekor! / New record!"));
63  }
64  Serial.print(F("Rekor / Best: ")); Serial.print(best); Serial.println(F(" ms"));
65  waitRelease();
66  delay(1000);
67}

O tempo médio de reação humana à luz é de cerca de 200–250 ms; um jogador bem concentrado consegue ficar um pouco abaixo disso. Se você definir USE_SOUND como true, um bipe toca junto com o LED. As reações ao som costumam ser um pouco mais rápidas do que à luz, então comparar os recordes dos dois modos rende um experimento interessante. Mostre o tempo num display de 7 segmentos ou num OLED em vez do Monitor Serial e você terá um aparelho a pilha que funciona sem computador.

Como fazer jogos com o ESP32

A maior parte do código deste guia roda no ESP32 com pequenas mudanças: as bibliotecas Adafruit SSD1306 e NeoPixel são compatíveis com o ESP32, e tone() está disponível nas versões 2.x e 3.x do core Arduino do ESP32. Três coisas precisam mudar: os pinos (a coluna ESP32 da tabela comum), os limites do joystick e a forma de gravar o recorde. O ADC do ESP32 é de 12 bits e retorna valores de 0 a 4095, então use cerca de 1200 e 2800 em vez de 300 e 700. Ligue o joystick nos pinos do ADC1, GPIO 32–39, porque os pinos do ADC2 não podem ser lidos com o Wi-Fi ligado.

O ESP32 não tem uma EEPROM de verdade; a biblioteca EEPROM a emula na flash e exige as chamadas EEPROM.begin() e EEPROM.commit(). O caminho mais limpo é a biblioteca Preferences: ela grava valores sob um namespace e um nome de chave na partição NVS da flash e distribui o desgaste sozinha. O jogo “corrida de toques” abaixo conta quantas vezes você consegue apertar o botão em 5 segundos e guarda o recorde com Preferences:

C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4#include <Preferences.h>
5
6Adafruit_SSD1306 display(128, 64, &Wire, -1);   // SDA = GPIO 21, SCL = GPIO 22
7Preferences prefs;
8
9const int BUTTON_PIN = 26;           // aksiyon butonu / action button
10const int BUZZER_PIN = 27;           // pasif buzzer / passive buzzer
11const unsigned long ROUND_MS = 5000; // 5 saniyelik tur / 5-second round
12
13uint32_t best = 0;
14
15// Sıçramasız basış: yalnız basıldığı an true / bounce-free: true only at the moment of a press
16bool buttonPressed() {
17  static bool stable = HIGH, lastRead = HIGH;
18  static unsigned long changedAt = 0;
19  bool r = digitalRead(BUTTON_PIN);
20  if (r != lastRead) { lastRead = r; changedAt = millis(); }
21  if (millis() - changedAt > 10 && r != stable) {
22    stable = r;
23    return stable == LOW;
24  }
25  return false;
26}
27
28void screen(const char *title, uint32_t big, uint32_t small) {
29  display.clearDisplay();
30  display.setTextSize(1);
31  display.setCursor(0, 0);   display.print(title);
32  display.setCursor(0, 56);  display.print("BEST "); display.print(best);
33  display.setTextSize(3);
34  display.setCursor(0, 20);  display.print(big);
35  display.setTextSize(1);
36  display.setCursor(90, 56); display.print(small);
37  display.display();
38}
39
40void setup() {
41  pinMode(BUTTON_PIN, INPUT_PULLUP);
42  Serial.begin(115200);
43  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
44    Serial.println("SSD1306 allocation failed");
45    for (;;) delay(1000);
46  }
47  display.setTextColor(SSD1306_WHITE);
48  prefs.begin("oyun", false);        // "oyun" ad alanı, okuma-yazma / "oyun" namespace, read-write
49  best = prefs.getUInt("rekor", 0);  // kayıt yoksa 0 / 0 when nothing is stored
50}
51
52void loop() {
53  screen("TAP RACE: PRESS", 0, 0);
54  while (!buttonPressed()) delay(1); // başlatma / start
55  for (int i = 3; i > 0; i--) {      // 3-2-1 geri sayım / 3-2-1 countdown
56    screen("GET READY", i, 0);
57    tone(BUZZER_PIN, 800, 80);
58    delay(1000);
59  }
60  tone(BUZZER_PIN, 1600, 150);
61
62  uint32_t taps = 0;
63  unsigned long start = millis(), lastDraw = 0;
64  while (millis() - start < ROUND_MS) {
65    if (buttonPressed()) taps++;
66    if (millis() - lastDraw > 100) { // ekranı 10 kez/sn çiz / redraw 10 times per second
67      lastDraw = millis();
68      screen("TAP!", taps, (ROUND_MS - (millis() - start)) / 1000 + 1);
69    }
70  }
71
72  if (taps > best) {                 // yeni rekor flash'a / new record to flash
73    best = taps;
74    prefs.putUInt("rekor", best);
75    tone(BUZZER_PIN, 2000, 300);
76  }
77  screen("RESULT", taps, 0);
78  Serial.printf("Basis / Taps: %lu  Rekor / Best: %lu\n", (unsigned long)taps, (unsigned long)best);
79  delay(3000);
80}

prefs.getUInt("rekor", 0) retorna o segundo parâmetro se não houver nada gravado, então você nunca precisa lidar com o problema do 0xFFFF da EEPROM. Os nomes de namespace e de chave podem ter no máximo 15 caracteres. Embora o processador do ESP32 seja muitas vezes mais rápido que o do Uno, o OLED usa o mesmo barramento I2C, então a taxa de quadros quase não muda; para jogos bem mais fluidos e coloridos, você pode usar um display TFT ILI9341 com SPI. Já o Bluetooth do ESP32 é um bom ponto de partida para transformar o celular num controle de jogo.

Jogos com o Raspberry Pi Pico e MicroPython

Se você programa o Raspberry Pi Pico em MicroPython, a lógica do jogo é a mesma e só a sintaxe muda: time.ticks_ms() e time.ticks_diff() em vez de millis(), read_u16(), que retorna 0–65535, em vez de analogRead(), e PWM em vez de tone(). O Pico não tem EEPROM, mas graças ao sistema de arquivos do MicroPython é muito fácil gravar o recorde num pequeno arquivo de texto na flash. O código abaixo é a versão para Pico do teste de reação e guarda o recorde no arquivo rekor.txt; salve-o na placa como main.py no Thonny e ele inicia sozinho sempre que a placa é ligada. Se você é novo no MicroPython, leia primeiro O que é o Raspberry Pi Pico?.

MicroPython
1from machine import Pin, PWM
2import time, random
3
4led = Pin("LED", Pin.OUT)                 # kart üstündeki LED / onboard LED
5button = Pin(14, Pin.IN, Pin.PULL_UP)     # GP14, basınca 0 / 0 when pressed
6buzzer = PWM(Pin(16))                     # GP16, pasif buzzer / passive buzzer
7buzzer.duty_u16(0)                        # başlangıçta sessiz / silent at start
8FILE = "rekor.txt"                        # rekor flash'ta dosyada / record kept in a file on flash
9
10def beep(freq, ms):
11    buzzer.freq(freq)
12    buzzer.duty_u16(32768)                # %50 görev oranı / 50% duty cycle
13    time.sleep_ms(ms)
14    buzzer.duty_u16(0)
15
16def load_best():
17    try:
18        with open(FILE) as f:
19            return int(f.read())
20    except (OSError, ValueError):         # dosya yok ya da bozuk / file missing or corrupt
21        return 99999
22
23def save_best(ms):
24    with open(FILE, "w") as f:
25        f.write(str(ms))
26
27def wait_release():
28    while button.value() == 0:
29        time.sleep_ms(5)
30    time.sleep_ms(50)                     # bırakma sıçraması / release bounce
31
32best = load_best()
33print("Baslamak icin butona bas / Press the button to start")
34
35while True:
36    while button.value() == 1:            # başlatma basışı / start press
37        time.sleep_ms(5)
38    wait_release()
39    print("Hazir ol... / Get ready...")
40
41    wait = random.randint(2000, 5000)     # 2-5 sn rastgele bekleme / random 2-5 s wait
42    start = time.ticks_ms()
43    early = False
44    while time.ticks_diff(time.ticks_ms(), start) < wait:
45        if button.value() == 0:
46            early = True
47            break
48    if early:
49        print("Erken bastin! / Too early!")
50        beep(150, 400)
51        wait_release()
52        continue
53
54    t0 = time.ticks_us()
55    led.on()                              # işaret / signal
56    while button.value() == 1 and time.ticks_diff(time.ticks_us(), t0) < 2000000:
57        pass
58    ms = time.ticks_diff(time.ticks_us(), t0) // 1000
59    led.off()
60
61    if ms >= 2000:
62        print("Cok yavas / Too slow")
63        continue
64    print("Sure / Time:", ms, "ms")
65    if ms < best:                         # daha kısa süre daha iyi / shorter is better
66        best = ms
67        save_best(best)
68        print("Yeni rekor! / New record!")
69        beep(1500, 150)
70    print("Rekor / Best:", best, "ms")
71    wait_release()
72    time.sleep(1)

Usar ticks_diff() na medição de tempo é importante: os contadores dão a volta depois de um certo valor, e simplesmente subtrair dois valores dá um resultado errado nesse momento; ticks_diff() trata essa volta corretamente. Para jogos com OLED, o driver ssd1306 que você instala pelo gerenciador de pacotes do Thonny oferece os comandos fill_rect(), text() e show(), parecidos com os da Adafruit GFX. O módulo neopixel, necessário para uma matriz NeoPixel, já vem com o MicroPython. Assim, você pode levar o Pong e o Snake para o Pico com a mesma lógica.

Erros comuns e como corrigir

SintomaCausaSolução
Tela em brancoSe o Monitor Serial mostra “SSD1306 allocation failed”, a RAM acabou; se não, o endereço ou as ligações estão erradosColoque os textos fixos dentro de F() e reduza os arrays grandes; sem a mensagem, verifique os fios SDA/SCL e tente o endereço 0x3D
O jogo começa sempre igualFalta randomSeed() ou a semente é sempre a mesmaUse como semente o valor de micros() no primeiro toque do jogador
Um toque faz pular duas vezesRepique do botãoUse a função buttonPressed(), que espera 15 ms de estabilidade
O recorde mostra 65535Uma EEPROM nunca gravada lê 0xFFTrate 0xFFFF como 0
O personagem se move sozinho com o joystick soltoO centro não é 512 e a zona morta é estreita demaisMantenha os limites 300/700 ou meça o centro na inicialização e calibre
A cobra pula na diagonalA matriz está ligada em zigue-zagueDefina SERPENTINE = true
O Arduino reinicia quando o brilho aumentaA matriz de LED consome corrente demaisReduza o setBrightness() e use uma fonte externa de 5 V
O PWM no pino 3 ou 11 falha quando tone() tocaNo Uno, tone() usa o Timer2Passe o componente que precisa de PWM para o pino 5, 6, 9 ou 10
O jogo fica lento com o tempoSerial.print ou desenhos desnecessários a cada quadroRemova a saída serial e mantenha o tempo de quadro acima do tempo de transferência do display
O LCD só mostra blocosAjuste de contraste ou endereço erradoGire o trimpot atrás do módulo e tente o endereço 0x27 ou 0x3F
Se um jogo não se comporta como você espera, teste primeiro a lógica sem o display: mostre a posição, a velocidade e o estado no Monitor Serial a cada quadro e acompanhe alguns quadros à mão. Depois de encontrar o bug, apague essas linhas, porque o Serial.print custa alguns milissegundos por quadro e desregula a velocidade do jogo. Se o código nem carrega na placa, veja o guia erro ao carregar código no Arduino.

Ideias para levar seus jogos mais longe

  • Pong para dois jogadores: apague as linhas da CPU, controle a segunda raquete com um potenciômetro no A2 e leve os dois controles para pontas opostas da mesa com fios compridos.
  • Controle por inclinação: use um MPU6050 em vez do joystick e guie a cobra ou a raquete inclinando a placa.
  • Música e efeitos sonoros: toque a música do jogo com um DFPlayer Mini; para efeitos curtos, um buzzer passivo basta.
  • Jogue pelo celular: transforme o celular num controle sem fio com um módulo Bluetooth HC-05.
  • Um gamepad para o computador: como o Arduino Leonardo pode agir como teclado USB, você pode montar um controle para jogos de PC com um joystick e botões.
  • Placar: mostre os recordes como texto rolante num módulo MAX7219 4 em 1.
  • Jogos de sorte: monte uma roleta com um anel WS2812B ou um dado eletrônico com LEDs; o jeito mais divertido de praticar random().

Fazer jogos com Arduino é o jeito mais gostoso de aprender código e hardware ao mesmo tempo. Depois de entender o loop do jogo, a detecção de colisão e o recorde na EEPROM, criar o seu próprio jogo é só questão de ter uma ideia; para mais inspiração, veja a lista de ideias de projetos Arduino. No app BlueGrays, você encontra Pong, o T-Rex no OLED, o Snake em matriz 8x8 e 16x16, o Simon, a corrida no LCD e o teste de reação como projetos passo a passo, com esquemas do circuito e código pronto para usar.

Perguntas frequentes

Que jogos dá para fazer com um Arduino?

Com um Arduino Uno, você pode fazer Pong, a corrida do T-Rex, jogos no estilo Flappy Bird, Snake, jogos simples de blocos, o jogo da memória Simon, um teste de reação e jogos de sorte, como dado ou roleta. Um OLED de 128×64 basta para jogos com gráficos, e uma matriz de LED NeoPixel para jogos coloridos em grade. Jogos como o Simon e o teste de reação são jogados só com LEDs e botões, sem nenhum display.

Qual é o melhor display para um jogo com Arduino?

Para iniciantes, a melhor escolha é o OLED SSD1306 de 0,96 polegada: é barato, liga com apenas dois fios de dados e desenha gráficos em movimento em 128×64 pixels. Se você quer uma imagem colorida maior, um display TFT ILI9341 ou ST7789 serve, e para jogos de pixel art, uma matriz de LED 8x8 ou 16x16. Um LCD 16x2 com caracteres personalizados também basta para jogos de corrida simples.

Como gravar um recorde no Arduino?

Se você gravar o recorde na EEPROM, ele não se perde quando a alimentação é desligada. Você grava com EEPROM.put(endereço, pontuação) e lê com EEPROM.get(endereço, pontuação); o Arduino Uno tem 1024 bytes para isso. Como cada célula pode ser gravada cerca de 100.000 vezes, grave só quando o jogo terminar e o recorde tiver sido batido.

Dá para fazer um gamepad com Arduino?

Sim. Placas com o processador ATmega32U4, como o Arduino Leonardo, o Micro ou o Pro Micro, podem aparecer para o computador como teclado, mouse ou controle de jogo USB; elas leem o joystick e os botões e enviam as teclas pressionadas. O Arduino Uno não consegue fazer isso diretamente, mas pode enviar os valores do joystick pela porta serial para um programa no computador.

Por que meu jogo com Arduino está lento ou travando?

As causas mais comuns são loops feitos com delay(), chamadas de Serial.print a cada quadro e o tempo de transferência do display. No Uno, enviar a tela inteira do OLED leva cerca de 25 ms, então um tempo de quadro de 30 ms é realista. Use um tempo de quadro fixo com millis() em vez de delay(), remova a saída serial e desenhe só o necessário.

Qual é o jogo com Arduino mais fácil para iniciantes?

O teste de reação é o mais fácil: precisa de um LED, um botão e um sketch curto, e mesmo assim ensina ideias centrais como controle de tempo, números aleatórios e EEPROM. Depois, passar para o Simon com quatro LEDs e quatro botões e, em seguida, para o Pong e o T-Rex num display OLED é um bom caminho de aprendizado.

Ver todos os projetos
Jogo de bola e raquetes - SSD1306
#86Intermediário
Premium

Jogo de bola e raquetes - SSD1306

Uma bola, duas raquetes e você: jogue tênis de mesa, um dos primeiros videogames, em um pequeno display OLED. Dois botões movem sua raquete à direita para cima e para baixo, enquanto o computador cont

4.5(47)
65
Corrida do T-Rex no OLED: jogo do dinossauro com Arduino
#87Avançado
Premium

Corrida do T-Rex no OLED: jogo do dinossauro com Arduino

O jogo do dinossauro que aparece quando a internet cai, agora na sua mesa: o dinossauro corre pelo pequeno display OLED, os cactos vêm na sua direção e você pula com um único botão. Sua pontuação aume

4.7(33)
53
Cobrinha em pixels: matriz 8x8
#88Avançado
Premium

Cobrinha em pixels: matriz 8x8

Monte o clássico jogo da cobrinha com as próprias mãos e jogue. Em uma matriz de luzes coloridas 8x8, você guia uma cobra verde com um joystick e come maçãs vermelhas; a cada maçã, a cobra cresce, ace

4.7(39)
57
Cobrinha na matriz 16x16
#89Avançado
Premium

Cobrinha na matriz 16x16

O clássico jogo da cobrinha, desta vez em um enorme display LED com 256 luzes. Você guia a cobra verde com um joystick e coleta maçãs vermelhas; a cada maçã, a cobra cresce, sua pontuação aumenta em u

4.8(52)
67
Jogo da memória com Arduino (4 LEDs + 4 botões + buzzer)
#93Avançado
Premium

Jogo da memória com Arduino (4 LEDs + 4 botões + buzzer)

Você confia na sua memória? Quatro LEDs coloridos acendem em sequência, cada um tocando sua própria nota, e você repete a mesma ordem apertando os botões. A cada rodada correta, uma nova cor é adicion

4.8(46)
69
Jogo de corrida no LCD com Arduino
#98Intermediário
Premium

Jogo de corrida no LCD com Arduino

Jogo de corrida infinita com rolagem lateral em um display LCD de 16x2 caracteres. O herói corre automaticamente, e o jogador pressiona o botão para pular os obstáculos. Obstáculos aleatórios aparecem nas linhas superior e inferior. O buzzer passivo toca a melodia de abertura, o som de pulo, o som de comemoração a cada 50 pontos e a melodia de fim de jogo. A tela de abertura "BlueGrays - Arduino Projects" aparece ao iniciar. A pontuação é atualizada em tempo real no canto superior direito.

4.6(32)
55
Teste de reação com Arduino e aviso sonoro: meça seus reflexos em milissegundos (LCD + buzzer + 3 LEDs)
#97Intermediário
Premium

Teste de reação com Arduino e aviso sonoro: meça seus reflexos em milissegundos (LCD + buzzer + 3 LEDs)

Seus reflexos são rápidos? Aperte o botão: o LED vermelho diz preparar, o amarelo diz apontar e começa uma espera aleatória de 1,5 a 3,5 segundos. Assim que o LED verde acende e o buzzer apita, aperte de novo; seu tempo de reação aparece em milissegundos no LCD I2C, com seu recorde da sessão e sua média embaixo. Apertou antes do verde? A tela mostra TOO EARLY! e toca um som de derrota. Segure o botão para um novo jogador com a mesma ordem de esperas.

4.6(33)
50