Guías · 21 min de lectura

Juegos con Arduino: Pong, T-Rex, Snake y Simon paso a paso

Cómo hacer juegos con Arduino: Pong y T-Rex en OLED, Snake en matriz LED, Simon, un runner en LCD y un test de reacción, con bucle de juego y récord en EEPROM.

Hacer juegos con Arduino es la forma más divertida de aprender electrónica y programación: con una pantalla OLED, unos pocos pulsadores y un joystick puedes construir tú mismo clásicos como Pong, el T-Rex runner, Snake y el juego de memoria Simon, y retar a tus amigos a batir tu récord. Mientras programas un juego aprendes casi sin darte cuenta los temas más importantes de la programación: temporización, máquinas de estados, sistemas de coordenadas, cálculo de colisiones, números aleatorios y memoria permanente. Todos estos juegos funcionan en un solo Arduino Uno; sus 2 KB de RAM bastan si usas la memoria con cabeza.

En esta guía construimos seis juegos de principio a fin: Pong y el T-Rex runner en una pantalla OLED, Snake en una matriz LED NeoPixel de 8x8 y de 16x16, Simon con cuatro LED y cuatro pulsadores, un juego de carreras en una LCD 16x2 y un test de reacción con precisión de milisegundos. Cada juego incluye lista de componentes, conexiones y código que compila. Primero aprenderás una vez el esqueleto que todos comparten, es decir, el bucle de juego, la detección de colisiones, la puntuación y el récord guardado en la EEPROM, y luego lo reutilizarás en cada juego. Al final también encontrarás versiones para ESP32 y Raspberry Pi Pico.

Juego Snake con Arduino en una matriz LED NeoPixel 8x8
Matriz LED NeoPixel WS2812B de 8x8: 64 píxeles de color y un solo cable de datos, una minipantalla ideal para Snake.

¿Qué componentes necesita cada juego?

Cuando haces un juego con Arduino, la primera decisión es la pantalla, porque determina qué tipo de juego puedes crear. Con solo unos LED ya puedes hacer Simon o un test de reacción, mientras que una OLED de 128×64 píxeles hace posibles Pong y T-Rex, con gráficos en movimiento y física. La tabla resume los juegos de esta guía de más fácil a más difícil:

JuegoPantallaEntradaOtros componentesDificultad
Test de reacción1 LED + monitor serie1 pulsadorResistencia de 220 Ω, buzzer opcionalFácil
Juego de memoria Simon4 LED de colores4 pulsadores4 resistencias de 220 Ω, buzzer pasivoFácil–media
Runner en LCDLCD 16x2 I2C1 pulsadorBuzzer pasivoMedia
PongOLED SSD1306 de 0,96"Joystick o potenciómetro de 10 kΩBuzzer pasivoMedia
T-Rex runnerOLED SSD1306 de 0,96"1 pulsador o el botón del joystickBuzzer pasivoMedia
Snake (8x8)Matriz NeoPixel 8x8JoystickResistencia de 330 Ω, condensador de 1000 µFMedia–avanzada
Snake (16x16)Matriz NeoPixel 16x16JoystickFuente externa de 5 V y 3–4 AAvanzada
Arduino Uno
Arduino Uno
OLED SSD1306 0.96"
OLED SSD1306 0.96"
Joystick
Joystick
LCD 16x2 I2C
LCD 16x2 I2C
Buzzer
Buzzer
Push button
Push button

Conexión común: un solo circuito como consola de juegos

Elegimos pines comunes para que todos los juegos excepto Simon funcionen en el mismo circuito. La OLED y la LCD I2C comparten los mismos dos cables (SDA y SCL); el joystick va a las entradas analógicas, el pulsador de acción a D3 y el buzzer a D8. Así, una vez montada esta "consola", cambias de juego simplemente cargando otro código. Puedes ver el mapa completo de pines de tu placa en la herramienta Pinout.

Componente / pinArduino UnoESP32Raspberry Pi Pico
OLED o LCD I2C: SDAA4GPIO 21GP4
OLED o LCD I2C: SCLA5GPIO 22GP5
Joystick VRx (eje X)A0GPIO 34GP26
Joystick VRy (eje Y)A1GPIO 35GP27
Joystick SW (botón)D2GPIO 25GP15
Pulsador de acción (la otra pata a GND)D3GPIO 26GP14
Buzzer pasivo (+)D8GPIO 27GP16
NeoPixel DIN (a través de 330 Ω)D6GPIO 13GP22
En el ESP32 y la Pico alimenta el joystick y la OLED con 3,3 V; un joystick alimentado a 5 V lleva más de 3,3 V a los pines analógicos y puede dañar la placa. Si conectas un módulo LCD I2C de 5 V a un ESP32, pon un conversor de nivel lógico entre ambos. Todos los pulsadores usan la resistencia pull-up interna: una pata va al pin, la otra a GND, y el pin lee LOW al pulsar.

¿Cómo funciona el bucle de juego (game loop)?

Todo videojuego se basa en un bucle de cuatro pasos que se repite decenas de veces por segundo: leer la entrada (pulsadores, joystick), actualizar el mundo (posición, velocidad, puntos), comprobar colisiones y dibujar la pantalla. Cada vuelta del bucle se llama fotograma (frame). Treinta fotogramas por segundo (30 FPS) bastan para que se vea fluido, lo que significa que cada fotograma dura unos 33 ms. La función loop() de Arduino ya es un bucle, pero gira muy rápido y de forma irregular, así que hay que dividirla en fotogramas fijos con millis().

Aquí es fundamental no usar delay(). Si escribes delay(33), el Arduino no hace nada durante esos 33 ms, puede perderse una pulsación corta y la velocidad del juego cambia cada vez que dibujar tarda más o menos. Con millis(), loop() lee la entrada en cada vuelta pero solo avanza el juego cuando toca un nuevo fotograma. La diferencia entre ambos métodos se explica en detalle en Arduino millis vs delay. Este esqueleto es la base de todos los juegos de la guía:

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}

La segunda idea clave del código es la máquina de estados. En cada momento el juego está en uno de tres estados: MENU, PLAYING o GAME_OVER. Cada estado reacciona de forma distinta al mismo pulsador: en el menú inicia la partida, durante el juego hace saltar al personaje y tras el game over abre una nueva ronda. Dividir el código así elimina los if anidados y facilita añadir un estado nuevo, como PAUSE. Tratamos el tema con más detalle en máquina de estados con Arduino.

Leer pulsadores y un joystick

Conectar los pulsadores con INPUT_PULLUP es el método más limpio: no hace falta resistencia externa, el pin lee HIGH en reposo y LOW al pulsar. Sin embargo, un pulsador mecánico rebota durante unos milisegundos al pulsarlo o soltarlo, y el Arduino lo interpreta como varias pulsaciones seguidas. En un juego eso significa saltar dos veces con una sola pulsación o que Simon cuente un color equivocado. La función buttonPressed() del esqueleto solo acepta un estado nuevo cuando el pin se ha mantenido igual durante 15 ms, y devuelve true una única vez, en el momento de pulsar. Para el circuito y otros métodos, consulta cómo conectar un pulsador a Arduino.

Un joystick está formado por dos potenciómetros y un botón. En el Uno cada eje da valores de 0 a 1023 y, con la palanca suelta, el centro está en torno a 512, aunque varía de un módulo a otro entre 490 y 530 aproximadamente. Por eso los juegos de cuadrícula usan umbrales en lugar de valores exactos: por debajo de 300 significa izquierda o arriba, por encima de 700 derecha o abajo. La amplia zona intermedia (zona muerta) evita que el personaje se desplace solo cuando sueltas la palanca. En Pong, en cambio, llevamos el valor analógico directamente a la pala: map() convierte el rango 0–1023 en el recorrido de 0 a 48 píxeles de la pala. La calibración y la tabla de direcciones están en la guía del módulo joystick.

¿Cómo se detectan las colisiones?

La detección de colisiones pregunta si dos objetos están en el mismo lugar al mismo tiempo. En los juegos con pantalla, el método más habitual es la prueba AABB (caja delimitadora alineada con los ejes): tratas cada objeto como un rectángulo. Dos rectángulos se solapan solo si se cumplen a la vez cuatro condiciones: el borde izquierdo de A está a la izquierda del borde derecho de B, el borde derecho de A está a la derecha del borde izquierdo de B, y lo mismo ocurre en el eje vertical. En código es una sola línea: ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by. En los juegos de cuadrícula (Snake, el runner en LCD) es aún más sencillo: si dos celdas tienen las mismas coordenadas, hay colisión.

Juego¿Qué choca con qué?Método
PongPelota con paredes y palasAABB + invertir la velocidad
T-RexDinosaurio con los cactusAABB (con una caja algo más pequeña)
SnakeCabeza con paredes, cuerpo y comidaIgualdad de celdas de la cuadrícula
Runner en LCDCorredor con un cactusMisma columna y misma fila
SimonColor pulsado con el color de la secuenciaComparación de elementos del array
Test de reacciónMomento de la pulsación con el de la señalComparación de tiempos (salida en falso)
Los objetos pequeños y rápidos pueden atravesar un obstáculo en un solo fotograma y saltarse la colisión; a esto se le llama tunneling. En Pong, si la pelota avanza más de 4 píxeles por fotograma, cruza la zona de comprobación de la pala de 3 píxeles sin llegar a tocarla. La solución es mantener la velocidad por fotograma por debajo del ancho de esa zona, y eso es justo lo que hace el límite MAX_SPEED = 2.8 del código de Pong.

Puntuación y récord guardado en la EEPROM

La puntuación vive en una variable de la RAM y desaparece en cuanto desenchufas el Arduino. Para que el récord sea permanente se usa la EEPROM: el ATmega328P del Arduino Uno tiene 1024 bytes de EEPROM que conservan su contenido sin alimentación. Para un récord bastan dos bytes de tipo uint16_t (0–65535). El único punto débil de la EEPROM es su vida útil: cada celda admite unas 100.000 escrituras. Parece mucho, pero si escribes la puntuación en cada fotograma, 30 veces por segundo, la celda se desgasta en unos 55 minutos. Lo correcto es escribir solo al terminar la partida y solo si se ha batido el récord; aunque batas 10 récords al día, la celda durará 27 años.

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() {}

El código tiene dos detalles finos. Primero, cada byte de una EEPROM que nunca se ha escrito vale 0xFF, por eso en el primer arranque el récord aparece como 65535 y la comprobación de 0xFFFF lo convierte en 0. En el test de reacción ocurre lo contrario: 0xFFFF significa "todavía no hay récord", porque allí un tiempo más corto es mejor. Segundo, EEPROM.put() lee cada byte antes de escribir y solo escribe los que han cambiado, lo que alarga todavía más la vida útil. Para el resto de comandos, consulta cómo usar la EEPROM de Arduino. Para que puedas cargar todos los juegos de esta guía en el mismo Uno uno tras otro, cada juego tiene su propia dirección:

JuegoDirección EEPROMValor guardado
T-Rex runner0–1Puntuación más alta
Snake2–3Mayor número de comidas
Runner en LCD4–5Puntuación más alta
Test de reacción6–7Tiempo más corto (ms)
Simon8–9Nivel más alto alcanzado

Pong: pantalla OLED y joystick

Pong es el juego de tenis de mesa de 1972 con el que empezó la era de los videojuegos. En nuestra versión controlas la pala izquierda con el eje Y del joystick (o con un potenciómetro en A1) y el Arduino juega con la pala derecha. Componentes: un Arduino Uno, una OLED SSD1306 I2C de 0,96 pulgadas, un joystick y un buzzer pasivo. Al instalar la librería Adafruit SSD1306 desde el gestor de librerías, instala también su dependencia, Adafruit GFX. La conexión de la pantalla y sus comandos de dibujo se explican en detalle en pantalla 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}

Lo que hace divertido a Pong es la función hitPaddle(). Si la pelota golpea el centro de la pala vuelve casi recta; si golpea el borde, rebota con un ángulo cerrado y pone en apuros al rival. Para ello escalamos la distancia de la pelota al centro de la pala a un valor entre -1 y +1 y la convertimos en velocidad vertical. Con cada golpe la pelota acelera un 5 % y queda limitada por MAX_SPEED. La pala del Arduino se mueve como máximo 1,2 píxeles por fotograma; si le das a la pelota un ángulo cerrado no llega a tiempo y te llevas el punto. Para hacer el juego más difícil sube ese valor a 1,5; para dos jugadores, borra las líneas de la CPU y controla la segunda pala con un potenciómetro en A2.

¿Por qué 30 ms por fotograma? El Uno envía el búfer de imagen de 1024 bytes a la OLED por I2C a 400 kHz en unos 25 ms, así que la pantalla no puede mostrar más de 35–40 fotogramas por segundo. Mantener el tiempo de fotograma justo por encima de ese tiempo de envío hace que la velocidad del juego sea constante. Y que no te engañe el aviso del compilador "Global variables use 28%": el búfer de la SSD1306 se reserva en tiempo de ejecución y el uso real de RAM sube a cerca del 78 %. Si la memoria no alcanza verás el error SSD1306 allocation failed; hemos reunido las soluciones en Arduino sin memoria.

T-Rex runner: física del salto y récord

Llevamos a la pantalla OLED el juego del dinosaurio que muestra Chrome cuando no hay internet. El dinosaurio corre en su sitio, los cactus pasan de derecha a izquierda y tú los saltas pulsando el botón. Para que el salto parezca realista usamos una física sencilla: al pulsar, la velocidad vertical pasa a -4,2 píxeles por fotograma, en cada fotograma la gravedad le suma 0,3 y la posición cambia según la velocidad. Así el dinosaurio sube frenando, se queda un instante en lo alto y cae cada vez más rápido; sube unos 27 píxeles y permanece 27 fotogramas en el aire, alrededor de 0,8 segundos. Los componentes son los mismos que en Pong; para saltar usas el pulsador de D3 o el botón del 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}
AjusteValorEfecto
JUMP_V-4.2Fuerza del salto; con -5 el dinosaurio sube unos 40 píxeles, casi hasta el borde superior
GRAVITY0.3Un valor mayor hace el salto más corto y brusco
speed2,0 → 5,0Velocidad de los cactus; aumenta 0,25 cada 200 fotogramas
random(70, 140)píxelesDistancia entre dos cactus; valores menores hacen el juego más difícil
Caja de colisión10×12 píxelesMás pequeña que el dinosaurio dibujado de 14×14, así los roces por poco no cuentan como choque

El récord se escribe en la EEPROM en el momento en que termina la partida, y solo si se ha batido; aparece en la esquina superior derecha como HI. Si mantienes pulsado el botón de D3 al encender la placa, el récord se borra, algo útil para empezar un torneo nuevo con tus amigos. La altura y el ancho de los cactus salen de random(). Para que cada partida empiece distinta, la línea randomSeed(analogRead(A3)) usa como semilla el ruido de un pin analógico sin conectar.

Snake en una matriz LED (8x8 y 16x16)

El juego de la serpiente que hicieron famoso los antiguos Nokia se juega mejor en una matriz LED, porque cada LED es una celda del juego. Usamos una matriz de color WS2812B NeoPixel: la versión 8x8 tiene 64 píxeles y la de 16x16, 256, todos controlados con un único cable de datos. El cuerpo de la serpiente se guarda en dos arrays (snakeX y snakeY) y el elemento 0 es la cabeza. En cada paso el cuerpo avanza una celda y la nueva posición se escribe en la cabeza; cuando come, la cola no se borra, así que crece una celda. Para compilar instala la librería Adafruit NeoPixel; los detalles de conexión están en la guía 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}

El código tiene tres reglas clave. Prohibido dar marcha atrás: si una serpiente que va a la derecha gira de golpe a la izquierda, choca con su propio cuello; readJoystick() compara la nueva dirección con la dirección actual de la serpiente y no con la pendiente, así que ni dos movimientos rápidos pueden darle la vuelta. Excepción de la cola: en un paso sin comida la punta de la cola se libera, de modo que meter la cabeza en esa celda no es una colisión. Colocar la comida: placeFood() vuelve a intentarlo hasta que la comida no caiga sobre la serpiente. Cada comida acorta el paso 10 ms (de 300 a 120 ms); al terminar la partida se enciende en azul un LED por cada comida y el récord se guarda en la EEPROM.

También importa cómo están conectados los LED de tu matriz. En la mayoría de placas rígidas de 8x8 los LED van fila a fila en el mismo sentido (SERPENTINE = false). Los paneles flexibles de 16x16 suelen ir en zigzag: una fila va de izquierda a derecha y la siguiente de derecha a izquierda. Si la serpiente salta en diagonal o una fila de cada dos va al revés, pon SERPENTINE a true; si las direcciones salen invertidas, gira el joystick o intercambia los umbrales 300 y 700. Para 16x16 basta con poner W y H a 16; entonces los arrays de la serpiente ocupan 512 bytes, el búfer NeoPixel 768 bytes y se llena cerca del 75 % de la RAM del Uno.

Matriz LED NeoPixel 16x16
Matriz NeoPixel de 16x16: un campo de juego de 256 píxeles significa una serpiente más larga y un juego más difícil; necesita alimentación externa de 5 V.
Un solo WS2812B consume unos 60 mA en blanco total; los 256 LED de una matriz de 16x16 pueden llegar a 15 A en el peor caso, y ni el USB ni el pin 5V del Arduino pueden darlos. Alimenta la matriz con una fuente externa de 5 V y 3–4 A, une el GND de la fuente con el GND del Arduino y pon una resistencia de 330 Ω en la línea de datos y un condensador de 1000 µF en la entrada de alimentación. setBrightness(30) en el código baja el brillo a cerca del 12 %; como en Snake hay pocos LED encendidos a la vez, la corriente se queda en unos cientos de mA. Para una opción de un solo color que gasta menos RAM, consulta matriz LED MAX7219.

Juego de memoria Simon: 4 LED y 4 pulsadores

Simon es el legendario juego de memoria de 1978: en cada ronda el aparato añade un color a la secuencia y tú repites la secuencia completa en el mismo orden. Componentes: cuatro LED de colores (verde, rojo, amarillo, azul), una resistencia de 220 Ω para cada uno, cuatro pulsadores y un buzzer pasivo. Conecta los LED a D9–D12, los pulsadores a D2–D5 y el buzzer a D8; este juego es un circuito aparte de la consola común. Puedes elegir la resistencia adecuada para tus LED con la calculadora de resistencia para LED. Los cuatro sonidos del Simon original son notas de un acorde de la mayor; el código usa las mismas notas (330, 440, 554 y 659 Hz), por eso suena bien incluso cuando pulsas 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}

En Simon la aleatoriedad lo es todo: si la secuencia es igual en cada partida, el jugador se la aprende de memoria. El método randomSeed(analogRead(A0)), que lee un pin sin conectar, es débil en la práctica porque el pin suele leer valores parecidos. Este código usa un truco más robusto: el juego espera la primera pulsación del jugador y toma como semilla el valor de micros() en ese instante. Nadie puede repetir el momento de una pulsación con precisión de microsegundos, así que cada partida empieza distinta. El tiempo de exhibición se acorta 20 ms por nivel y se queda en 180 ms; si no pulsas en 3 segundos o pulsas el color equivocado, la partida termina y, si es récord, el nivel alcanzado se guarda en la EEPROM.

Runner en LCD: caracteres personalizados en una pantalla 16x2

En una LCD de caracteres 16x2 no puedes dibujar píxeles, pero con createChar() puedes definir hasta 8 caracteres personalizados de 5×8 píxeles. Para el runner bastan tres: dos posturas de carrera y un cactus. La fila inferior de la pantalla es el suelo; los cactus se desplazan de derecha a izquierda y, al pulsar el botón, el corredor sube a la fila superior y se queda unos pasos en el aire. Como el juego es de cuadrícula, la comprobación de colisión es una sola línea: si hay un cactus en la columna del corredor mientras está en el suelo, se acaba la partida. Para la librería y la conexión, consulta pantalla 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}

Desplazar el mundo consiste en copiar cada elemento del array ground una posición a la izquierda; la columna de la derecha recibe un cactus nuevo con un 35 por ciento de probabilidad. La regla MIN_GAP = 4 deja al menos cuatro columnas vacías entre dos cactus, de modo que todos los obstáculos se pueden superar. Cada 25 puntos el paso se acorta 15 ms, de 300 a 120 ms. Reescribir las dos filas en cada paso en lugar de borrar la pantalla con lcd.clear() evita el parpadeo; el comando clear() por sí solo tarda unos 2 ms. Si solo ves cuadrados, gira el ajuste de contraste de la parte trasera del módulo; si no ves nada, cambia la dirección de 0x27 a 0x3F.

Test de reacción: mide tu tiempo de reacción en milisegundos

Es el juego más sencillo y el más adictivo: un LED se enciende en un momento aleatorio, pulsas el botón lo más rápido que puedas y el Arduino escribe el tiempo transcurrido en milisegundos en el monitor serie. Solo necesitas un LED en D7 con una resistencia de 220 Ω, el pulsador de D3 y un buzzer opcional. La espera es aleatoria, de 2 a 5 segundos; si pulsas mientras esperas recibes el aviso "demasiado pronto", así que no se puede hacer trampa adivinando. El tiempo se mide con micros(), se calcula tu media y tu mejor tiempo se guarda en la 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}

El tiempo medio de reacción humano ante la luz ronda los 200–250 ms; un jugador bien concentrado puede bajar un poco de ahí. Si pones USE_SOUND a true, suena un pitido junto con el LED. Ante un sonido solemos reaccionar algo más rápido que ante la luz, así que comparar los récords de los dos modos es un experimento interesante. Si muestras el tiempo en un display de 7 segmentos o en una OLED en lugar del monitor serie, tendrás un aparato a pilas que funciona sin ordenador.

Juegos con ESP32

La mayor parte del código de esta guía funciona en el ESP32 con pequeños cambios: las librerías Adafruit SSD1306 y NeoPixel son compatibles con el ESP32, y tone() está disponible en las versiones 2.x y 3.x del núcleo Arduino para ESP32. Hay que cambiar tres cosas: los pines (columna ESP32 de la tabla común), los umbrales del joystick y la forma de guardar el récord. El ADC del ESP32 es de 12 bits y devuelve valores de 0 a 4095, así que usa unos 1200 y 2800 en lugar de 300 y 700. Conecta el joystick a los pines ADC1, GPIO 32–39, porque los pines ADC2 no se pueden leer con el Wi-Fi activo.

El ESP32 no tiene una EEPROM real; la librería EEPROM la emula en la flash y necesita llamadas a EEPROM.begin() y EEPROM.commit(). Es más limpio usar la librería Preferences: guarda los valores con un espacio de nombres (namespace) y una clave en la partición NVS de la flash y reparte el desgaste por sí misma. El juego "carrera de clics" que verás abajo cuenta cuántas veces puedes pulsar el botón en 5 segundos y guarda el récord con 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) devuelve el segundo parámetro si no hay nada guardado, así que nunca tendrás que lidiar con el problema del 0xFFFF de la EEPROM. Los nombres del espacio y de la clave pueden tener como máximo 15 caracteres. Aunque el procesador del ESP32 es muchas veces más rápido que el del Uno, la OLED usa el mismo bus I2C, así que la tasa de fotogramas apenas cambia; para juegos mucho más fluidos y en color puedes usar una pantalla TFT ILI9341 por SPI. El Bluetooth del ESP32, por su parte, es un buen punto de partida para convertir el móvil en un mando.

Juegos con Raspberry Pi Pico y MicroPython

Si programas la Raspberry Pi Pico en MicroPython, la lógica del juego es la misma y solo cambia la sintaxis: time.ticks_ms() y time.ticks_diff() en lugar de millis(), read_u16(), que devuelve de 0 a 65535, en lugar de analogRead(), y PWM en lugar de tone(). La Pico no tiene EEPROM, pero gracias al sistema de archivos de MicroPython es muy fácil escribir el récord en un pequeño archivo de texto en la flash. El código de abajo es la versión para Pico del test de reacción y guarda el récord en el archivo rekor.txt; si lo guardas en la placa como main.py desde Thonny, arrancará solo cada vez que la enciendas. Si eres nuevo en MicroPython, lee antes qué es la 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)

Al medir tiempos es importante usar ticks_diff(): los contadores vuelven a empezar al llegar a cierto valor, y restar directamente dos valores da un resultado erróneo en ese momento; ticks_diff() calcula bien ese desbordamiento. Para juegos con OLED, el driver ssd1306 que instalas desde el gestor de paquetes de Thonny ofrece comandos como fill_rect(), text() y show(), parecidos a los de Adafruit GFX. El módulo neopixel que necesitas para una matriz NeoPixel ya viene incluido en MicroPython. Así puedes llevar también Pong y Snake a la Pico con la misma lógica.

Errores comunes y soluciones

SíntomaCausaSolución
La pantalla se queda en blancoSi el monitor serie muestra "SSD1306 allocation failed", falta RAM; si no, la dirección o el cableado son incorrectosPon los textos fijos dentro de F() y reduce los arrays grandes; sin ese mensaje, revisa los cables SDA/SCL y prueba la dirección 0x3D
El juego empieza igual cada vezNo hay randomSeed() o la semilla siempre es la mismaUsa como semilla el valor de micros() en la primera pulsación
Una pulsación provoca dos saltosRebote del pulsadorUsa la función buttonPressed(), que espera 15 ms de estabilidad
El récord muestra 65535Una EEPROM sin escribir lee 0xFFTrata 0xFFFF como 0
El personaje se desplaza solo con el joystick sueltoEl centro no es 512 y la zona muerta es estrechaMantén los umbrales 300/700 o mide el centro al arrancar y calibra
La serpiente salta en diagonalLa matriz está conectada en zigzagPon SERPENTINE = true
El Arduino se reinicia al subir el brilloLa matriz LED consume demasiada corrienteBaja setBrightness() y usa una fuente externa de 5 V
El PWM del pin 3 u 11 falla cuando suena tone()En el Uno, tone() usa el Timer2Pasa el componente que necesita PWM al pin 5, 6, 9 o 10
El juego se vuelve lento con el tiempoSerial.print o dibujo innecesario en cada fotogramaQuita la salida serie y mantén el fotograma por encima del tiempo de envío de la pantalla
La LCD solo muestra cuadradosContraste mal ajustado o dirección incorrectaGira el ajuste trasero y prueba la dirección 0x27 o 0x3F
Si un juego no se comporta como esperas, prueba primero la lógica sin pantalla: muestra en el monitor serie la posición, la velocidad y el estado en cada fotograma y sigue unos cuantos fotogramas a mano. Cuando encuentres el fallo, borra esas líneas, porque Serial.print gasta unos milisegundos por fotograma y altera la velocidad del juego. Si el código ni siquiera se sube a la placa, revisa la guía Arduino no sube el código.

Ideas para mejorar tus juegos

  • Pong para dos: borra las líneas de la CPU, controla la segunda pala con un potenciómetro en A2 y lleva los dos mandos a los extremos de la mesa con cables largos.
  • Control por inclinación: usa un MPU6050 en lugar del joystick y dirige la serpiente o la pala inclinando la placa.
  • Música y efectos: reproduce música de juego con un DFPlayer Mini; para efectos cortos basta un buzzer pasivo.
  • Jugar con el móvil: convierte tu teléfono en un mando inalámbrico con un módulo Bluetooth HC-05.
  • Mando para el ordenador: como el Arduino Leonardo puede funcionar como teclado USB, puedes construir un mando para juegos de PC con un joystick y pulsadores.
  • Marcador: muestra los récords como texto deslizante en un módulo MAX7219 de 4 en 1.
  • Juegos de azar: haz una ruleta con un anillo WS2812B o un dado electrónico con LED; la forma más divertida de practicar random().

Hacer juegos con Arduino es la manera más amena de aprender código y hardware a la vez. Cuando entiendas el bucle de juego, la detección de colisiones y el récord en la EEPROM, crear tu propio juego será solo cuestión de tener una idea; para inspirarte más, echa un vistazo a la lista de ideas de proyectos con Arduino. En la app BlueGrays encontrarás Pong, el T-Rex en OLED, Snake en matrices de 8x8 y 16x16, Simon, el runner en LCD y el test de reacción como proyectos paso a paso con esquemas de circuito y código listo para usar.

Preguntas frecuentes

¿Qué juegos se pueden hacer con Arduino?

Con un Arduino Uno puedes hacer Pong, el T-Rex runner, juegos tipo Flappy Bird, Snake, juegos sencillos de bloques, el juego de memoria Simon, un test de reacción y juegos de azar como un dado o una ruleta. Para juegos con gráficos basta una OLED de 128×64 y para juegos de cuadrícula en color, una matriz LED NeoPixel. Simon y el test de reacción se juegan incluso sin pantalla, solo con LED y pulsadores.

¿Cuál es la mejor pantalla para un juego con Arduino?

Para empezar, la mejor opción es la OLED SSD1306 de 0,96 pulgadas: es barata, se conecta con solo dos cables de datos y dibuja gráficos en movimiento a 128×64 píxeles. Si quieres una imagen más grande y en color, va bien una pantalla TFT ILI9341 o ST7789, y para juegos de estilo pixel art, una matriz LED de 8x8 o 16x16. Una LCD 16x2 con caracteres personalizados también sirve para juegos de carreras sencillos.

¿Cómo se guarda un récord en Arduino?

Si escribes el récord en la EEPROM, no se pierde aunque se corte la alimentación. Lo escribes con EEPROM.put(dirección, récord) y lo lees con EEPROM.get(dirección, récord); el Arduino Uno tiene 1024 bytes para ello. Como cada celda admite unas 100.000 escrituras, escribe solo cuando termine la partida y se haya batido el récord.

¿Se puede hacer un mando de videojuegos con Arduino?

Sí. Las placas con el procesador ATmega32U4, como el Arduino Leonardo, Micro o Pro Micro, pueden presentarse ante el ordenador como teclado, ratón o mando USB; leen el joystick y los pulsadores y envían pulsaciones de teclas. El Arduino Uno no puede hacerlo directamente, pero sí enviar los valores del joystick por el puerto serie a un programa del ordenador.

¿Por qué mi juego de Arduino va lento o se traba?

Las causas más habituales son los bucles hechos con delay(), las llamadas a Serial.print en cada fotograma y el tiempo de envío de la pantalla. En el Uno, enviar toda la imagen de la OLED tarda unos 25 ms, así que un fotograma de 30 ms es realista. Usa un tiempo de fotograma fijo con millis() en lugar de delay(), quita la salida serie y dibuja solo lo necesario.

¿Cuál es el juego de Arduino más fácil para principiantes?

El test de reacción es el más fácil: se hace con un LED, un pulsador y un programa corto, pero enseña conceptos básicos como la temporización, los números aleatorios y la EEPROM. Después, un buen camino de aprendizaje es pasar a Simon con cuatro LED y cuatro pulsadores, y luego a Pong y T-Rex con una pantalla OLED.

Ver todos los proyectos
Juego de Raquetas - SSD1306
#86Intermedio
Premium

Juego de Raquetas - SSD1306

Una pelota, dos palas y tú: juegas al tenis de mesa, uno de los primeros videojuegos de la historia, en una pequeña pantalla OLED. Con dos botones mueves tu pala de la derecha arriba y abajo, mientras

4.5(47)
65
OLED T-Rex Run: Juego del dinosaurio con Arduino
#87Avanzado
Premium

OLED T-Rex Run: Juego del dinosaurio con Arduino

El juego del dinosaurio que aparece cuando se va internet, ahora en tu mesa: el dinosaurio corre por la pequeña pantalla OLED, los cactus se le echan encima y tú saltas con un solo botón. Tu puntuació

4.7(33)
53
Serpiente de píxeles Matriz 8x8
#88Avanzado
Premium

Serpiente de píxeles Matriz 8x8

Monta con tus propias manos el clásico juego de la serpiente y juega. En una matriz de luces de colores de 8x8 guías una serpiente verde con un joystick y te comes manzanas rojas; con cada manzana la

4.7(39)
57
Matriz Snake 16x16
#89Avanzado
Premium

Matriz Snake 16x16

El clásico juego de la serpiente, esta vez en una enorme pantalla LED de 256 luces. Guías a la serpiente verde con un joystick y recoges manzanas rojas; con cada manzana la serpiente crece, tu puntuac

4.8(52)
67
Juego de memoria con Arduino (4 LED + 4 pulsadores + zumbador)
#93Avanzado
Premium

Juego de memoria con Arduino (4 LED + 4 pulsadores + zumbador)

¿Te fías de tu memoria? Cuatro LED de colores se encienden en secuencia, cada uno con su propia nota, y tú repites el mismo orden pulsando los botones. Cada ronda acertada añade un color nuevo a la se

4.8(46)
69
Juego Arduino LCD Runner
#98Intermedio
Premium

Juego Arduino LCD Runner

Juego endless runner de desplazamiento lateral que se ejecuta en una pantalla LCD de 16x2 caracteres. El héroe corre automáticamente, el jugador salta los obstáculos pulsando el botón. Aparecen obstáculos aleatorios en las filas superior e inferior. El zumbador pasivo reproduce la melodía de apertura, el sonido de salto, el sonido de celebración de 50 puntos y la melodía de fin de partida. La pantalla de inicio "BlueGrays - Arduino Projects" se muestra al arrancar.

4.6(32)
55
Arduino Test de Reacción con Aviso Sonoro: Mide tus Reflejos en Milisegundos (LCD + Zumbador + 3 LED)
#97Intermedio
Premium

Arduino Test de Reacción con Aviso Sonoro: Mide tus Reflejos en Milisegundos (LCD + Zumbador + 3 LED)

¿Tienes buenos reflejos? Pulsa el botón: el LED rojo dice preparados, el amarillo dice listos y empieza una espera aleatoria de 1,5 a 3,5 segundos. En cuanto se enciende el LED verde y suena el zumbador, vuelve a pulsar; tu tiempo de reacción aparece en milisegundos en la LCD I2C, con tu récord de la sesión y tu media debajo. Si pulsas antes del verde, la pantalla muestra TOO EARLY! y suena un tono de derrota. Mantén pulsado para un nuevo jugador con las mismas esperas.

4.6(33)
50