PCBHub
November 24, 202523 min read 1 views

Arduino LED Racer Game (My Students Love It!) : 4 Steps (with Pictures) - Instructables

pcbhub
pcbhub
Author
Arduino LED Racer Game (My Students Love It!) : 4 Steps (with Pictures) - Instructables

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

Article visual
Article visual
Article visual
Article visual

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

Article visual

Electronic Components:


Materials for Construction:


Step 1: Laser Cut Wood Parts

Article visual
Article visual

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

Article visual
Article visual
Article visual

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

Article visual
Article visual
Article visual
Article visual

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

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);
}

Enjoyed this article?

Support the author by leaving a like.