Guides · 21 min de lecture

Jeux Arduino : créer Pong, T-Rex, Snake et Simon pas à pas

Jeux Arduino pas à pas : Pong et T-Rex sur OLED, Snake sur matrice LED, Simon, runner sur LCD et test de réaction, avec boucle de jeu et record en EEPROM.

Créer des jeux avec Arduino est la façon la plus amusante d'apprendre l'électronique et la programmation : avec un écran OLED, quelques boutons et un joystick, tu peux fabriquer toi-même des classiques comme Pong, le T-Rex runner, Snake et le jeu de mémoire Simon, puis défier tes amis de battre ton record. En écrivant un jeu, tu apprends presque sans t'en rendre compte les notions les plus importantes de la programmation : la gestion du temps, les machines à états, les systèmes de coordonnées, le calcul des collisions, les nombres aléatoires et la mémoire permanente. Tous ces jeux tournent sur un seul Arduino Uno ; ses 2 Ko de RAM suffisent si tu utilises la mémoire intelligemment.

Dans ce guide, nous construisons six jeux de bout en bout : Pong et le T-Rex runner sur un écran OLED, Snake sur une matrice LED NeoPixel 8x8 et 16x16, Simon avec quatre LED et quatre boutons, un jeu de course sur un LCD 16x2 et un test de réaction précis à la milliseconde. Chaque jeu est accompagné de la liste des composants, du câblage et d'un code qui compile. Tu apprendras d'abord une seule fois le squelette commun à tous, c'est-à-dire la boucle de jeu, la détection de collision, le score et le record sauvegardé en EEPROM, puis tu le réutiliseras dans chaque jeu. À la fin, tu trouveras aussi des versions pour l'ESP32 et le Raspberry Pi Pico.

Jeu Snake Arduino sur une matrice LED NeoPixel 8x8
Matrice LED NeoPixel WS2812B 8x8 : 64 pixels couleur et un seul fil de données, un mini-écran idéal pour Snake.

Quels composants pour quel jeu ?

Quand tu crées un jeu avec Arduino, le premier choix est l'écran, car c'est lui qui détermine le type de jeu possible. Avec seulement quelques LED, tu peux déjà faire Simon ou un test de réaction, tandis qu'un OLED de 128×64 pixels rend possibles Pong et T-Rex, avec des graphismes animés et de la physique. Le tableau résume les jeux de ce guide, du plus facile au plus difficile :

JeuÉcranEntréeAutres composantsDifficulté
Test de réaction1 LED + moniteur série1 boutonRésistance de 220 Ω, buzzer en optionFacile
Jeu de mémoire Simon4 LED de couleur4 boutons4 résistances de 220 Ω, buzzer passifFacile à moyen
Runner sur LCDLCD 16x2 I2C1 boutonBuzzer passifMoyen
PongOLED SSD1306 0,96"Joystick ou potentiomètre de 10 kΩBuzzer passifMoyen
T-Rex runnerOLED SSD1306 0,96"1 bouton ou le bouton du joystickBuzzer passifMoyen
Snake (8x8)Matrice NeoPixel 8x8JoystickRésistance de 330 Ω, condensateur de 1000 µFMoyen à avancé
Snake (16x16)Matrice NeoPixel 16x16JoystickAlimentation externe 5 V de 3 à 4 AAvancé
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

Câblage commun : un seul circuit comme console de jeu

Nous avons choisi des broches communes pour que tous les jeux sauf Simon tournent sur le même circuit. L'OLED et le LCD I2C partagent les deux mêmes fils (SDA et SCL) ; le joystick va sur les entrées analogiques, le bouton d'action sur D3 et le buzzer sur D8. Une fois cette « console » montée, tu changes de jeu simplement en téléversant un autre programme. Tu peux voir le brochage complet de ta carte dans l'outil Pinout.

Composant / brocheArduino UnoESP32Raspberry Pi Pico
OLED ou LCD I2C : SDAA4GPIO 21GP4
OLED ou LCD I2C : SCLA5GPIO 22GP5
Joystick VRx (axe X)A0GPIO 34GP26
Joystick VRy (axe Y)A1GPIO 35GP27
Joystick SW (bouton)D2GPIO 25GP15
Bouton d'action (autre patte à GND)D3GPIO 26GP14
Buzzer passif (+)D8GPIO 27GP16
NeoPixel DIN (via 330 Ω)D6GPIO 13GP22
Sur l'ESP32 et le Pico, alimente le joystick et l'OLED en 3,3 V ; un joystick alimenté en 5 V envoie plus de 3,3 V sur les broches analogiques et peut endommager la carte. Si tu branches un module LCD I2C 5 V sur un ESP32, place un convertisseur de niveau logique entre les deux. Tous les boutons utilisent la résistance de pull-up interne : une patte va à la broche, l'autre à GND, et la broche lit LOW quand on appuie.

Comment fonctionne la boucle de jeu (game loop) ?

Chaque jeu vidéo repose sur une boucle de quatre étapes répétée des dizaines de fois par seconde : lire les entrées (boutons, joystick), mettre à jour le monde (position, vitesse, score), vérifier les collisions et dessiner l'écran. Un tour de boucle s'appelle une image (frame). Trente images par seconde (30 FPS) suffisent pour un rendu fluide, donc chaque image dure environ 33 ms. La fonction loop() d'Arduino est déjà une boucle, mais elle tourne très vite et de façon irrégulière ; il faut donc la découper en images régulières avec millis().

Ici, il est essentiel de ne pas utiliser delay(). Si tu écris delay(33), l'Arduino ne fait rien pendant ces 33 ms, peut rater un appui bref, et la vitesse du jeu change dès que le dessin prend plus ou moins de temps. Avec millis(), loop() lit les entrées à chaque tour mais ne fait avancer le jeu que lorsqu'une nouvelle image est due. La différence entre les deux méthodes est expliquée en détail dans Arduino millis ou delay. Le squelette ci-dessous est la base de tous les jeux de ce guide :

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 deuxième idée clé du code est la machine à états. À tout moment, le jeu se trouve dans l'un des trois états : MENU, PLAYING ou GAME_OVER. Chaque état réagit différemment au même bouton : dans le menu, un appui lance la partie, pendant le jeu il fait sauter le personnage, après le game over il ouvre une nouvelle manche. Découper le code ainsi supprime les if imbriqués et facilite l'ajout d'un nouvel état, comme PAUSE. Nous détaillons le sujet dans machine à états avec Arduino.

Lire des boutons et un joystick

Brancher les boutons avec INPUT_PULLUP est la méthode la plus propre : pas besoin de résistance externe, la broche lit HIGH au repos et LOW quand on appuie. Mais un bouton mécanique rebondit pendant quelques millisecondes à l'appui et au relâchement, et l'Arduino y voit plusieurs appuis successifs. Dans un jeu, cela signifie sauter deux fois pour un seul appui, ou une couleur mal comptée dans Simon. La fonction buttonPressed() du squelette n'accepte un nouvel état que si la broche est restée stable pendant 15 ms, et ne renvoie true qu'une seule fois, au moment de l'appui. Pour le schéma et d'autres méthodes, consulte brancher un bouton sur Arduino.

Un joystick se compose de deux potentiomètres et d'un bouton. Sur l'Uno, chaque axe donne une valeur de 0 à 1023 et, manette relâchée, le centre est d'environ 512, mais cette valeur varie d'un module à l'autre entre 490 et 530 environ. C'est pourquoi les jeux sur grille utilisent des seuils plutôt que des valeurs exactes : en dessous de 300, c'est gauche ou haut ; au-dessus de 700, droite ou bas. La large zone intermédiaire (zone morte) empêche le personnage de dériver tout seul quand tu lâches la manette. Dans Pong, au contraire, on associe directement la valeur analogique à la raquette : map() convertit la plage 0–1023 en course de 0 à 48 pixels de la raquette. L'étalonnage et le tableau des directions sont dans le guide du module joystick.

Comment détecter les collisions ?

La détection de collision vérifie si deux objets se trouvent au même endroit au même moment. Dans les jeux à écran, la méthode la plus courante est le test AABB (boîte englobante alignée sur les axes) : tu considères chaque objet comme un rectangle. Deux rectangles se chevauchent uniquement si quatre conditions sont vraies en même temps : le bord gauche de A est à gauche du bord droit de B, le bord droit de A est à droite du bord gauche de B, et les deux mêmes conditions valent sur l'axe vertical. En code, c'est une seule ligne : ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by. Dans les jeux sur grille (Snake, le runner sur LCD), c'est encore plus simple : si deux cases ont les mêmes coordonnées, il y a collision.

JeuQui entre en collision avec quoi ?Méthode
PongBalle avec les murs et les raquettesAABB + inversion de la vitesse
T-RexDinosaure avec les cactusAABB (avec une boîte un peu plus petite)
SnakeTête avec les murs, le corps et la nourritureÉgalité des cases de la grille
Runner sur LCDCoureur avec un cactusMême colonne et même ligne
SimonCouleur appuyée avec la couleur de la séquenceComparaison d'éléments du tableau
Test de réactionInstant de l'appui avec celui du signalComparaison de temps (faux départ)
Les petits objets rapides peuvent traverser un obstacle en une seule image et rater la collision ; on appelle cela le tunneling. Dans Pong, si la balle avance de plus de 4 pixels par image, elle franchit la zone de contrôle de la raquette de 3 pixels sans jamais la toucher. La solution consiste à garder la vitesse par image inférieure à la largeur de cette zone, et c'est exactement ce que fait la limite MAX_SPEED = 2.8 dans le code de Pong.

Score et record sauvegardé en EEPROM

Le score est stocké dans une variable en RAM et disparaît dès que tu débranches l'Arduino. Pour qu'un record soit permanent, on utilise l'EEPROM : l'ATmega328P de l'Arduino Uno possède 1024 octets d'EEPROM qui gardent leur contenu sans alimentation. Deux octets de type uint16_t suffisent pour un record (0–65535). Le seul point faible de l'EEPROM est son endurance : chaque cellule supporte environ 100 000 écritures. Cela semble beaucoup, mais si tu écris le score à chaque image, soit 30 fois par seconde, la cellule est usée en 55 minutes environ. La bonne méthode consiste à écrire seulement à la fin de la partie, et seulement si le record est battu ; même avec 10 nouveaux records par jour, la cellule tiendra 27 ans.

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

Le code comporte deux subtilités. D'abord, chaque octet d'une EEPROM jamais écrite vaut 0xFF ; au tout premier démarrage, le record s'affiche donc à 65535, et le test sur 0xFFFF le ramène à 0. Pour le test de réaction, c'est l'inverse : 0xFFFF signifie « pas encore de record », car un temps plus court y est meilleur. Ensuite, EEPROM.put() lit chaque octet avant d'écrire et n'écrit que ceux qui ont changé, ce qui prolonge encore la durée de vie. Pour les autres commandes, consulte utiliser l'EEPROM d'Arduino. Pour que tu puisses charger tous les jeux de ce guide l'un après l'autre sur le même Uno, chaque jeu a sa propre adresse :

JeuAdresse EEPROMValeur enregistrée
T-Rex runner0–1Meilleur score
Snake2–3Plus grand nombre de fruits mangés
Runner sur LCD4–5Meilleur score
Test de réaction6–7Temps le plus court (ms)
Simon8–9Niveau le plus élevé atteint

Pong : écran OLED et joystick

Pong est le jeu de tennis de table sorti en 1972 qui a lancé l'ère du jeu vidéo. Dans notre version, tu contrôles la raquette gauche avec l'axe Y du joystick (ou un potentiomètre sur A1), et l'Arduino joue la raquette droite. Composants : un Arduino Uno, un OLED SSD1306 I2C de 0,96 pouce, un joystick et un buzzer passif. En installant la bibliothèque Adafruit SSD1306 depuis le gestionnaire de bibliothèques, installe aussi sa dépendance Adafruit GFX. Le câblage de l'écran et ses commandes de dessin sont expliqués en détail dans écran 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}

Ce qui rend Pong amusant, c'est la fonction hitPaddle(). Si la balle touche le milieu de la raquette, elle repart presque droite ; si elle touche le bord, elle rebondit avec un angle serré et met l'adversaire en difficulté. Pour cela, on ramène la distance entre la balle et le centre de la raquette à une valeur entre -1 et +1, que l'on transforme en vitesse verticale. À chaque frappe, la balle accélère de 5 % dans la limite de MAX_SPEED. La raquette de l'Arduino se déplace au maximum de 1,2 pixel par image ; si tu donnes un angle serré à la balle, elle n'arrive pas à suivre et tu marques le point. Pour corser le jeu, monte cette valeur à 1,5 ; pour jouer à deux, supprime les lignes du CPU et contrôle la deuxième raquette avec un potentiomètre sur A2.

Pourquoi 30 ms par image ? L'Uno envoie le tampon d'image de 1024 octets à l'OLED en I2C à 400 kHz en 25 ms environ ; l'écran ne peut donc pas afficher plus de 35 à 40 images par seconde. Garder la durée d'une image juste au-dessus de ce temps de transfert rend la vitesse du jeu constante. Et ne te laisse pas tromper par le message du compilateur « Global variables use 28% » : le tampon du SSD1306 est alloué à l'exécution, et l'utilisation réelle de la RAM monte à environ 78 %. Si la mémoire manque, tu verras l'erreur SSD1306 allocation failed ; nous avons réuni les solutions dans Arduino mémoire insuffisante.

T-Rex runner : physique du saut et record

Nous portons sur l'écran OLED le jeu du dinosaure que Chrome affiche quand il n'y a pas d'internet. Le dinosaure court sur place, les cactus défilent de droite à gauche et tu sautes par-dessus en appuyant sur le bouton. Pour que le saut paraisse réaliste, nous utilisons une physique simple : à l'appui, la vitesse verticale passe à -4,2 pixels par image, à chaque image la gravité y ajoute 0,3, et la position change de la valeur de la vitesse. Le dinosaure monte donc en ralentissant, reste un instant au sommet puis retombe de plus en plus vite ; il monte d'environ 27 pixels et reste 27 images en l'air, soit environ 0,8 seconde. Les composants sont les mêmes que pour Pong ; pour sauter, tu utilises le bouton sur D3 ou le bouton du 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}
RéglageValeurEffet
JUMP_V-4.2Force du saut ; à -5, le dinosaure monte d'environ 40 pixels, presque jusqu'en haut de l'écran
GRAVITY0.3Une valeur plus grande rend le saut plus court et plus sec
speed2,0 → 5,0Vitesse des cactus ; augmente de 0,25 toutes les 200 images
random(70, 140)pixelsDistance entre deux cactus ; des valeurs plus petites rendent le jeu plus difficile
Boîte de collision10×12 pixelsPlus petite que le dinosaure dessiné de 14×14, les passages de justesse ne comptent pas comme un choc

Le record est écrit en EEPROM au moment où la partie se termine, et seulement s'il est battu ; il s'affiche en haut à droite sous la forme HI. Si tu maintiens le bouton D3 enfoncé à la mise sous tension, le record est remis à zéro, pratique pour lancer un nouveau tournoi entre amis. La hauteur et la largeur des cactus sont tirées avec random(). Pour que chaque partie commence différemment, la ligne randomSeed(analogRead(A3)) utilise comme graine le bruit d'une broche analogique non connectée.

Snake sur une matrice LED (8x8 et 16x16)

Le jeu du serpent rendu célèbre par les anciens téléphones Nokia se joue idéalement sur une matrice LED, car chaque LED est une case du jeu. Nous utilisons une matrice couleur WS2812B NeoPixel : la version 8x8 compte 64 pixels et la version 16x16 en compte 256, tous pilotés par un seul fil de données. Le corps du serpent est stocké dans deux tableaux (snakeX et snakeY), et l'élément 0 est la tête. À chaque pas, le corps avance d'une case et la nouvelle position est écrite dans la tête ; quand le serpent mange, la queue n'est pas effacée, il grandit donc d'une case. Pour compiler, installe la bibliothèque Adafruit NeoPixel ; les détails de câblage sont dans le guide 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}

Le code repose sur trois règles essentielles. Pas de demi-tour : si un serpent qui va vers la droite tourne brusquement à gauche, il se mord le cou ; readJoystick() compare donc la nouvelle direction à la direction actuelle du serpent et non à celle en attente, si bien que même deux mouvements rapides ne peuvent pas le retourner. Exception de la queue : lors d'un pas sans nourriture, le bout de la queue se libère, donc y envoyer la tête n'est pas une collision. Placer la nourriture : placeFood() recommence jusqu'à ce que la nourriture ne tombe pas sur le serpent. Chaque fruit raccourcit le pas de 10 ms (de 300 à 120 ms) ; à la fin de la partie, une LED s'allume en bleu par fruit mangé, et le record est enregistré en EEPROM.

Il faut aussi savoir comment les LED de ta matrice sont câblées. Sur la plupart des cartes rigides 8x8, les LED se suivent ligne par ligne dans le même sens (SERPENTINE = false). Les panneaux souples 16x16 sont généralement câblés en zigzag : une ligne va de gauche à droite, la suivante de droite à gauche. Si le serpent saute en diagonale ou avance à l'envers une ligne sur deux, mets SERPENTINE à true ; si les directions sont inversées, tourne le joystick ou échange les seuils 300 et 700. Pour du 16x16, il suffit de mettre W et H à 16 ; les tableaux du serpent occupent alors 512 octets, le tampon NeoPixel 768 octets, et environ 75 % de la RAM de l'Uno est utilisée.

Matrice LED NeoPixel 16x16
Matrice NeoPixel 16x16 : une aire de jeu de 256 pixels, c'est un serpent plus long et un jeu plus difficile ; elle demande une alimentation externe 5 V.
Une seule WS2812B consomme environ 60 mA en blanc maximal ; les 256 LED d'une matrice 16x16 peuvent atteindre 15 A dans le pire des cas, ce que ni l'USB ni la broche 5V de l'Arduino ne peuvent fournir. Alimente la matrice avec une alimentation externe 5 V de 3 à 4 A, relie le GND de l'alimentation à celui de l'Arduino, et place une résistance de 330 Ω sur la ligne de données et un condensateur de 1000 µF sur l'entrée d'alimentation. Dans le code, setBrightness(30) réduit la luminosité à environ 12 % ; comme peu de LED sont allumées en même temps dans Snake, le courant reste de quelques centaines de mA. Pour une option monochrome qui consomme moins de RAM, consulte matrice LED MAX7219.

Jeu de mémoire Simon : 4 LED et 4 boutons

Simon est le légendaire jeu de mémoire sorti en 1978 : à chaque manche, l'appareil ajoute une couleur à la séquence et tu dois répéter toute la séquence dans le même ordre. Composants : quatre LED de couleur (vert, rouge, jaune, bleu), une résistance de 220 Ω pour chacune, quatre boutons et un buzzer passif. Branche les LED sur D9–D12, les boutons sur D2–D5 et le buzzer sur D8 ; ce jeu est un circuit séparé de la console commune. Tu peux choisir la bonne résistance pour tes LED avec le calculateur de résistance pour LED. Les quatre sons du Simon d'origine sont les notes d'un accord de la majeur ; le code utilise les mêmes notes (330, 440, 554 et 659 Hz), si bien que le jeu reste agréable à l'oreille même quand tu appuies vite.

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}

Dans Simon, le hasard est essentiel : si la séquence est la même à chaque partie, le joueur l'apprend par cœur. La méthode randomSeed(analogRead(A0)), qui lit une broche non connectée, est faible en pratique, car la broche renvoie souvent des valeurs proches. Ce code utilise une astuce plus solide : le jeu attend le premier appui du joueur et prend la valeur de micros() à cet instant comme graine. Personne ne peut reproduire le moment d'un appui à la microseconde près, donc chaque partie démarre différemment. La durée d'affichage diminue de 20 ms par niveau et se stabilise à 180 ms ; si tu n'appuies pas en 3 secondes ou si tu appuies sur la mauvaise couleur, la partie s'arrête, et le niveau atteint est enregistré en EEPROM s'il s'agit d'un record.

Runner sur LCD : caractères personnalisés sur un écran 16x2

Sur un LCD 16x2 à caractères, tu ne peux pas dessiner de pixels, mais createChar() permet de définir jusqu'à 8 caractères personnalisés de 5×8 pixels. Trois suffisent pour le runner : deux poses de course et un cactus. La ligne du bas représente le sol ; les cactus défilent de droite à gauche et, quand tu appuies sur le bouton, le coureur passe sur la ligne du haut et reste en l'air pendant quelques pas. Comme le jeu fonctionne sur une grille, la détection de collision tient en une ligne : s'il y a un cactus dans la colonne du coureur pendant qu'il est au sol, la partie est finie. Pour la bibliothèque et le câblage, consulte écran 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}

Faire défiler le monde consiste à copier chaque élément du tableau ground d'une case vers la gauche ; la colonne de droite reçoit un nouveau cactus avec une probabilité de 35 %. La règle MIN_GAP = 4 laisse au moins quatre colonnes vides entre deux cactus, si bien que chaque obstacle peut être franchi. Tous les 25 points, le pas raccourcit de 15 ms, de 300 à 120 ms. Réécrire les deux lignes à chaque pas plutôt que d'effacer l'écran avec lcd.clear() évite le scintillement ; la commande clear() prend à elle seule environ 2 ms. Si tu ne vois que des carrés, tourne le réglage de contraste au dos du module ; si tu ne vois rien du tout, remplace l'adresse 0x27 par 0x3F.

Test de réaction : mesure ton temps de réaction en millisecondes

C'est le jeu le plus simple et le plus addictif : une LED s'allume à un instant aléatoire, tu appuies sur le bouton le plus vite possible et l'Arduino affiche le temps écoulé en millisecondes dans le moniteur série. Il te faut seulement une LED sur D7 avec une résistance de 220 Ω, le bouton sur D3 et un buzzer en option. L'attente est aléatoire, entre 2 et 5 secondes ; si tu appuies pendant l'attente, tu reçois l'avertissement « trop tôt », donc impossible de tricher en devinant. Le temps est mesuré avec micros(), ta moyenne est calculée et ton meilleur temps est enregistré en 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}

Le temps de réaction moyen d'un humain à la lumière est d'environ 200 à 250 ms ; un joueur bien concentré peut descendre un peu en dessous. Si tu passes USE_SOUND à true, un bip retentit en même temps que la LED. On réagit en général un peu plus vite à un son qu'à une lumière, donc comparer les records des deux modes est une expérience intéressante. Affiche le temps sur un afficheur 7 segments ou un OLED au lieu du moniteur série, et tu obtiens un appareil sur piles qui fonctionne sans ordinateur.

Créer des jeux avec l'ESP32

La plupart des codes de ce guide fonctionnent sur l'ESP32 avec de petites modifications : les bibliothèques Adafruit SSD1306 et NeoPixel sont compatibles avec l'ESP32, et tone() existe dans les versions 2.x et 3.x du core Arduino pour ESP32. Trois choses doivent changer : les broches (colonne ESP32 du tableau commun), les seuils du joystick et la sauvegarde du record. L'ADC de l'ESP32 est sur 12 bits et renvoie des valeurs de 0 à 4095 ; utilise donc environ 1200 et 2800 au lieu de 300 et 700. Branche le joystick sur les broches ADC1, GPIO 32 à 39, car les broches ADC2 ne peuvent pas être lues quand le Wi-Fi est actif.

L'ESP32 n'a pas de véritable EEPROM ; la bibliothèque EEPROM l'émule dans la mémoire flash et exige des appels à EEPROM.begin() et EEPROM.commit(). La solution la plus propre est la bibliothèque Preferences : elle écrit les valeurs sous un espace de noms (namespace) et une clé dans la partition NVS de la flash et répartit l'usure elle-même. Le jeu « course aux clics » ci-dessous compte combien de fois tu peux appuyer sur le bouton en 5 secondes et enregistre le record avec 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) renvoie le second paramètre si rien n'est enregistré ; tu n'as donc jamais à gérer le problème du 0xFFFF de l'EEPROM. Les noms de l'espace et de la clé font au maximum 15 caractères. Même si le processeur de l'ESP32 est bien plus rapide que celui de l'Uno, l'OLED utilise le même bus I2C, donc la fréquence d'images change peu ; pour des jeux beaucoup plus fluides et en couleur, tu peux utiliser un écran TFT ILI9341 en SPI. Le Bluetooth de l'ESP32, lui, est un bon point de départ pour transformer un smartphone en manette.

Jeux avec le Raspberry Pi Pico et MicroPython

Si tu programmes le Raspberry Pi Pico en MicroPython, la logique du jeu reste la même et seule la syntaxe change : time.ticks_ms() et time.ticks_diff() à la place de millis(), read_u16(), qui renvoie 0 à 65535, à la place d'analogRead(), et le PWM à la place de tone(). Le Pico n'a pas d'EEPROM, mais grâce au système de fichiers de MicroPython, il est très simple d'écrire le record dans un petit fichier texte en flash. Le code ci-dessous est la version Pico du test de réaction et garde le record dans le fichier rekor.txt ; enregistre-le sur la carte sous le nom main.py avec Thonny et il démarrera tout seul à chaque mise sous tension. Si tu débutes en MicroPython, lis d'abord qu'est-ce que le 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)

Pour mesurer le temps, il est important d'utiliser ticks_diff() : les compteurs repartent à zéro après une certaine valeur, et une simple soustraction donne alors un résultat faux ; ticks_diff() gère correctement ce débordement. Pour les jeux sur OLED, le pilote ssd1306 que tu installes depuis le gestionnaire de paquets de Thonny propose des commandes comme fill_rect(), text() et show(), proches d'Adafruit GFX. Le module neopixel nécessaire pour une matrice NeoPixel est déjà inclus dans MicroPython. Tu peux ainsi porter aussi Pong et Snake sur le Pico avec la même logique.

Erreurs fréquentes et solutions

SymptômeCauseSolution
L'écran reste videSi le moniteur série affiche « SSD1306 allocation failed », la RAM manque ; sinon, l'adresse ou le câblage est fauxMets les textes fixes dans F() et réduis les grands tableaux ; sans ce message, vérifie les fils SDA/SCL et essaie l'adresse 0x3D
Le jeu démarre toujours de la même façonPas de randomSeed() ou toujours la même graineInitialise avec la valeur de micros() au premier appui du joueur
Un appui provoque deux sautsRebond du boutonUtilise la fonction buttonPressed(), qui attend 15 ms de stabilité
Le record affiche 65535Une EEPROM jamais écrite lit 0xFFConsidère 0xFFFF comme 0
Le personnage dérive quand on lâche le joystickLe centre n'est pas à 512 et la zone morte est trop étroiteGarde les seuils 300/700 ou mesure le centre au démarrage pour l'étalonner
Le serpent saute en diagonaleLa matrice est câblée en zigzagMets SERPENTINE = true
L'Arduino redémarre quand la luminosité augmenteLa matrice LED consomme trop de courantBaisse setBrightness() et utilise une alimentation externe 5 V
Le PWM de la broche 3 ou 11 se dérègle quand tone() joueSur l'Uno, tone() utilise le Timer2Déplace le composant qui a besoin du PWM sur la broche 5, 6, 9 ou 10
Le jeu ralentit au fil du tempsSerial.print ou dessin inutile à chaque imageSupprime les sorties série et garde la durée d'image au-dessus du temps de transfert de l'écran
Le LCD n'affiche que des carrésMauvais réglage du contraste ou mauvaise adresseTourne le réglage au dos et essaie l'adresse 0x27 ou 0x3F
Si un jeu ne se comporte pas comme prévu, teste d'abord la logique sans écran : affiche la position, la vitesse et l'état à chaque image dans le moniteur série et suis quelques images à la main. Une fois le bug trouvé, supprime ces lignes, car Serial.print coûte quelques millisecondes par image et fausse la vitesse du jeu. Si le code ne se téléverse même pas sur la carte, consulte le guide Arduino : le téléversement ne fonctionne pas.

Idées pour aller plus loin

  • Pong à deux : supprime les lignes du CPU, contrôle la deuxième raquette avec un potentiomètre sur A2 et place les deux manettes aux deux bouts de la table avec de longs câbles.
  • Contrôle par inclinaison : utilise un MPU6050 au lieu du joystick et dirige le serpent ou la raquette en inclinant la carte.
  • Musique et effets sonores : joue une musique de jeu avec un DFPlayer Mini ; pour de courts effets, un buzzer passif suffit.
  • Jouer avec le téléphone : transforme ton téléphone en manette sans fil avec un module Bluetooth HC-05.
  • Manette pour ordinateur : comme l'Arduino Leonardo peut se comporter comme un clavier USB, tu peux fabriquer une manette pour jeux PC avec un joystick et des boutons.
  • Tableau des scores : affiche les records en texte défilant sur un module MAX7219 4 en 1.
  • Jeux de hasard : fabrique une roulette avec un anneau WS2812B ou un dé électronique avec des LED ; la façon la plus amusante de pratiquer random().

Créer des jeux avec Arduino est la manière la plus agréable d'apprendre le code et le matériel en même temps. Une fois la boucle de jeu, la détection de collision et le record en EEPROM compris, concevoir ton propre jeu n'est plus qu'une question d'idée ; pour trouver l'inspiration, parcours la liste d'idées de projets Arduino. Dans l'application BlueGrays, tu trouveras Pong, le T-Rex sur OLED, Snake sur matrices 8x8 et 16x16, Simon, le runner sur LCD et le test de réaction sous forme de projets pas à pas, avec schémas de câblage et code prêt à l'emploi.

Questions fréquentes

Quels jeux peut-on créer avec Arduino ?

Avec un Arduino Uno, tu peux créer Pong, le T-Rex runner, des jeux façon Flappy Bird, Snake, des jeux de blocs simples, le jeu de mémoire Simon, un test de réaction et des jeux de hasard comme un dé ou une roulette. Un OLED de 128×64 suffit pour les jeux graphiques et une matrice LED NeoPixel pour les jeux colorés sur grille. Simon et le test de réaction se jouent même sans écran, avec seulement des LED et des boutons.

Quel est le meilleur écran pour un jeu Arduino ?

Pour débuter, le meilleur choix est l'OLED SSD1306 de 0,96 pouce : il est bon marché, se branche avec seulement deux fils de données et affiche des graphismes animés en 128×64 pixels. Pour une image plus grande et en couleur, un écran TFT ILI9341 ou ST7789 convient, et pour les jeux façon pixel art, une matrice LED 8x8 ou 16x16. Un LCD 16x2 avec des caractères personnalisés suffit aussi pour des jeux de course simples.

Comment sauvegarder un meilleur score sur Arduino ?

Si tu écris le record dans l'EEPROM, il n'est pas perdu en cas de coupure de courant. Tu l'écris avec EEPROM.put(adresse, record) et tu le lis avec EEPROM.get(adresse, record) ; l'Arduino Uno dispose pour cela de 1024 octets. Comme chaque cellule supporte environ 100 000 écritures, écris seulement à la fin de la partie et si le record est battu.

Peut-on fabriquer une manette de jeu avec Arduino ?

Oui. Les cartes équipées de l'ATmega32U4, comme l'Arduino Leonardo, le Micro ou le Pro Micro, peuvent se présenter à l'ordinateur comme un clavier, une souris ou une manette USB ; elles lisent le joystick et les boutons et envoient des appuis de touches. L'Arduino Uno ne peut pas le faire directement, mais il peut envoyer les valeurs du joystick par le port série à un programme sur l'ordinateur.

Pourquoi mon jeu Arduino est-il lent ou saccadé ?

Les causes les plus fréquentes sont les boucles construites avec delay(), les appels à Serial.print à chaque image et le temps de transfert de l'écran. Sur l'Uno, envoyer toute l'image de l'OLED prend environ 25 ms, donc une durée d'image de 30 ms est réaliste. Utilise une durée d'image fixe avec millis() au lieu de delay(), supprime les sorties série et ne dessine que le nécessaire.

Quel est le jeu Arduino le plus facile pour débuter ?

Le test de réaction est le plus facile : il demande une LED, un bouton et un programme court, mais il enseigne des notions de base comme la gestion du temps, les nombres aléatoires et l'EEPROM. Ensuite, un bon parcours consiste à passer à Simon avec quatre LED et quatre boutons, puis à Pong et T-Rex sur un écran OLED.

Voir tous les projets
Jeu de Raquettes - SSD1306
#86Intermédiaire
Premium

Jeu de Raquettes - SSD1306

Une balle, deux raquettes et toi : tu joues au tennis de table, l'un des tout premiers jeux vidéo, sur un petit écran OLED. Deux boutons font monter et descendre ta raquette à droite, tandis que l'ord

4.5(47)
65
OLED T-Rex Run : Jeu de dinosaures Arduino
#87Avancé
Premium

OLED T-Rex Run : Jeu de dinosaures Arduino

Le jeu du dinosaure qui apparaît quand internet est coupé, maintenant sur ton bureau : le dinosaure court sur le petit écran OLED, les cactus foncent sur lui et tu sautes avec un seul bouton. Ton scor

4.7(33)
53
Serpent à pixels Matrice 8x8
#88Avancé
Premium

Serpent à pixels Matrice 8x8

Construis de tes mains le jeu classique du serpent et joue. Sur une matrice lumineuse colorée de 8x8, tu guides un serpent vert avec un joystick et tu manges des pommes rouges ; à chaque pomme, le ser

4.7(39)
57
Matrix Snake 16x16
#89Avancé
Premium

Matrix Snake 16x16

Le jeu du serpent classique, cette fois sur un immense écran LED de 256 lumières. Tu guides le serpent vert avec un joystick et ramasses les pommes rouges ; à chaque pomme, le serpent s'allonge, ton s

4.8(52)
67
Jeu de mémoire avec Arduino (4 LED + 4 boutons + buzzer)
#93Avancé
Premium

Jeu de mémoire avec Arduino (4 LED + 4 boutons + buzzer)

Tu as confiance en ta mémoire ? Quatre LED de couleur s'allument dans un certain ordre, chacune avec sa propre note, et tu dois refaire la même suite en appuyant sur les boutons. Chaque manche réussie

4.8(46)
69
Jeu Arduino LCD Runner
#98Intermédiaire
Premium

Jeu Arduino LCD Runner

Jeu de course sans fin à défilement latéral fonctionnant sur un écran LCD de 16x2 caractères. Le héros court automatiquement, le joueur saute par-dessus les obstacles en appuyant sur le bouton. Des obstacles aléatoires apparaissent sur les rangées du haut et du bas. Un buzzer passif joue la mélodie d'ouverture, le son de saut, le son de célébration des 50 points et la mélodie de fin de partie. L'écran d'accueil "BlueGrays - Arduino Projects" s'affiche au démarrage.

4.6(32)
55
Arduino Test de Réaction avec Signal Sonore : Mesure tes Réflexes en Millisecondes (LCD + Buzzer + 3 LED)
#97Intermédiaire
Premium

Arduino Test de Réaction avec Signal Sonore : Mesure tes Réflexes en Millisecondes (LCD + Buzzer + 3 LED)

Tes réflexes sont-ils rapides ? Appuie sur le bouton : la LED rouge dit à vos marques, la jaune prêts, puis une attente aléatoire de 1,5 à 3,5 secondes commence. Dès que la LED verte s'allume et que le buzzer bipe, appuie à nouveau ; ton temps de réaction s'affiche en millisecondes sur l'écran LCD I2C, avec ton record et ta moyenne en dessous. Appuie avant le vert et l'écran affiche TOO EARLY! avec un son de défaite. Maintiens appuyé : nouveau joueur, même ordre d'attentes.

4.6(33)
50