Jeu de tennis Arduino : Raquette virtuelle avec NeoPixel, capteurs et Bluetooth
Composants et fournitures
![]() |
|
× | 2 | |||
![]() |
|
× | 1 | |||
![]() |
|
× | 1 | |||
![]() |
|
× | 1 | |||
![]() |
|
× | 1 | |||
![]() |
|
× | 1 |
Outils nécessaires
![]() |
|
Applications et services en ligne
![]() |
|
À propos de ce projet
Ce projet comporte 3 parties : l'affichage (plateau de jeu), la raquette (manette), et le routeur maître (script Python).
Partie 1 – L'affichage
L'affichage est une matrice NeoPixel 15×10 pilotée par un Arduino Nano. Il gère la logique du jeu et reçoit les commandes du script Python sur l'ordinateur hôte. Une balle grandit et descend dans la matrice, donnant une illusion de profondeur. Si le joueur la frappe, elle repart dans l'autre sens.

Points
La direction horizontale de la balle change à chaque frappe. Si le joueur la manque, l'ordinateur marque un point ; si l'ordinateur la manque (25 % de chance), le joueur marque. Le score s'affiche quand l'ordinateur marque.

Meilleur score
Un meilleur score est stocké dans une EEPROM I2C à l'adresse 0x04. S'il est battu, l'adresse est mise à jour. Si l'ordinateur atteint 10 points, c'est la fin de partie.

Fabriquer la matrice
La construction de la matrice prend du temps. Suivez le guide illustré.


Partie 2 – La raquette (manette)
La raquette utilise un second Arduino Nano avec un capteur 9DoF (3,3 V uniquement !) et un module HC-05 BLE. Si l'accélération sur l'axe X ou Y dépasse un seuil, elle envoie « 1 » via Bluetooth au PC hôte, qui le relaie à l'affichage. Les lectures sont effectuées à intervalle régulier, avec un seuil à 1,5 g.
Pour accéder au capteur I2C 9DoF, installez la bibliothèque SparkFun (lien dans le projet original).
La raquette est faite d'une boîte et d'un carton découpé en forme de raquette, recouvert de papier blanc avec un dessin. Alimentation par batterie 9V sur la broche Vin du Nano (important : ne pas alimenter directement en 9V non régulé !).

Communication Bluetooth
La raquette et l'affichage ne communiquent pas directement. Un script Python sur le PC utilise les deux ports série (Bluetooth et matrice) pour relayer l'information et synchroniser les deux systèmes (reset au démarrage des ports série). La raquette envoie « 1 » → Python → Matrice. Un anti-rebond de 1 s évite la saturation des buffers (32 octets).
Construction de la raquette
Cliquez sur les images pour suivre la construction pas à pas :






Amusez-vous bien avec votre jeu de tennis !
Code
Code Matrice (C/C++)
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <EEPROM.h>
#define PIN 6 // Broche data matrice
#define EEPROM_ADR 0x50 // Adresse I2C EEPROM
#define HS_ADR 0x02 // Adresse high-score dans EEPROM
#define NOTE_C1 33
#define SPKR_PIN 3
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(15, 10, PIN,
NEO_MATRIX_BOTTOM + NEO_MATRIX_LEFT + NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800);
int ball_x = 7, ball_y = 2;
int radius = 1, r_incr = 1;
bool isSwung = false;
int ball_x_dir = 1, ball_y_dir = 1;
bool isDirRight = true;
int score = 0, COM_score = 0, highscore = 0;
int framerate = 50;
int serial_data;
String score_string = "";
uint16_t colors[] = {matrix.Color(255,0,0), matrix.Color(0,255,0), matrix.Color(150,200,0)};
int melody[] = {0}, tempo[] = {0};
static unsigned long lastFrame = 0;
void setup() {
Serial.begin(9600);
matrix.begin();
matrix.fillScreen(0);
matrix.setTextColor(colors[1]);
randomSeed(analogRead(A2));
display_scores();
highscore = read_HS();
Serial.println(highscore, DEC);
matrix.setCursor(0,1);
matrix.print("HS: "); matrix.show(); delay(1000);
matrix.fillScreen(0);
matrix.setCursor(0,1);
matrix.print(highscore, DEC); matrix.show(); delay(1000);
score_string = "";
}
void loop() {
if ((lastFrame + framerate) < millis()) {
update_frame();
lastFrame = millis();
}
}
void update_frame() {
serial_data = Serial.parseInt();
if (serial_data) isSwung = true;
else isSwung = false;
if (ball_y >= 5 && ball_y < 7 && isSwung) {
if (isDirRight) { ball_x_dir = -1; ball_y_dir = -1; }
else { ball_x_dir = 1; ball_y_dir = -1; }
r_incr = -1;
}
else if (ball_y >= 8) { COM_score += 1; end_round(); }
else if (ball_y <= 2) {
isDirRight = !isDirRight;
int randNum = random(4);
Serial.println(randNum);
if (randNum == 2) { // 25% chance COM manque
score += 1;
if (score > highscore) write_HS();
end_round();
} else {
if (isDirRight) { ball_x_dir = 1; ball_y_dir = 1; }
else { ball_x_dir = -1; ball_y_dir = 1; }
r_incr = 1;
}
}
ball_x += ball_x_dir;
ball_y += ball_y_dir;
radius += r_incr;
matrix.fillScreen(0);
matrix.fillCircle(ball_x, ball_y, radius, colors[2]);
matrix.show();
}
void end_round() {
if (COM_score >= 10) end_game();
isDirRight = true; r_incr = 1;
ball_x_dir = 1; ball_y_dir = 1;
ball_x = 7; ball_y = 2; radius = 1;
display_scores();
matrix.fillScreen(0);
matrix.fillCircle(ball_x, ball_y, radius, colors[2]);
matrix.show();
}
void end_game() {
matrix.fillScreen(0);
matrix.setCursor(0,1);
matrix.setTextColor(colors[0]);
matrix.drawLine(3,0,12,9,colors[0]);
matrix.drawLine(11,0,2,9,colors[0]);
matrix.show(); delay(500);
while(1) {}
}
void display_scores() {
matrix.fillScreen(0);
matrix.setTextColor(colors[1]);
matrix.setCursor(0,1);
score_string = String(score) + "-" + String(COM_score);
scrollText(score_string);
matrix.fillScreen(0); delay(2000); matrix.show();
}
void scrollText(String text) {
int x = matrix.width();
for (int i=0; i<24; i++) {
matrix.fillScreen(0);
matrix.setCursor(x,2);
matrix.print(text);
x -= 1; matrix.show(); delay(150);
}
}
void write_HS() { EEPROM.write(0x04, int(score)); }
int read_HS() { return EEPROM.read(0x04); }
Code Raquette (C/C++)
#include <Wire.h>
#include <SPI.h>
#include <SparkFunLSM9DS1.h>
LSM9DS1 imu;
#define LSM9DS1_M 0x1E
#define LSM9DS1_AG 0x6B
#define PRINT_SPEED 10
static unsigned long lastPrint = 0;
float accelx = 0, accely = 0;
void setup() {
Serial.begin(9600);
imu.settings.device.commInterface = IMU_MODE_I2C;
imu.settings.device.mAddress = LSM9DS1_M;
imu.settings.device.agAddress = LSM9DS1_AG;
if (!imu.begin()) { while (1); }
}
void loop() {
if ((lastPrint + PRINT_SPEED) < millis()) {
if (imu.accelAvailable()) imu.readAccel();
lastPrint = millis();
}
accelx = imu.calcAccel(imu.ax);
accely = imu.calcAccel(imu.ay);
if (accelx <= -1.5 || accelx >= 1.5) { Serial.print(1); delay(600); }
else if (accely <= -1.5 || accely >= 1.5) { Serial.print(1); delay(600); }
}
Code Python (Relais)
import serial, time
matrix_port = "COM3"
raquet_port = "COM9"
matrix = serial.Serial(matrix_port, 9600)
racquet = serial.Serial(raquet_port, 9600)
time.sleep(10)
while True:
data = racquet.read()
print(data)
if data == "1":
print("hit")
matrix.write("1")
time.sleep(1)
time.sleep(0.05)
Schémas
Processus de fabrication
- Détecteur de langue TinyML avec Edge Impulse sur Arduino Nano 33 BLE Sense
- Construire un jeu gyroscopique temps réel avec Arduino Nano et capteur MPU-6050
- Dé numérique Arduino sur écran LCD 16x2 – Générateur aléatoire à bouton poussoir
- Construire un Iron Man animé avec Arduino : capteurs, sons et relais
- Find Me : Localisateur d'objets intelligent avec Arduino et Bluetooth
- Contrôleur d'humidificateur DIY avec Arduino et relais – Installation haute tension sécurisée
- Mesure de distance en temps réel par sonar Arduino et visualisation Processing
- MobBob : Construisez votre propre robot Arduino, contrôlé via smartphone Android
- Construire une lampe de bureau réactive à l'audio avec Arduino Nano







