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