Whack-a-mole on an Arduino Uno
Four lamps, four buttons, a piezo and a score display on an Uno: hit each mole before it ducks in a thirty-second round that speeds up as you score. The whole build, an Arduino Uno and 14 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.
Intermediate Runs in your browser. Free, and no account needed.
The parts retail for about $35. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino Whack-a-Mole Game: LEDs, Buttons, Score
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Whack-a-mole: four lamps, four buttons, a piezo and a score display.
//
// Wiring:
// moles pins 2, 3, 4, 5 -> 220 ohm -> LED -> GND
// buttons pins 6, 7, 8, 9 -> pushbutton -> GND (button 1 under mole 1)
// piezo pin 10 -> piezo -> GND
// TM1637 CLK -> A0 DIO -> A1 VCC -> 5 V GND -> GND
//
// Press any button to start a thirty-second round. A mole lights up: hit the
// button under it before it goes out. Every hit scores a point and makes the
// next mole a little quicker. Hit the wrong button and you lose a point. The
// display shows the seconds left on the left and the score on the right, and
// the best score of the session when the round is over.
//
// The moles are random, so the sketch seeds the generator from micros() at the
// first press: that is a person's reaction time, which nothing can predict.
#include <TM1637Display.h>
const uint8_t MOLES = 4;
const uint8_t LAMP[MOLES] = {2, 3, 4, 5};
const uint8_t BUTTON[MOLES] = {6, 7, 8, 9};
const uint8_t PIEZO_PIN = 10;
const uint32_t ROUND_MS = 30000;
TM1637Display display(A0, A1);
int8_t mole = -1; // the lit mole, or -1
uint32_t moleUntil = 0; // when it goes back down
uint32_t nextMoleAt = 0; // when the next one pops up
uint16_t upMs = 900; // how long a mole stays up
int score = 0;
int best = 0;
bool playing = false;
uint32_t roundStart = 0;
bool wasDown[MOLES];
bool seeded = false;
static void lampsOff()
{
for (uint8_t i = 0; i < MOLES; i++) {
digitalWrite(LAMP[i], LOW);
}
}
/** Seconds left on the left two digits, the score on the right two, colon on. */
static void showRound(uint8_t secondsLeft)
{
int both = secondsLeft * 100 + (score < 0 ? 0 : score % 100);
display.showNumberDecEx(both, 0b01000000, true);
}
static void showBest()
{
const uint8_t dashes[2] = {SEG_G, SEG_G};
display.setSegments(dashes, 2, 0);
display.showNumberDec(best % 100, false, 2, 2);
}
static void startRound()
{
if (!seeded) {
randomSeed(micros());
seeded = true;
}
score = 0;
upMs = 900;
playing = true;
roundStart = millis();
nextMoleAt = roundStart + 600;
mole = -1;
lampsOff();
tone(PIEZO_PIN, 1047, 120);
Serial.println("Go! Thirty seconds.");
}
static void endRound()
{
playing = false;
lampsOff();
mole = -1;
if (score > best) {
best = score;
}
Serial.print("Time! Score ");
Serial.print(score);
Serial.print(", best ");
Serial.println(best);
tone(PIEZO_PIN, 392, 400);
showBest();
}
void setup()
{
Serial.begin(9600);
for (uint8_t i = 0; i < MOLES; i++) {
pinMode(LAMP[i], OUTPUT);
pinMode(BUTTON[i], INPUT_PULLUP);
}
noTone(PIEZO_PIN);
display.setBrightness(5);
showBest();
delay(20);
Serial.println("Whack-a-mole: press any button to start");
}
void loop()
{
uint32_t now = millis();
// Read the buttons: a new press is down now and was up last time.
int8_t hit = -1;
for (uint8_t i = 0; i < MOLES; i++) {
bool down = digitalRead(BUTTON[i]) == LOW;
if (down && !wasDown[i]) {
hit = (int8_t)i;
}
wasDown[i] = down;
}
if (!playing) {
// Between rounds the moles take turns peeking, and any press starts.
uint8_t peek = (uint8_t)((now / 250) % MOLES);
for (uint8_t i = 0; i < MOLES; i++) {
digitalWrite(LAMP[i], i == peek ? HIGH : LOW);
}
if (hit >= 0) {
startRound();
}
delay(5);
return;
}
uint32_t elapsed = now - roundStart;
if (elapsed >= ROUND_MS) {
endRound();
return;
}
if (hit >= 0) {
if (hit == mole) {
score++;
digitalWrite(LAMP[mole], LOW);
mole = -1;
nextMoleAt = now + (uint32_t)random(150, 500);
if (upMs > 380) {
upMs -= 30;
}
tone(PIEZO_PIN, 1568, 60);
Serial.print("Hit! ");
Serial.println(score);
} else {
score--;
tone(PIEZO_PIN, 147, 120);
Serial.println("Missed");
}
}
if (mole >= 0 && (int32_t)(now - moleUntil) >= 0) {
// too slow: it ducks back down
digitalWrite(LAMP[mole], LOW);
mole = -1;
nextMoleAt = now + (uint32_t)random(150, 500);
}
if (mole < 0 && (int32_t)(now - nextMoleAt) >= 0) {
int8_t pick;
do {
pick = (int8_t)random(MOLES);
} while (pick == hit);
mole = pick;
moleUntil = now + upMs;
digitalWrite(LAMP[mole], HIGH);
}
showRound((uint8_t)((ROUND_MS - elapsed + 999) / 1000));
delay(5);
}
Parts list
16 parts, plus the jumper wires. They retail for about $35; on Mokxi they're free.
- 1 × Arduino Uno R3
- 1 × Full-size breadboard
- 4 × Resistor, 220 Ω
- 1 × LED, red
- 1 × LED, yellow
- 1 × LED, green
- 1 × LED, blue
- 4 × Pushbutton
- 1 × Piezo speaker
- 1 × Four-digit display, TM1637
How it is wired
17 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.
- Ground: Arduino Uno R3 pin GND; LED, red pin C; LED, yellow pin C; LED, green pin C; LED, blue pin C; Pushbutton (1) pin 2a; Pushbutton (2) pin 2a; Pushbutton (3) pin 2a; Pushbutton (4) pin 2a; Piezo speaker pin 2; Four-digit display, TM1637 pin GND
- Arduino Uno R3 pin 10; Piezo speaker pin 1
- Arduino Uno R3 pin 9; Pushbutton (4) pin 1a
- Arduino Uno R3 pin 8; Pushbutton (3) pin 1a
- Arduino Uno R3 pin 7; Pushbutton (2) pin 1a
- Arduino Uno R3 pin 6; Pushbutton (1) pin 1a
- Arduino Uno R3 pin 5; Resistor, 220 Ω (4) pin 1
- Arduino Uno R3 pin 4; Resistor, 220 Ω (3) pin 1
- Arduino Uno R3 pin 3; Resistor, 220 Ω (2) pin 1
- Arduino Uno R3 pin 2; Resistor, 220 Ω (1) pin 1
- Arduino Uno R3 pin 5V; Four-digit display, TM1637 pin VCC
- Arduino Uno R3 pin A0; Four-digit display, TM1637 pin CLK
- Arduino Uno R3 pin A1; Four-digit display, TM1637 pin DIO
- Resistor, 220 Ω (1) pin 2; LED, red pin A
- Resistor, 220 Ω (2) pin 2; LED, yellow pin A
- Resistor, 220 Ω (3) pin 2; LED, green pin A
- Resistor, 220 Ω (4) pin 2; LED, blue pin A
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.