Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Mirko Pavleski
Published © GPL3+

Tug of War Arduino Game on WS2812 Led strip

This is an interesting and simple to make two player Arduino game that can be made in less than a day.

BeginnerFull instructions provided2 hours169
Tug of War Arduino Game on WS2812 Led strip

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
WS2812 Addressable LED Strip
Digilent WS2812 Addressable LED Strip
×1
General Purpose Transistor NPN
General Purpose Transistor NPN
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×2
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×4
Resistors
×2
Buzzer
Buzzer
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

Code

C/C++
...
#include <Adafruit_NeoPixel.h>

// Pin definitions
#define LED_PIN 6
#define BUTTON1_PIN 2        // Player 1 button
#define BUTTON2_PIN 3        // Player 2 button
#define BUZZER_PIN 4         // Buzzer pin
#define LED_INTENSITY_POT A0 // Potentiometer for LED brightness
#define SPEED_POT A1        // Potentiometer for game speed
#define POWER1_PIN 7        // Player 1 power move button
#define POWER2_PIN 8        // Player 2 power move button

// LED strip configuration
#define NUM_LEDS 60         // Total number of LEDs (29 + 2 + 29)
#define CENTER_POS 30       // Center position (0-based index)

// Game constants
#define DEBOUNCE_TIME 50    // Button debounce time in milliseconds
#define VICTORY_FLASHES 3   // Number of victory flashes
#define FLASH_DELAY 200     // Delay between flashes in milliseconds
#define MIN_CLICK_SPEED 1   // Minimum pixels to move per click
#define MAX_CLICK_SPEED 5   // Maximum pixels to move per click

// Power move constants
#define POWER_BOOST 3       // Fixed amount added to speed during power move
#define POWER_MOVES_COUNT 3 // Number of powered moves each player gets
#define POWER_FLASH_DURATION 500 // Duration of power move activation flash
#define MAX_POWER_SPEED (MAX_CLICK_SPEED + 2) // Maximum speed during power move

// Initialize LED strip
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// Colors
uint32_t COLOR_RED;
uint32_t COLOR_BLUE;
uint32_t COLOR_YELLOW;
uint32_t COLOR_MAGENTA;
const uint32_t COLOR_OFF = strip.Color(0, 0, 0);

// Game state variables
int flagPosition = CENTER_POS;  // Current position of the center of the red flag
unsigned long lastButton1Press = 0;
unsigned long lastButton2Press = 0;
bool gameActive = false;  // Changed to false initially
int clickSpeed = 1;      // Number of pixels to move per click

// Power move state variables
bool player1PowerActive = false;
bool player2PowerActive = false;
int player1PowerMovesLeft = 0;
int player2PowerMovesLeft = 0;
bool player1PowerAvailable = true;  // Can only use once per round
bool player2PowerAvailable = true;  // Can only use once per round
unsigned long lastPower1Press = 0;
unsigned long lastPower2Press = 0;

bool button1LastState = HIGH;
bool button2LastState = HIGH;
bool power1LastState = HIGH;
bool power2LastState = HIGH;

void setup() {
  // Initialize LED strip
  strip.begin();
  strip.show();
  
  // Set up buttons with internal pull-up resistors
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  pinMode(BUTTON2_PIN, INPUT_PULLUP);
  pinMode(POWER1_PIN, INPUT_PULLUP);
  pinMode(POWER2_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_INTENSITY_POT, INPUT);
  pinMode(SPEED_POT, INPUT);

    button1LastState = digitalRead(BUTTON1_PIN);
    button2LastState = digitalRead(BUTTON2_PIN);
    power1LastState = digitalRead(POWER1_PIN);
    power2LastState = digitalRead(POWER2_PIN);
  
  // Initial game state
  resetGame();
}

void loop() {
  if (!gameActive) {
    playStartSequence();
  }
  
  if (gameActive) {
    // Update LED brightness based on potentiometer
    updateColors();
    
    // Update game speed based on potentiometer
    updateGameSpeed();
    
    // Check for power moves
    checkPowerMoves();
    
    // Check for button presses with debounce
    checkButtons();
    updateLEDs();
  }
}

void checkPowerMoves() {
    // Read current power button states
    bool power1CurrentState = digitalRead(POWER1_PIN);
    bool power2CurrentState = digitalRead(POWER2_PIN);
    
    // Check Player 1 power move button
    if (power1CurrentState == LOW && power1LastState == HIGH) {  // Detect only falling edge
        if (millis() - lastPower1Press > DEBOUNCE_TIME && player1PowerAvailable) {
            activatePowerMove(1);
            lastPower1Press = millis();
        }
    }
    
    // Check Player 2 power move button
    if (power2CurrentState == LOW && power2LastState == HIGH) {  // Detect only falling edge
        if (millis() - lastPower2Press > DEBOUNCE_TIME && player2PowerAvailable) {
            activatePowerMove(2);
            lastPower2Press = millis();
        }
    }

        // Update power button states
    power1LastState = power1CurrentState;
    power2LastState = power2CurrentState;
}

void activatePowerMove(int player) {
    if (player == 1 && player1PowerAvailable) {
        player1PowerActive = true;
        player1PowerMovesLeft = POWER_MOVES_COUNT;
        player1PowerAvailable = false;
        flashPowerMove(1);
    } else if (player == 2 && player2PowerAvailable) {
        player2PowerActive = true;
        player2PowerMovesLeft = POWER_MOVES_COUNT;
        player2PowerAvailable = false;
        flashPowerMove(2);
    }
}

void flashPowerMove(int player) {
    strip.clear();
    // Flash player's side
    if (player == 1) {
        for (int i = 0; i < CENTER_POS; i++) {
            strip.setPixelColor(i, COLOR_MAGENTA);
        }
        tone(BUZZER_PIN, 2000, 200); // Power move activation sound
    } else {
        for (int i = CENTER_POS; i < NUM_LEDS; i++) {
            strip.setPixelColor(i, COLOR_MAGENTA);
        }
        tone(BUZZER_PIN, 2500, 200); // Power move activation sound
    }
    strip.show();
    delay(POWER_FLASH_DURATION);
    updateLEDs();
}

void playStartSequence() {
  // First update colors for the sequence
  updateColors();
  
  // Flash center position in preparation
  for (int i = 0; i < 3; i++) {
    // First two beeps with one frequency
    if (i < 2) {
      strip.clear();
      strip.setPixelColor(CENTER_POS, COLOR_RED);
      strip.setPixelColor(CENTER_POS - 1, COLOR_RED);
      strip.show();
      tone(BUZZER_PIN, 800, 200);  // Lower frequency for ready beeps
      delay(300);
      
      strip.clear();
      strip.show();
      delay(300);
    }
    // Final beep with different frequency
    else {
      strip.clear();
      strip.setPixelColor(CENTER_POS, COLOR_MAGENTA);
      strip.setPixelColor(CENTER_POS - 1, COLOR_MAGENTA);
      strip.show();
      tone(BUZZER_PIN, 1200, 400);  // Higher frequency for start beep
      delay(400);
    }
  }
  
  // Start the game
  gameActive = true;
  flagPosition = CENTER_POS;
  updateLEDs();
}

void updateColors() {
  // Read LED intensity from potentiometer (0-255)
  int intensity = map(analogRead(LED_INTENSITY_POT), 0, 1023, 0, 255);
  
  // Update colors with new intensity
  COLOR_RED = strip.Color(intensity, 0, 0);
  COLOR_BLUE = strip.Color(0, 0, intensity);
  COLOR_YELLOW = strip.Color(intensity, intensity, 0);
  COLOR_MAGENTA = strip.Color(intensity, 0, intensity);
}

void updateGameSpeed() {
  // Read speed value from potentiometer
  clickSpeed = map(analogRead(SPEED_POT), 0, 1023, MIN_CLICK_SPEED, MAX_CLICK_SPEED);
}

void checkButtons() {
    // Read current button states
    bool button1CurrentState = digitalRead(BUTTON1_PIN);
    bool button2CurrentState = digitalRead(BUTTON2_PIN);
    
    // Check Player 1 button (pulls flag left)
    if (button1CurrentState == LOW && button1LastState == HIGH) {  // Detect only falling edge
        if (millis() - lastButton1Press > DEBOUNCE_TIME) {
            // Calculate move amount with a more balanced power multiplier
            int moveAmount = clickSpeed;
            if (player1PowerActive && player1PowerMovesLeft > 0) {
                moveAmount = min(moveAmount + POWER_BOOST, MAX_POWER_SPEED);
                player1PowerMovesLeft--;
                if (player1PowerMovesLeft <= 0) {
                    player1PowerActive = false;
                }
            }
            flagPosition -= moveAmount;
            tone(BUZZER_PIN, player1PowerActive ? 800 : 1000, 20);
            lastButton1Press = millis();
        }
    }
    
    // Check Player 2 button (pulls flag right)
    if (button2CurrentState == LOW && button2LastState == HIGH) {  // Detect only falling edge
        if (millis() - lastButton2Press > DEBOUNCE_TIME) {
            int moveAmount = clickSpeed;
            if (player2PowerActive && player2PowerMovesLeft > 0) {
                moveAmount = min(moveAmount + POWER_BOOST, MAX_POWER_SPEED);
                player2PowerMovesLeft--;
                if (player2PowerMovesLeft <= 0) {
                    player2PowerActive = false;
                }
            }
            flagPosition += moveAmount;
            tone(BUZZER_PIN, player2PowerActive ? 1300 : 1500, 20);
            lastButton2Press = millis();
        }
    }
    
    // Update button states
    button1LastState = button1CurrentState;
    button2LastState = button2CurrentState;
    
    // Keep flag position within bounds
    flagPosition = constrain(flagPosition, 1, NUM_LEDS - 2);
    
    // Check for victory conditions
    if (flagPosition <= 1 || flagPosition >= NUM_LEDS - 2) {
        gameActive = false;
        celebrateVictory();
    }
}

void updateLEDs() {
  strip.clear();
  
  // Draw the red flag (2 LEDs)
  strip.setPixelColor(flagPosition, COLOR_RED);
  strip.setPixelColor(flagPosition - 1, COLOR_RED);
  
  // Draw Player 1 side (blue)
  for (int i = 0; i < flagPosition - 1; i++) {
    strip.setPixelColor(i, i == 0 ? COLOR_MAGENTA : COLOR_BLUE);
  }
  
  // Draw Player 2 side (yellow)
  for (int i = flagPosition + 1; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, i == NUM_LEDS - 1 ? COLOR_MAGENTA : COLOR_YELLOW);
  }
  
  strip.show();
}

void celebrateVictory() {
  // Determine winner's color
  uint32_t winnerColor = (flagPosition <= 1) ? COLOR_BLUE : COLOR_YELLOW;
  
  // Victory tune
  if (flagPosition <= 1) {
    playVictoryTune(1000);  // Lower pitch for Player 1
  } else {
    playVictoryTune(1500);  // Higher pitch for Player 2
  }
  
  // Flash victory animation
  for (int i = 0; i < VICTORY_FLASHES; i++) {
    // Fill with winner's color
    for (int j = 0; j < NUM_LEDS; j++) {
      strip.setPixelColor(j, winnerColor);
    }
    strip.show();
    delay(FLASH_DELAY);
    
    // Turn off
    strip.clear();
    strip.show();
    delay(FLASH_DELAY);
  }
  
  delay(3000);
  resetGame();
}

void playVictoryTune(int baseFreq) {
  tone(BUZZER_PIN, baseFreq, 200);
  delay(200);
  tone(BUZZER_PIN, baseFreq * 1.25, 200);
  delay(200);
  tone(BUZZER_PIN, baseFreq * 1.5, 400);
  delay(400);
}

void resetGame() {
    flagPosition = CENTER_POS;
    gameActive = false;
    player1PowerActive = false;
    player2PowerActive = false;
    player1PowerMovesLeft = 0;
    player2PowerMovesLeft = 0;
    player1PowerAvailable = true;
    player2PowerAvailable = true;
    updateLEDs();
}

Credits

Mirko Pavleski
168 projects • 1371 followers
Contact

Comments

Please log in or sign up to comment.