Arduino LED Racer Game (My Students Love It!)

by NewsonsElectronics in Circuits > Arduino

236 Views, 5 Favorites, 0 Comments

Arduino LED Racer Game (My Students Love It!)

thumbnail.jpg
My Students LOVE This Arduino LED Racing Game!
9. ON the wall.jpg
10. The race.jpg

I built a simple Arduino LED Racer Game to mount outside my classroom — and the students can’t stop playing it! This build uses two push buttons to control each player’s LED, and a random blue LED boost that gives players a surprise speed advantage during the race. This is a fun, easy electronics project perfect for: • Classrooms • School hallways • Maker projects • STEM challenges • Beginner Arduino builds


Link to the build files on google drive.

https://drive.google.com/drive/folders/1wmG4bFRxWrWwNnB8UCCmns_-aNVCjvR8?usp=sharing


Link to the build video

https://youtu.be/gnp0eAMo8CE


Tinkercad design

https://www.tinkercad.com/things/8E99X8R75Ys/editel?returnTo=%2Fdashboard&sharecode=a8DgkWgxdweH1VU9lzTOLk4WWt5w7nW3A4f3EczTte8

Supplies

Schematic.jpg

Laser Cut Wood Parts

1.cad files.jpg
2. Assembly.jpg

Download the CAD file and 3D cut out the box using 3mm wood sheets. Glue the box together using hot glue.

Solder the Electronics

4. Jumper wire.jpg
5. LED wiring.jpg
6. Join led Strips.jpg

Solder a jumper wire from the input jack to the 5V pin on the ISP header of the Arduino UNO.

Solder the three LEDs with a common ground and include a 1 kΩ resistor in the ground line.

Connect the two reals of LED strips together depending on the length of your display.


Connect to UNO

7. UNO wiring.jpg
Schematic.jpg
8. Inside the box.jpg
9. ON the wall.jpg

Connect the button pins to digital pins 4 and 5.

Connect the speaker between GND and A0.

Connect the LEDs to pins A5, A4, and A3.

Put the top of the box on and test to see that everything is working correctly.

Upload the Code

// Newson's Electronics
// LED racer game
// November 11,2025



#include <Adafruit_NeoPixel.h>

#define LED_PIN 11
#define LED_COUNT 275
#define LED_RED A5
#define LED_GREEN A3
#define LED_YELLOW A4

#define BUTTON1_PIN 4
#define BUTTON2_PIN 5
#define BUZZER1_PIN A0 // single buzzer for both players

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

const unsigned long GAME_TIMEOUT = 1UL * 60UL * 1000UL; // 5 minutes in ms

// Colors
uint32_t red = strip.Color(255, 0, 0);
uint32_t green = strip.Color(0, 255, 0);
uint32_t blue = strip.Color(0, 0, 255);

// Player variables
float pos1=0, speed1=0;
float pos2=0, speed2=0;
unsigned long lastPress1=0, lastPress2=0;
unsigned long interval1=0, interval2=0;
bool pushed1=false, pushed2=false;
int redLaps=0, greenLaps=0;

// LEDs
int ledLengthRed = 4, ledLengthGreen = 4;
int number = 0; // boost LED

// Sound
unsigned long lastUpdate=0;
unsigned long soundInterval=50;

// Race state
bool raceFinished=false;
int winner=0; // 1 = player1, 2 = player2
bool waitForRestart=false;
bool rainbowRunning=false;

// Flash state
unsigned long lastFlash=0;
bool flashState=false;
int flashCount=0;
const int maxFlashes=10;

// Rainbow variables
uint16_t rainbowOffset=0;

// Inactivity timeout
unsigned long lastButtonPressTime=0;

void setup() {
Serial.begin(9600);
randomSeed(analogRead(A2));

pinMode(BUZZER1_PIN, OUTPUT);
pinMode(BUTTON1_PIN, INPUT_PULLUP);
pinMode(BUTTON2_PIN, INPUT_PULLUP);
pinMode(LED_RED, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_GREEN, OUTPUT);

strip.begin();
strip.setBrightness(60);
strip.clear();
strip.show();

number = random(1, LED_COUNT);

startCountdown();
Serial.println("Two-player LED momentum game ready!");

// Initialize inactivity timer
lastButtonPressTime = millis();
}

// --- Player input and speed updates ---
void handlePlayers(unsigned long now){
int state1=digitalRead(BUTTON1_PIN);
if(state1==LOW && !pushed1) pushed1=true;
if(state1==HIGH && pushed1 && redLaps < 20){
pushed1=false;
lastPress1=now;
speed1+=0.2;
}
if(!pushed1) interval1=now-lastPress1;

int state2=digitalRead(BUTTON2_PIN);
if(state2==LOW && !pushed2) pushed2=true;
if(state2==HIGH && pushed2 && greenLaps < 20){
pushed2=false;
lastPress2=now;
speed2+=0.2;
}
if(!pushed2) interval2=now-lastPress2;
}

// --- Movement and friction ---
void handleMovement(){
pos1 += speed1*0.02;
pos2 += speed2*0.02;

if(interval1>300 || redLaps>20) speed1*=0.95;
if(interval2>300 || greenLaps>20) speed2*=0.95;

if(pos1>=LED_COUNT){ pos1=0; redLaps++; }
if(pos1<0) pos1+=LED_COUNT;
if(pos2>=LED_COUNT){ pos2=0; greenLaps++; }
if(pos2<0) pos2+=LED_COUNT;
}

// --- Boost LEDs ---
void handleBoosts(){
for(int i=0;i<ledLengthRed;i++){
int idx=((int)pos1+i)%LED_COUNT;
if(idx==number){ number=random(1,LED_COUNT); speed1+=5; }
}
for(int i=0;i<ledLengthGreen;i++){
int idx=((int)pos2+i)%LED_COUNT;
if(idx==number){ number=random(1,LED_COUNT); speed2+=5; }
}
}

// --- Draw LEDs ---
void drawLEDs(){
strip.clear();
for(int i=0;i<ledLengthRed;i++) strip.setPixelColor(((int)pos1+i)%LED_COUNT, red);
for(int i=0;i<ledLengthGreen;i++) strip.setPixelColor(((int)pos2+i)%LED_COUNT, green);
strip.setPixelColor(number, blue);
strip.show();
}

// --- Engine sound based on combined speeds ---
void handleEngineSound(){
unsigned long now=millis();
if(now - lastUpdate < soundInterval) return;
lastUpdate=now;

float combinedSpeed = speed1 + speed2;
int freq = map(combinedSpeed,0,100,0,1000);
if (combinedSpeed>100) freq=1000;
if(combinedSpeed<1) noTone(BUZZER1_PIN);
else tone(BUZZER1_PIN,freq);
}

// --- Check finish ---
void checkFinish(){
if(redLaps>=20 && !raceFinished){
raceFinished=true;
winner=1;
Serial.println("Player 1 wins!");
flashCount=0;
lastFlash=millis();
}
if(greenLaps>=20 && !raceFinished){
raceFinished=true;
winner=2;
Serial.println("Player 2 wins!");
flashCount=0;
lastFlash=millis();
}
if(raceFinished){
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
noTone(BUZZER1_PIN);
}
}

// --- Winner flash with melody ---
void handleWinnerFlash(unsigned long now){
if(now - lastFlash > 500){
lastFlash = now;
flashState = !flashState;

strip.clear();
strip.show();

if(flashState){
uint32_t color = (winner==1)? red : green;
for(int i=0;i<LED_COUNT;i++) strip.setPixelColor(i, color);
tone(BUZZER1_PIN, 1000, 150); // C note
delay(150);
noTone(BUZZER1_PIN);

tone(BUZZER1_PIN, 1200, 150); // D note
delay(150);
noTone(BUZZER1_PIN);

tone(BUZZER1_PIN, 1500, 300); // G note
delay(300);
noTone(BUZZER1_PIN);
}
strip.show();
delay(200);
flashCount++;
}

if(flashCount>=maxFlashes){
raceFinished=false;
rainbowRunning=true;
}
}

// --- Rainbow effect ---
void showRainbow(){
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, LOW);
for(int i=0;i<LED_COUNT;i++){
strip.setPixelColor(i, Wheel((i*256/LED_COUNT + rainbowOffset) & 255));
}
strip.show();
rainbowOffset++;
delay(20);
}

// --- Color wheel for rainbow ---
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

// --- Start countdown ---
void startCountdown() {
// Reset game variables
speed1 = speed2 = pos1 = pos2 = 0;
redLaps = greenLaps = 0;
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, LOW);
number = random(1, LED_COUNT);

// Countdown sequence: RED -> YELLOW -> GREEN
int redTones[] = {400, 450}; // Two beep tones for RED
int redDelays[] = {500, 500}; // Duration of each step
// Flash RED LED and beep
for (int i = 0; i < 2; i++) {
digitalWrite(LED_RED, HIGH);
tone(BUZZER1_PIN, redTones[i], 300);
delay(redDelays[i]);
digitalWrite(LED_RED, LOW);
delay(200);
}

// Flash YELLOW LED and beep
digitalWrite(LED_YELLOW, HIGH);
tone(BUZZER1_PIN, 500, 300);
delay(500);
digitalWrite(LED_YELLOW, LOW);
delay(200);

// Turn GREEN LED ON with a long beep
digitalWrite(LED_GREEN, HIGH);
tone(BUZZER1_PIN, 800, 600);
delay(600);
delay(400);
noTone(BUZZER1_PIN);
}

void loop() {
unsigned long now = millis();

// --- Track button presses ---
if (digitalRead(BUTTON1_PIN) == LOW || digitalRead(BUTTON2_PIN) == LOW) {
lastButtonPressTime = now; // reset timer on button press
}

// --- Check 5-minute inactivity timeout ---
if (!raceFinished && !waitForRestart && !rainbowRunning) {
if (now - lastButtonPressTime > GAME_TIMEOUT) {
// End game automatically
raceFinished = true;
waitForRestart = true;
rainbowRunning = false;
strip.clear();
strip.show();
Serial.println("Game ended due to inactivity!");
}
}

// --- Game logic ---
if (!rainbowRunning && !raceFinished && !waitForRestart) {
handlePlayers(now);
handleMovement();
handleBoosts();
drawLEDs();
handleEngineSound();
checkFinish();
}
else if (raceFinished) {
handleWinnerFlash(now);
}
else if (rainbowRunning) {
showRainbow();
if (digitalRead(BUTTON1_PIN) == LOW || digitalRead(BUTTON2_PIN) == LOW) {
rainbowRunning = false;
strip.clear();
strip.show();
startCountdown();
}
}
else if (waitForRestart) {
if (digitalRead(BUTTON1_PIN) == LOW || digitalRead(BUTTON2_PIN) == LOW) {
waitForRestart = false;
startCountdown();
}
}

delay(10);
}