Tug of war game on an Arduino Nano
Tug of war game for two players on a 15-LED NeoPixel strip: every button press pulls the light one LED your way, and the first to pull it off wins. The whole build, an Arduino Nano and 4 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Beginner Runs in your browser. Free, and no account needed.
The parts retail for about $31. On Mokxi they're free.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Tug of war: two players, two buttons and a strip of 15 NeoPixels on an
// Arduino Nano. Whoever presses faster pulls the light to their end.
//
// red button D2 and GND (INPUT_PULLUP: a press reads LOW)
// blue button D12 and GND
// LED strip DIN -> D11, 5V -> 5V, GND -> GND (15 WS2812B LEDs)
// piezo D13 and GND
//
// The white light is the knot in the rope. It starts in the middle. Every
// press by red pulls it one LED toward red's end of the strip (the first LED,
// at DIN), every press by blue one LED toward the last. The LEDs on each side
// of the knot glow in the color of the player who owns that end. Pull the
// knot right off your end to win the round.
//
// A round starts with a countdown: three short beeps and a high one for GO.
// Presses before GO do not count. After a win the strip flashes the winner's
// color, the score goes to the Serial Monitor, and a new round starts.
//
// # Counting presses fairly
//
// A press is the moment a pin goes from HIGH to LOW. The sketch remembers
// each pin's last reading and only counts that edge, so holding a button down
// pulls once, not over and over. Contacts bounce for a few milliseconds, so a
// second edge within 30 ms of the first is ignored.
#include <Adafruit_NeoPixel.h>
const uint8_t RED_BUTTON = 2;
const uint8_t BLUE_BUTTON = 12;
const uint8_t STRIP_PIN = 11;
const uint8_t PIEZO = 13;
const uint8_t LEDS = 15;
const int8_t END = LEDS / 2; // 7: the knot wins at -8 or +8
Adafruit_NeoPixel strip(LEDS, STRIP_PIN, NEO_GRB + NEO_KHZ800);
// Where the knot is: 0 is the middle, negative is toward red.
int8_t knot = 0;
uint8_t redWins = 0;
uint8_t blueWins = 0;
struct Player {
uint8_t pin;
bool last;
unsigned long at;
};
Player red = {RED_BUTTON, HIGH, 0};
Player blue = {BLUE_BUTTON, HIGH, 0};
// True once for each new press.
bool pressed(Player &p) {
bool now = digitalRead(p.pin);
bool edge = now == LOW && p.last == HIGH && millis() - p.at > 30;
if (now != p.last) p.at = millis();
p.last = now;
return edge;
}
void draw() {
int8_t at = END + knot; // the knot's LED, 0 to 14
for (int8_t i = 0; i < LEDS; i++) {
if (i == at) {
strip.setPixelColor(i, strip.Color(255, 255, 255));
} else if (i < at) {
strip.setPixelColor(i, strip.Color(60, 0, 0));
} else {
strip.setPixelColor(i, strip.Color(0, 0, 60));
}
}
strip.show();
}
void countdown() {
strip.clear();
strip.show();
Serial.println("ready...");
for (uint8_t i = 0; i < 3; i++) {
tone(PIEZO, 440, 150);
delay(600);
}
tone(PIEZO, 880, 300);
Serial.println("GO!");
knot = 0;
// Forget anything pressed during the countdown.
pressed(red);
pressed(blue);
draw();
}
void win(bool redWon) {
if (redWon) {
redWins++;
} else {
blueWins++;
}
Serial.print(redWon ? "red wins! " : "blue wins! ");
Serial.print("score: red ");
Serial.print(redWins);
Serial.print(", blue ");
Serial.println(blueWins);
uint32_t color = redWon ? strip.Color(255, 0, 0) : strip.Color(0, 0, 255);
// a little fanfare, the strip flashing in time
const unsigned int tune[4] = {523, 659, 784, 1047};
for (uint8_t i = 0; i < 4; i++) {
strip.fill(color);
strip.show();
tone(PIEZO, tune[i], 150);
delay(180);
strip.clear();
strip.show();
delay(70);
}
strip.fill(color);
strip.show();
delay(1500);
}
void setup() {
Serial.begin(115200);
pinMode(RED_BUTTON, INPUT_PULLUP);
pinMode(BLUE_BUTTON, INPUT_PULLUP);
pinMode(PIEZO, OUTPUT);
strip.begin();
strip.setBrightness(80);
Serial.println("Tug of war: red on D2, blue on D12");
countdown();
}
void loop() {
bool moved = false;
if (pressed(red)) {
knot--;
moved = true;
}
if (pressed(blue)) {
knot++;
moved = true;
}
if (!moved) return;
tone(PIEZO, 300 + 40 * (knot + END), 15);
if (knot < -END) {
win(true);
countdown();
} else if (knot > END) {
win(false);
countdown();
} else {
draw();
Serial.print("knot at ");
Serial.println(knot);
}
}
Parts list
6 parts, plus the jumper wires. They retail for about $31; on Mokxi they're free.
- 1 × Arduino Nano
- 2 × Arcade button
- 1 × Addressable RGB LEDs
- 1 × Power, 5 V
- 1 × Piezo speaker
How it is wired
9 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Nano pin 2; Arcade button (2) pin 2
- Arduino Nano pin 11; Addressable RGB LEDs pin DIN
- Arduino Nano pin 12; Arcade button (1) pin 2
- Arduino Nano pin 13; Piezo speaker pin 1
- Ground: Arcade button (1) pin 1
- 5 V: Addressable RGB LEDs pin VDD
- Ground: Addressable RGB LEDs pin VSS
- Ground: Arcade button (2) pin 1
- Ground: Piezo speaker pin 2
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.