Introduction: Arduino LED Racer Game (My Students Love It!)




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.
Link to the build video
Tinkercad design
Supplies

Electronic Components:
- Arduino Uno x 1 → Amazon.ca | Amazon.com | Taobao
- WS2812B 5V LED Strip X2 x 5 meters 60 LEDS/Meter → Amazon.ca | Amazon.com | Taobao
- 100mm Large LED push buttonsX2 RED + Green → Amazon.ca | Amazon.com | Taobao
- 5mm LEDS x3→ Amazon.ca | Amazon.com | Taobao
- 5V Buzzer x1→ , Amazon.ca | Amazon.com | Taobao
- 5V >6A Power Supply→ Amazon.ca | Amazon.com | Taobao
Materials for Construction:
- 3mm Wood Sheets (at least 600mm wide and 200mm high)→ Amazon.ca | Amazon.com | Taobao
- Wires and solder → Amazon.ca | Amazon.com
- Super glue→ Amazon.ca | Amazon.com
Step 1: Laser Cut Wood Parts


Download the CAD file and 3D cut out the box using 3mm wood sheets. Glue the box together using hot glue.
Step 2: Solder the Electronics



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.
Step 3: Connect to UNO




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.
Step 4: 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);
}
