Arduino Spiele programmieren: Pong, T-Rex, Snake und Simon
Arduino Spiele selbst bauen: Pong und T-Rex auf dem OLED, Snake auf der LED-Matrix, Simon, LCD-Runner und Reaktionstest, mit Game Loop und Highscore im EEPROM.
Arduino Spiele programmieren ist der unterhaltsamste Weg, Elektronik und Programmieren zu lernen: Mit einem OLED-Display, ein paar Tastern und einem Joystick baust du Klassiker wie Pong, den T-Rex-Runner, Snake und das Gedächtnisspiel Simon selbst und forderst deine Freunde zum Highscore-Duell heraus. Beim Schreiben eines Spiels lernst du ganz nebenbei die wichtigsten Themen der Programmierung: Timing, Zustandsautomaten, Koordinatensysteme, Kollisionsberechnung, Zufallszahlen und dauerhaften Speicher. Alle diese Spiele laufen auf einem einzigen Arduino Uno; seine 2 KB RAM reichen, wenn du sparsam mit dem Speicher umgehst.
In dieser Anleitung bauen wir sechs Spiele von Anfang bis Ende: Pong und den T-Rex-Runner auf dem OLED-Display, Snake auf einer 8x8- und einer 16x16-NeoPixel-LED-Matrix, Simon mit vier LEDs und vier Tastern, ein Runner-Spiel auf dem 16x2-LCD und einen millisekundengenauen Reaktionstest. Zu jedem Spiel gibt es Teileliste, Verdrahtung und kompilierbaren Code. Das gemeinsame Gerüst aller Spiele, also Game Loop, Kollisionserkennung, Punktezählung und Highscore im EEPROM, lernst du zuerst einmal und verwendest es dann in jedem Spiel wieder. Am Ende findest du außerdem Versionen für den ESP32 und den Raspberry Pi Pico.

Welches Spiel braucht welche Bauteile?
Wenn du mit dem Arduino ein Spiel baust, ist die erste Entscheidung das Display, denn es bestimmt, welche Art von Spiel möglich ist. Schon mit ein paar LEDs lassen sich Simon oder ein Reaktionstest bauen; ein OLED mit 128×64 Pixeln macht dagegen Pong und T-Rex mit bewegter Grafik und Physik möglich. Die Tabelle fasst die Spiele dieser Anleitung von leicht bis schwer zusammen:
| Spiel | Display | Eingabe | Weitere Bauteile | Schwierigkeit |
|---|---|---|---|---|
| Reaktionstest | 1 LED + serieller Monitor | 1 Taster | 220-Ω-Widerstand, optional Summer | Leicht |
| Gedächtnisspiel Simon | 4 farbige LEDs | 4 Taster | 4 × 220-Ω-Widerstand, passiver Summer | Leicht bis mittel |
| LCD-Runner | 16x2-I2C-LCD | 1 Taster | Passiver Summer | Mittel |
| Pong | 0,96"-SSD1306-OLED | Joystick oder 10-kΩ-Potentiometer | Passiver Summer | Mittel |
| T-Rex-Runner | 0,96"-SSD1306-OLED | 1 Taster oder Joystick-Taster | Passiver Summer | Mittel |
| Snake (8x8) | 8x8-NeoPixel-Matrix | Joystick | 330-Ω-Widerstand, 1000-µF-Kondensator | Mittel bis fortgeschritten |
| Snake (16x16) | 16x16-NeoPixel-Matrix | Joystick | Externes 5-V-Netzteil mit 3–4 A | Fortgeschritten |






Gemeinsame Verdrahtung: eine Schaltung als Spielkonsole
Wir haben die Pins so gewählt, dass alle Spiele außer Simon auf derselben Schaltung laufen. OLED und I2C-LCD teilen sich dieselben zwei Leitungen (SDA und SCL); der Joystick kommt an die analogen Eingänge, der Aktionstaster an D3 und der Summer an D8. Hast du diese „Spielkonsole“ einmal aufgebaut, wechselst du das Spiel einfach durch Hochladen eines anderen Sketches. Die komplette Pinbelegung deines Boards zeigt dir das Pinout-Tool.
| Bauteil / Pin | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| OLED oder I2C-LCD: SDA | A4 | GPIO 21 | GP4 |
| OLED oder I2C-LCD: SCL | A5 | GPIO 22 | GP5 |
| Joystick VRx (X-Achse) | A0 | GPIO 34 | GP26 |
| Joystick VRy (Y-Achse) | A1 | GPIO 35 | GP27 |
| Joystick SW (Taster) | D2 | GPIO 25 | GP15 |
| Aktionstaster (anderes Bein an GND) | D3 | GPIO 26 | GP14 |
| Passiver Summer (+) | D8 | GPIO 27 | GP16 |
| NeoPixel DIN (über 330 Ω) | D6 | GPIO 13 | GP22 |
Wie funktioniert ein Game Loop?
Jedes Videospiel basiert auf einer Schleife aus vier Schritten, die sich dutzende Male pro Sekunde wiederholt: Eingabe lesen (Taster, Joystick), Spielwelt aktualisieren (Position, Geschwindigkeit, Punkte), Kollisionen prüfen und Bildschirm zeichnen. Ein Durchlauf dieser Schleife heißt Frame. 30 Frames pro Sekunde (30 FPS) reichen für eine flüssige Darstellung, jeder Frame dauert also etwa 33 ms. Die loop()-Funktion des Arduino ist zwar schon eine Schleife, läuft aber sehr schnell und unregelmäßig; deshalb teilst du sie mit millis() in feste Frames auf.
Hier auf delay() zu verzichten ist entscheidend. Schreibst du delay(33), tut der Arduino 33 ms lang gar nichts, kann einen kurzen Tastendruck verpassen, und das Spieltempo ändert sich, sobald das Zeichnen mal länger oder kürzer dauert. Mit millis() liest loop() die Eingabe bei jedem Durchlauf, schaltet das Spiel aber nur weiter, wenn ein neuer Frame fällig ist. Den Unterschied der beiden Methoden erklärt der Artikel Arduino millis statt delay ausführlich. Das folgende Gerüst ist die Basis aller Spiele in dieser Anleitung:
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}Die zweite wichtige Idee im Code ist der Zustandsautomat (State Machine). Das Spiel befindet sich immer in genau einem von drei Zuständen: MENU, PLAYING oder GAME_OVER. Jeder Zustand reagiert anders auf denselben Taster: Im Menü startet ein Druck das Spiel, während des Spiels springt die Figur, nach dem Game Over beginnt eine neue Runde. So aufgeteilter Code kommt ohne verschachtelte if-Blöcke aus, und ein neuer Zustand wie PAUSE ist schnell ergänzt. Ausführlicher behandeln wir das Thema im Artikel Arduino Zustandsautomat.
Taster und Joystick einlesen
Taster mit INPUT_PULLUP anzuschließen ist die sauberste Methode: Du brauchst keinen externen Widerstand, der Pin liest im Ruhezustand HIGH und beim Drücken LOW. Ein mechanischer Taster prellt aber beim Drücken und Loslassen einige Millisekunden lang, und der Arduino sieht darin mehrere Drücke hintereinander. Im Spiel heißt das: zweimal springen bei einem Druck oder eine falsch gewertete Farbe bei Simon. Die Funktion buttonPressed() im Gerüst übernimmt einen neuen Zustand erst, wenn der Pin 15 ms lang stabil war, und liefert genau einmal true, im Moment des Drückens. Schaltung und weitere Entprell-Methoden findest du unter Taster am Arduino anschließen.
Ein Joystick besteht aus zwei Potentiometern und einem Taster. Am Uno liefert jede Achse Werte von 0 bis 1023, in der Mittelstellung etwa 512, wobei dieser Wert je nach Modul zwischen etwa 490 und 530 schwankt. Darum arbeiten Rasterspiele mit Schwellen statt exakten Werten: unter 300 bedeutet links oder oben, über 700 rechts oder unten. Der breite Bereich dazwischen (die Totzone) verhindert, dass die Figur von allein wegdriftet, wenn du den Hebel loslässt. Bei Pong bilden wir den Analogwert dagegen direkt auf den Schläger ab: map() rechnet den Bereich 0–1023 in die 0 bis 48 Pixel Weg des Schlägers um. Kalibrierung und Richtungstabelle stehen im Artikel Joystick-Modul.
Wie funktioniert die Kollisionserkennung?
Die Kollisionserkennung prüft, ob zwei Objekte gleichzeitig am selben Ort sind. Bei Spielen mit Bildschirm ist der AABB-Test (achsenparallele Bounding Box) am verbreitetsten: Du betrachtest jedes Objekt als Rechteck. Zwei Rechtecke überlappen nur, wenn alle vier Bedingungen gleichzeitig erfüllt sind: Die linke Kante von A liegt links der rechten Kante von B, die rechte Kante von A rechts der linken Kante von B, und dieselben zwei Bedingungen gelten auf der senkrechten Achse. Im Code ist das eine einzige Zeile: ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by. In Rasterspielen (Snake, LCD-Runner) ist es noch einfacher: Haben zwei Zellen dieselben Koordinaten, liegt eine Kollision vor.
| Spiel | Was kollidiert womit? | Methode |
|---|---|---|
| Pong | Ball mit Wänden und Schlägern | AABB + Umkehren der Geschwindigkeit |
| T-Rex | Dinosaurier mit den Kakteen | AABB (mit etwas kleinerer Box) |
| Snake | Kopf mit Wand, Körper und Futter | Gleichheit der Rasterzellen |
| LCD-Runner | Läufer mit Kaktus | Gleiche Spalte und gleiche Zeile |
| Simon | Gedrückte Farbe mit Farbe der Folge | Vergleich von Array-Elementen |
| Reaktionstest | Zeitpunkt des Drückens mit Zeitpunkt des Signals | Zeitvergleich (Frühstart) |
Punkte zählen und Highscore im EEPROM speichern
Die Punktzahl steht in einer Variablen im RAM und ist weg, sobald du den Arduino vom Strom trennst. Damit der Highscore erhalten bleibt, nutzt du das EEPROM: Der ATmega328P des Arduino Uno hat 1024 Byte EEPROM, die ihren Inhalt auch ohne Strom behalten. Für einen Highscore reichen zwei Byte vom Typ uint16_t (0–65535). Die einzige Schwachstelle des EEPROM ist die Lebensdauer: Jede Zelle lässt sich etwa 100.000-mal beschreiben. Das klingt viel, doch wenn du den Wert in jedem Frame schreibst, also 30-mal pro Sekunde, ist die Zelle nach etwa 55 Minuten verschlissen. Richtig ist es, nur am Spielende zu schreiben und nur, wenn der Rekord gebrochen wurde; selbst bei 10 neuen Rekorden am Tag hält die Zelle 27 Jahre.
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() {}Der Code hat zwei Feinheiten. Erstens ist jedes Byte eines nie beschriebenen EEPROM 0xFF; deshalb erscheint der Highscore beim allerersten Start als 65535, und die Prüfung auf 0xFFFF macht daraus 0. Beim Reaktionstest ist es umgekehrt: Dort bedeutet 0xFFFF „noch kein Rekord“, weil eine kürzere Zeit besser ist. Zweitens liest EEPROM.put() jedes Byte vor dem Schreiben und schreibt nur die geänderten Bytes, was die Lebensdauer zusätzlich verlängert. Die übrigen EEPROM-Befehle findest du unter Arduino EEPROM nutzen. Damit du alle Spiele dieser Anleitung nacheinander auf denselben Uno laden kannst, hat jedes Spiel seine eigene Adresse:
| Spiel | EEPROM-Adresse | Gespeicherter Wert |
|---|---|---|
| T-Rex-Runner | 0–1 | Höchste Punktzahl |
| Snake | 2–3 | Meistes gefressenes Futter |
| LCD-Runner | 4–5 | Höchste Punktzahl |
| Reaktionstest | 6–7 | Kürzeste Zeit (ms) |
| Simon | 8–9 | Höchstes erreichtes Level |
Pong: OLED-Display und Joystick
Pong ist das 1972 erschienene Tischtennisspiel, mit dem das Zeitalter der Videospiele begann. In unserer Version steuerst du den linken Schläger mit der Y-Achse des Joysticks (oder einem Potentiometer an A1), den rechten spielt der Arduino. Bauteile: ein Arduino Uno, ein 0,96-Zoll-SSD1306-I2C-OLED, ein Joystick und ein passiver Summer. Wenn du die Bibliothek Adafruit SSD1306 über den Bibliotheksverwalter installierst, installiere auch ihre Abhängigkeit Adafruit GFX. Anschluss und Zeichenbefehle des Displays erklärt der Artikel OLED-Display SSD1306 im Detail.
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}Was Pong spannend macht, ist die Funktion hitPaddle(). Trifft der Ball die Mitte des Schlägers, kommt er fast gerade zurück; trifft er die Kante, prallt er in steilem Winkel ab und bringt den Gegner in Bedrängnis. Dazu skalieren wir den Abstand des Balls zur Schlägermitte auf einen Wert zwischen -1 und +1 und machen daraus die senkrechte Geschwindigkeit. Mit jedem Treffer wird der Ball 5 % schneller, begrenzt durch MAX_SPEED. Der Schläger des Arduino bewegt sich höchstens 1,2 Pixel pro Frame; gibst du dem Ball einen steilen Winkel, kommt er nicht hinterher und du punktest. Für ein schwierigeres Spiel erhöhe den Wert auf 1,5; für zwei Spieler löschst du die CPU-Zeilen und steuerst den zweiten Schläger mit einem Potentiometer an A2.
Warum 30 ms pro Frame? Der Uno schickt den 1024 Byte großen Bildpuffer mit 400 kHz über I2C in etwa 25 ms ans OLED; mehr als 35 bis 40 Bilder pro Sekunde schafft das Display also nicht. Liegt die Framezeit knapp über dieser Übertragungszeit, bleibt das Spieltempo konstant. Lass dich auch nicht vom Compiler-Hinweis „Global variables use 28%“ täuschen: Der SSD1306-Puffer wird erst zur Laufzeit reserviert, die tatsächliche RAM-Nutzung steigt auf etwa 78 %. Reicht der Speicher nicht, erscheint der Fehler SSD1306 allocation failed; die Lösungen haben wir unter Arduino Speicher voll gesammelt.
T-Rex-Runner: Sprungphysik und Highscore
Wir holen das Dino-Spiel, das Chrome ohne Internetverbindung zeigt, auf das OLED-Display. Der Dinosaurier läuft auf der Stelle, Kakteen ziehen von rechts nach links vorbei, und du springst per Tastendruck darüber. Damit der Sprung realistisch wirkt, nutzen wir einfache Physik: Beim Drücken wird die senkrechte Geschwindigkeit auf -4,2 Pixel pro Frame gesetzt, in jedem Frame addiert die Schwerkraft 0,3 dazu, und die Position ändert sich um die Geschwindigkeit. So steigt der Dino immer langsamer, verharrt oben kurz und fällt immer schneller; er steigt etwa 27 Pixel hoch und bleibt 27 Frames, also rund 0,8 Sekunden, in der Luft. Die Bauteile sind dieselben wie bei Pong; zum Springen nutzt du den Taster an D3 oder den Joystick-Taster.
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}| Einstellung | Wert | Wirkung |
|---|---|---|
| JUMP_V | -4.2 | Sprungkraft; bei -5 steigt der Dino etwa 40 Pixel bis fast an den oberen Rand |
| GRAVITY | 0.3 | Ein größerer Wert macht den Sprung kürzer und härter |
| speed | 2,0 → 5,0 | Tempo der Kakteen; steigt alle 200 Frames um 0,25 |
| random(70, 140) | Pixel | Abstand zwischen zwei Kakteen; kleinere Werte machen das Spiel schwerer |
| Hitbox | 10×12 Pixel | Kleiner als der gezeichnete 14×14-Dino, knappe Ausweichmanöver zählen nicht als Treffer |
Der Highscore wird beim Spielende ins EEPROM geschrieben, und zwar nur, wenn er gebrochen wurde; oben rechts steht er als HI. Hältst du beim Einschalten den Taster an D3 gedrückt, wird der Rekord gelöscht, praktisch für ein neues Turnier mit Freunden. Höhe und Breite der Kakteen bestimmt random(). Damit jedes Spiel anders beginnt, nutzt die Zeile randomSeed(analogRead(A3)) das Rauschen eines unbeschalteten Analogpins als Startwert.
Snake auf der LED-Matrix (8x8 und 16x16)
Das durch alte Nokia-Handys berühmte Snake spielt man am besten auf einer LED-Matrix, denn jede LED ist eine Spielzelle. Wir verwenden eine farbige WS2812B-NeoPixel-Matrix: Die 8x8-Version hat 64, die 16x16-Version 256 Pixel, alle über eine einzige Datenleitung angesteuert. Der Körper der Schlange liegt in zwei Arrays (snakeX und snakeY), Element 0 ist der Kopf. Bei jedem Schritt rückt der Körper um eine Zelle nach, und der Kopf bekommt die neue Position; frisst die Schlange, wird das Schwanzende nicht gelöscht, sie wächst also um eine Zelle. Installiere zum Kompilieren die Bibliothek Adafruit NeoPixel; Details zum Anschluss stehen im Artikel WS2812B NeoPixel.
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}Im Code stecken drei entscheidende Regeln. Kein Umkehren: Biegt eine nach rechts laufende Schlange plötzlich nach links ab, beißt sie sich in den eigenen Hals; readJoystick() prüft die neue Richtung deshalb nicht gegen die vorgemerkte, sondern gegen die aktuelle Richtung, sodass selbst zwei schnelle Bewegungen keine Kehrtwende erzeugen. Ausnahme fürs Schwanzende: Bei einem Schritt ohne Futter wird das Schwanzende frei, also zählt es nicht als Kollision, wenn der Kopf dorthin zieht. Futter platzieren: placeFood() würfelt so lange neu, bis das Futter nicht auf der Schlange liegt. Jedes Futter verkürzt die Schrittzeit um 10 ms (von 300 auf 120 ms); am Spielende leuchtet für jedes gefressene Futter eine LED blau, und der Rekord landet im EEPROM.
Wichtig ist auch, wie die LEDs deiner Matrix verdrahtet sind. Bei den meisten starren 8x8-Platinen laufen sie Zeile für Zeile in dieselbe Richtung (SERPENTINE = false). Flexible 16x16-Panels sind meist im Zickzack verdrahtet: Eine Zeile läuft von links nach rechts, die nächste von rechts nach links. Springt die Schlange diagonal oder läuft jede zweite Zeile rückwärts, setze SERPENTINE auf true; sind die Richtungen vertauscht, drehe den Joystick oder tausche die Schwellen 300 und 700. Für 16x16 setzt du einfach W und H auf 16; die Schlangen-Arrays belegen dann 512 Byte, der NeoPixel-Puffer 768 Byte, und etwa 75 % des Uno-RAMs sind belegt.

Gedächtnisspiel Simon: 4 LEDs und 4 Taster
Simon, in Deutschland als Senso bekannt, ist das legendäre Gedächtnisspiel von 1978: In jeder Runde hängt das Gerät eine Farbe an die Folge, und du wiederholst die ganze Folge in derselben Reihenfolge. Bauteile: vier farbige LEDs (grün, rot, gelb, blau), je ein 220-Ω-Widerstand, vier Taster und ein passiver Summer. Schließe die LEDs an D9–D12, die Taster an D2–D5 und den Summer an D8 an; dieses Spiel ist eine eigene Schaltung neben der Spielkonsole. Den passenden Vorwiderstand für deine LEDs findest du mit dem LED-Vorwiderstand-Rechner. Die vier Töne des Originals sind Noten eines A-Dur-Akkords; der Code nutzt dieselben Noten (330, 440, 554 und 659 Hz), daher klingt es auch bei schnellem Drücken angenehm.
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}Bei Simon ist der Zufall alles: Ist die Folge in jedem Spiel gleich, lernt der Spieler sie auswendig. Die Methode randomSeed(analogRead(A0)), die einen offenen Pin liest, ist in der Praxis schwach, weil der Pin oft ähnliche Werte liefert. Dieser Code nutzt einen robusteren Trick: Das Spiel wartet auf den ersten Tastendruck und nimmt den micros()-Wert dieses Moments als Startwert. Niemand kann den Zeitpunkt eines Drucks auf die Mikrosekunde genau wiederholen, also beginnt jedes Spiel anders. Die Anzeigedauer sinkt pro Level um 20 ms und bleibt bei 180 ms stehen; drückst du 3 Sekunden lang nicht oder die falsche Farbe, ist das Spiel vorbei, und das erreichte Level wird bei einem Rekord ins EEPROM geschrieben.
LCD-Runner: eigene Zeichen auf dem 16x2-Display
Auf einem 16x2-Zeichen-LCD kannst du keine Pixel zeichnen, aber mit createChar() bis zu 8 eigene Zeichen mit je 5×8 Pixeln anlegen. Für das Runner-Spiel reichen drei: zwei Laufposen und ein Kaktus. Die untere Zeile ist der Boden; Kakteen ziehen von rechts nach links, und auf Tastendruck wechselt der Läufer in die obere Zeile und bleibt einige Schritte in der Luft. Weil das Spiel rasterbasiert ist, ist die Kollisionsprüfung eine einzige Zeile: Steht in der Spalte des Läufers ein Kaktus, während er am Boden ist, ist das Spiel vorbei. Bibliothek und Anschluss erklärt der Artikel I2C-LCD-Display.
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}Das Scrollen der Welt bedeutet, jedes Element des Arrays ground um eine Stelle nach links zu kopieren; in die rechte Spalte kommt mit 35 Prozent Wahrscheinlichkeit ein neuer Kaktus. Die Regel MIN_GAP = 4 lässt mindestens vier leere Spalten zwischen zwei Kakteen, sodass jedes Hindernis zu schaffen ist. Alle 25 Punkte sinkt die Schrittzeit um 15 ms, von 300 auf 120 ms. Beide Zeilen bei jedem Schritt neu zu schreiben, statt das Display mit lcd.clear() zu löschen, verhindert Flackern; der Befehl clear() allein dauert etwa 2 ms. Siehst du nur Kästchen, dreh am Kontrastregler auf der Rückseite des Moduls; siehst du gar nichts, ändere die Adresse von 0x27 auf 0x3F.
Reaktionstest: Reaktionszeit in Millisekunden messen
Das ist das einfachste und zugleich süchtig machendste Spiel: Eine LED leuchtet zu einem zufälligen Zeitpunkt auf, du drückst so schnell wie möglich den Taster, und der Arduino schreibt die verstrichene Zeit in Millisekunden in den seriellen Monitor. Du brauchst nur eine LED an D7 mit 220-Ω-Widerstand, den Taster an D3 und optional einen Summer. Die Wartezeit liegt zufällig zwischen 2 und 5 Sekunden; drückst du während des Wartens, kommt die Warnung „zu früh“, Raten bringt also nichts. Gemessen wird mit micros(), dein Durchschnitt wird berechnet, und deine Bestzeit landet im EEPROM.
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}Die durchschnittliche Reaktionszeit des Menschen auf Licht liegt bei etwa 200 bis 250 ms; ein konzentrierter Spieler kommt etwas darunter. Setzt du USE_SOUND auf true, ertönt zusammen mit der LED ein Piepton. Auf Töne reagiert man meist etwas schneller als auf Licht, daher ist der Vergleich der Rekorde beider Modi ein spannendes Experiment. Zeigst du die Zeit statt im seriellen Monitor auf einer 7-Segment-Anzeige oder einem OLED an, hast du ein batteriebetriebenes Gerät, das ohne Computer auskommt.
Spiele mit dem ESP32
Der meiste Code dieser Anleitung läuft mit kleinen Änderungen auf dem ESP32: Die Bibliotheken Adafruit SSD1306 und NeoPixel unterstützen den ESP32, und tone() gibt es in den Versionen 2.x und 3.x des ESP32-Arduino-Cores. Drei Dinge musst du ändern: die Pins (ESP32-Spalte der gemeinsamen Tabelle), die Joystick-Schwellen und das Speichern des Highscores. Der ADC des ESP32 arbeitet mit 12 Bit und liefert Werte von 0 bis 4095; nimm also etwa 1200 und 2800 statt 300 und 700. Schließe den Joystick an die ADC1-Pins GPIO 32–39 an, denn ADC2-Pins lassen sich bei aktivem WLAN nicht auslesen.
Der ESP32 hat kein echtes EEPROM; die EEPROM-Bibliothek emuliert es im Flash und braucht die Aufrufe EEPROM.begin() und EEPROM.commit(). Sauberer ist die Bibliothek Preferences: Sie speichert Werte unter einem Namensraum (Namespace) und einem Schlüssel in der NVS-Partition des Flash und verteilt den Verschleiß selbst. Das folgende Spiel „Klick-Rennen“ zählt, wie oft du den Taster in 5 Sekunden drücken kannst, und speichert den Rekord mit Preferences:
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) gibt den zweiten Parameter zurück, wenn noch nichts gespeichert ist; mit dem 0xFFFF-Problem des EEPROM musst du dich also nie herumschlagen. Namensraum und Schlüssel dürfen höchstens 15 Zeichen lang sein. Obwohl der Prozessor des ESP32 um ein Vielfaches schneller ist als der des Uno, nutzt das OLED denselben I2C-Bus, die Bildrate ändert sich also kaum; für deutlich flüssigere und farbige Spiele eignet sich ein per SPI angeschlossenes ILI9341-TFT-Display. Das Bluetooth des ESP32 wiederum ist ein guter Startpunkt, um das Smartphone zum Gamecontroller zu machen.
Spiele mit dem Raspberry Pi Pico und MicroPython
Programmierst du den Raspberry Pi Pico in MicroPython, bleibt die Spiellogik gleich, nur die Syntax ändert sich: time.ticks_ms() und time.ticks_diff() statt millis(), read_u16() mit Werten von 0 bis 65535 statt analogRead() und PWM statt tone(). Der Pico hat kein EEPROM, aber dank des Dateisystems von MicroPython ist es sehr einfach, den Rekord in eine kleine Textdatei im Flash zu schreiben. Der folgende Code ist die Pico-Version des Reaktionstests und speichert den Rekord in der Datei rekor.txt; speicherst du ihn in Thonny als main.py auf dem Board, startet er bei jedem Einschalten von selbst. Bist du neu bei MicroPython, lies zuerst Was ist der Raspberry Pi Pico?.
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)Bei der Zeitmessung ticks_diff() zu verwenden ist wichtig: Die Zähler laufen nach einem bestimmten Wert über, und einfaches Subtrahieren liefert in diesem Moment ein falsches Ergebnis; ticks_diff() berechnet den Überlauf korrekt. Für OLED-Spiele bietet der ssd1306-Treiber, den du über den Paketmanager von Thonny installierst, Befehle wie fill_rect(), text() und show(), ähnlich wie Adafruit GFX. Das neopixel-Modul für eine NeoPixel-Matrix ist in MicroPython bereits enthalten. So kannst du auch Pong und Snake mit derselben Logik auf den Pico bringen.
Häufige Fehler und Lösungen
| Symptom | Ursache | Lösung |
|---|---|---|
| Display bleibt leer | Steht im seriellen Monitor "SSD1306 allocation failed", reicht der RAM nicht; sonst stimmen Adresse oder Verdrahtung nicht | Feste Texte in F() packen, große Arrays verkleinern; ohne Meldung SDA/SCL prüfen und Adresse 0x3D testen |
| Das Spiel beginnt jedes Mal gleich | Kein randomSeed() oder immer derselbe Startwert | Mit dem micros()-Wert des ersten Tastendrucks initialisieren |
| Ein Druck löst zwei Sprünge aus | Prellen des Tasters | Die Funktion buttonPressed() mit 15 ms Stabilitätsprüfung verwenden |
| Der Highscore zeigt 65535 | Nie beschriebenes EEPROM liest 0xFF | 0xFFFF als 0 behandeln |
| Die Figur driftet bei losgelassenem Joystick | Mitte liegt nicht bei 512, Totzone zu schmal | Schwellen 300/700 beibehalten oder die Mitte beim Start messen und kalibrieren |
| Die Schlange springt diagonal | Matrix ist im Zickzack verdrahtet | SERPENTINE = true setzen |
| Der Arduino startet neu, wenn die Helligkeit steigt | Die LED-Matrix zieht zu viel Strom | setBrightness() senken und externes 5-V-Netzteil nutzen |
| PWM an Pin 3 oder 11 spinnt, sobald tone() spielt | Am Uno nutzt tone() den Timer2 | Bauteil, das PWM braucht, an Pin 5, 6, 9 oder 10 legen |
| Das Spiel wird mit der Zeit langsamer | Serial.print oder unnötiges Zeichnen in jedem Frame | Serielle Ausgaben entfernen, Framezeit über der Übertragungszeit des Displays halten |
| Das LCD zeigt nur Kästchen | Kontrast falsch oder falsche Adresse | Regler auf der Rückseite drehen, Adresse 0x27 oder 0x3F testen |
Ideen, um deine Spiele weiterzuentwickeln
- Pong zu zweit: Lösche die CPU-Zeilen, steuere den zweiten Schläger mit einem Potentiometer an A2 und verlege beide Controller mit langen Kabeln an die Enden des Tisches.
- Neigungssteuerung: Nutze statt des Joysticks einen MPU6050 und lenke Schlange oder Schläger durch Kippen des Boards.
- Musik und Soundeffekte: Spiele mit einem DFPlayer Mini Spielmusik ab; für kurze Effekte reicht ein passiver Summer.
- Mit dem Smartphone spielen: Mach dein Handy mit einem HC-05-Bluetooth-Modul zum kabellosen Controller.
- Gamepad für den PC: Weil sich der Arduino Leonardo als USB-Tastatur ausgeben kann, baust du mit Joystick und Tastern einen Controller für PC-Spiele.
- Anzeigetafel: Zeige die Highscores als Laufschrift auf einem 4-in-1-MAX7219-Modul.
- Glücksspiele: Baue mit einem WS2812B-Ring ein Roulette-Rad oder mit LEDs einen elektronischen Würfel; der unterhaltsamste Weg, random() zu üben.
Arduino Spiele zu bauen ist die schönste Art, Code und Hardware gleichzeitig zu lernen. Hast du Game Loop, Kollisionserkennung und den Highscore im EEPROM einmal verstanden, ist dein eigenes Spiel nur noch eine Frage der Idee; mehr Inspiration findest du in der Liste Arduino Projektideen. In der BlueGrays-App findest du Pong, den OLED-T-Rex, Snake auf 8x8- und 16x16-Matrix, Simon, den LCD-Runner und den Reaktionstest als Schritt-für-Schritt-Projekte mit Schaltplänen und fertigem Code.
Häufige Fragen
Welche Spiele kann man mit dem Arduino programmieren?
Mit einem Arduino Uno lassen sich Pong, der T-Rex-Runner, Flappy-Bird-ähnliche Spiele, Snake, einfache Blockspiele, das Gedächtnisspiel Simon, ein Reaktionstest und Glücksspiele wie Würfel oder Roulette umsetzen. Für Spiele mit Grafik reicht ein 128×64-OLED, für farbige Rasterspiele eine NeoPixel-LED-Matrix. Simon und der Reaktionstest kommen sogar ganz ohne Display aus, nur mit LEDs und Tastern.
Welches Display eignet sich am besten für Arduino Spiele?
Für Einsteiger ist das 0,96-Zoll-SSD1306-OLED die beste Wahl: Es ist günstig, braucht nur zwei Datenleitungen und zeichnet mit 128×64 Pixeln bewegte Grafik. Willst du ein größeres Farbbild, passt ein ILI9341- oder ST7789-TFT-Display, für Pixel-Art-Spiele eine 8x8- oder 16x16-LED-Matrix. Auch ein 16x2-LCD mit eigenen Zeichen reicht für einfache Runner-Spiele.
Wie speichert man einen Highscore beim Arduino?
Schreibst du den Highscore ins EEPROM, geht er auch bei Stromausfall nicht verloren. Du schreibst ihn mit EEPROM.put(adresse, wert) und liest ihn mit EEPROM.get(adresse, wert); der Arduino Uno hat dafür 1024 Byte. Da jede Zelle nur etwa 100.000-mal beschrieben werden kann, solltest du nur am Spielende schreiben und nur, wenn der Rekord gebrochen wurde.
Kann man mit dem Arduino einen Gamecontroller bauen?
Ja. Boards mit dem ATmega32U4, etwa Arduino Leonardo, Micro oder Pro Micro, können sich am Computer als USB-Tastatur, Maus oder Gamecontroller anmelden; sie lesen Joystick und Taster und senden Tastendrücke. Der Arduino Uno kann das nicht direkt, er kann die Joystick-Werte aber über die serielle Schnittstelle an ein Programm auf dem PC schicken.
Warum ruckelt oder lahmt mein Arduino Spiel?
Die häufigsten Ursachen sind Schleifen mit delay(), Serial.print-Aufrufe in jedem Frame und die Übertragungszeit des Displays. Am Uno dauert es etwa 25 ms, das ganze OLED-Bild zu senden, daher ist eine Framezeit von 30 ms realistisch. Nutze statt delay() eine feste Framezeit mit millis(), entferne serielle Ausgaben und zeichne nur, was nötig ist.
Welches Arduino Spiel ist am einfachsten für Anfänger?
Am einfachsten ist der Reaktionstest: Er braucht eine LED, einen Taster und einen kurzen Sketch, vermittelt aber Grundlagen wie Timing, Zufallszahlen und EEPROM. Danach ist Simon mit vier LEDs und vier Tastern und anschließend Pong und T-Rex auf dem OLED-Display ein guter Lernweg.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
Paddle-Ball-Spiel - SSD1306
Ein Ball, zwei Schläger und du: Du spielst Tischtennis, eines der allerersten Videospiele, auf einem kleinen OLED-Display. Mit zwei Knöpfen bewegst du deinen Schläger rechts nach oben und unten, den l

OLED T-Rex Run: Arduino Dinosaurier Spiel
Das Dinosaurierspiel, das erscheint, wenn das Internet weg ist, jetzt auf deinem Schreibtisch: Der Dino rennt über das kleine OLED-Display, Kakteen kommen auf ihn zu, und du springst mit einem einzige

Pixel-Schlange 8x8 Matrix
Bau das klassische Schlangenspiel selbst und spiel es. Auf einer bunten 8x8-Lichtmatrix steuerst du eine grüne Schlange mit dem Joystick und frisst rote Äpfel; mit jedem Apfel wird die Schlange länger

Matrix Schlange 16x16
Das klassische Schlangenspiel, diesmal auf einem riesigen LED-Display aus 256 Lichtern. Du lenkst die grüne Schlange mit einem Joystick und sammelst rote Äpfel; mit jedem Apfel wird die Schlange länge

Arduino Gedächtnisspiel (4 LEDs + 4 Taster + Summer)
Vertraust du deinem Gedächtnis? Vier bunte LEDs leuchten nacheinander auf, jede mit ihrem eigenen Ton, und du wiederholst die Reihenfolge mit den Tastern. Jede richtige Runde fügt der Folge eine neue

Arduino LCD-Runner-Spiel
Seitlich scrollendes Endlos-Laufspiel, das auf einem 16x2-Zeichen-LCD-Bildschirm läuft. Der Held läuft automatisch, der Spieler springt über Hindernisse, indem er die Taste drückt. Zufällige Hindernisse erscheinen in der oberen und unteren Reihe. Passiver Buzzer spielt Eröffnungsmelodie, Sprunggeräusch, 50-Punkte-Feierton und Spielende-Melodie. Beim Start wird der Splash-Screen "BlueGrays - Arduino Projects" angezeigt.

Arduino Reaktionstest mit Tonsignal: Miss deine Reflexe in Millisekunden (LCD + Buzzer + 3 LEDs)
Wie schnell sind deine Reflexe? Drück den Taster: Die rote LED heißt Auf die Plätze, die gelbe Fertig, dann beginnt eine zufällige Wartezeit von 1,5 bis 3,5 Sekunden. Sobald die grüne LED leuchtet und der Buzzer piept, drückst du erneut; deine Reaktionszeit erscheint in Millisekunden auf dem I2C-LCD, darunter Sitzungsrekord und Durchschnitt. Wer vor Grün drückt, sieht TOO EARLY! und hört einen Verliererton. Gedrückt halten startet einen neuen Spieler mit derselben Wartefolge.
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 200 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

