DIY Simple Arduino Whack-a-Mole Game
2025-08-18 | By Mirko Pavleski
License: General Public License Displays Arduino
A "Whack-a-Mole" game is a classic arcade-style game where moles pop up randomly from holes, and the player uses a mallet or similar tool to "whack" the moles before they disappear.
The goal is to hit the moles as they appear, to score points while avoiding hitting any other targets. It’s typically timed, and the game ends after a certain period, with the player's score displayed. This time, I will present you with a simple way to make a Whack-a-Mole portable game using an Arduino microcontroller.
In this case, the "moles" will be small buttons with built-in LEDs in different colors. A randomly selected button lights up for a certain time when we have to press it to get a point.
This project is sponsored by PCBWay. They have all the services you need to create your project, whether it's a school project or a complex professional project. On PCBWay you can share your experiences or get inspiration for your next project. They also provide completed surface mount SMT PCB assembly service and ISO9001 quality control.
The device is very simple to make and consists of a few components:
- Arduino Nano microcontroller module
- 5 Buttons with built-in LEDs with different colours
- LCD display with 16x2 characters and I2C communication protocol
- and Buzzer
The start of the game is indicated on the LCD display, and then the buttons start to light up in random order. With each successful press of a button while it is lit, we get a point. If we press the wrong button, we get a negative point, and the score is reduced by 1.
Successful or unsuccessful activation of the button (actually catching the mole), as well as other states of the game, are signaled by an appropriate sound generated by the buzzer. When the game starts, the first line shows the current score, while the second line shows a bar graph that gradually decreases over time and disappears after exactly thirty seconds, the same time the game lasts. This way, we have a visual representation of the remaining time in the game.
To make the game more fun and addictive, over time, the duration of the lighting of the corresponding button is reduced; in fact, the game is accelerated. At the end of the game, all the buttons flash several times, and this is accompanied by appropriate sounds. Then the final score appears on the screen.
After a 5-second break, a new game starts.
I updated the code so that the player has a better overview of the game duration, and now the bar, instead of 16 horizontal bars, consists of 80 vertical bars.
And finally, a quick conclusion. This is a really simple Arduino version of the Whack-a-Mole arcade game made with just a few components, but endlessly fun and addictive, great for testing and practicing reflexes. I installed the device in a suitable box made of PVC board with a thickness of 3 to 5mm and covered with self-adhesive colored wallpaper. Also, for compactness and mobility, the game is powered by 2 lithium batteries (7.4V).
/*Arduino Whack-A-Mole Game by mircemk, June 2025 */ #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); // Pin Definitions const int buttonPins[] = {8, 9, 10, 11, 12}; // Button pins const int ledPins[] = {2, 3, 4, 5, 6}; // LED pins const int buzzerPin = 13; // Buzzer pin (digital 13) const int numMoles = 5; // Number of moles/buttons/LEDs // Game variables int currentMole = -1; // Current mole (LED) to be lit int score = 0; // Player's score unsigned long reactionTime = 1000; // Initial reaction time (milliseconds) unsigned long lastMoleTime = 0; // Time when the last mole was lit unsigned long gameStartTime = 0; // Start time of the game unsigned long gameDuration = 30000; // Total game duration (30 seconds) // Reaction time adjustment const unsigned long reactionTimeDecrement = 100; // Time to reduce reaction by (milliseconds) const unsigned long minReactionTime = 300; // Minimum reaction time (milliseconds) void setup() { lcd.backlight(); lcd.begin(16, 2); lcd.clear(); lcd.setCursor(2, 0); lcd.print("Whack-a-Mole"); lcd.setCursor(3, 1); lcd.print("by mircemk"); delay(2000); lcd.clear(); // Initialize button pins and LED pins for (int i = 0; i < numMoles; i++) { pinMode(buttonPins[i], INPUT_PULLUP); // Set button pins as input with pull-up resistors pinMode(ledPins[i], OUTPUT); // Set LED pins as output digitalWrite(ledPins[i], LOW); // Turn off all LEDs initially } pinMode(buzzerPin, OUTPUT); // Set buzzer pin as output Serial.begin(9600); // For debugging and displaying the score randomSeed(analogRead(0)); // Initialize random seed from an unused analog pin Serial.println("Whack-a-Mole Game Started!"); lcd.setCursor(2, 0); lcd.print("GAME STARTED!"); delay(500); lcd.clear(); gameStartTime = millis(); // Record game start time } void loop() { unsigned long currentMillis = millis(); // Get the current time // Update the progress bar unsigned long elapsedTime = currentMillis - gameStartTime; if (elapsedTime <= gameDuration) { int barLength = map(gameDuration - elapsedTime, 0, gameDuration, 0, 16); lcd.setCursor(0, 1); for (int i = 0; i < 16; i++) { lcd.print(i < barLength ? '-' : ' '); // Print '-' for remaining time, ' ' for elapsed } } // Light up a mole after a certain amount of time (based on reactionTime) if (currentMillis - lastMoleTime >= reactionTime) { if (currentMole != -1) { digitalWrite(ledPins[currentMole], LOW); // Turn off the previous mole } currentMole = random(0, numMoles); // Randomly pick a mole (LED) digitalWrite(ledPins[currentMole], HIGH); // Light up the chosen LED lastMoleTime = currentMillis; // Update the time when the mole was lit } // Check if the player pressed the correct button for the lit mole for (int i = 0; i < numMoles; i++) { if (digitalRead(buttonPins[i]) == LOW) { // Button pressed (LOW due to INPUT_PULLUP) if (i == currentMole) { score++; // Correct mole hit lcd.setCursor(2, 0); lcd.print(" Score: "); // Clear the score field with extra spaces lcd.setCursor(12, 0); lcd.print(score); // Update the score tone(buzzerPin, 1000, 200); // High-pitched sound (1000Hz) for 200ms if (reactionTime > minReactionTime) { reactionTime -= reactionTimeDecrement; } } else { score--; // Wrong mole hit lcd.setCursor(2, 0); lcd.print(" Score: "); // Clear the score field with extra spaces lcd.setCursor(12, 0); lcd.print(score); // Update the score tone(buzzerPin, 400, 200); // Low-pitched sound (400Hz) for 200ms } digitalWrite(ledPins[currentMole], LOW); // Turn off the current mole currentMole = -1; // Reset the mole to indicate no active mole delay(500); // Short delay to debounce button press } } // End the game after the set duration if (elapsedTime >= gameDuration) { lcd.setCursor(3, 0); lcd.print("Game Over! "); lcd.setCursor(0, 1); lcd.print("Final Score: "); lcd.setCursor(13, 1); lcd.print(" "); // Clear any leftover characters lcd.setCursor(13, 1); lcd.print(score); // Print the final score // Flash all LEDs and play a sound three times for (int i = 0; i < 3; i++) { for (int j = 0; j < numMoles; j++) { digitalWrite(ledPins[j], HIGH); } tone(buzzerPin, 1000, 300); // Play sound delay(300); // Keep LEDs on for (int j = 0; j < numMoles; j++) { digitalWrite(ledPins[j], LOW); } delay(300); // Keep LEDs off } delay(2000); // Pause before resetting the game lcd.clear(); score = 0; currentMole = -1; for (int i = 0; i < numMoles; i++) { digitalWrite(ledPins[i], LOW); } lcd.setCursor(0, 0); lcd.print(" Start New Game"); delay(5000); lcd.clear(); gameStartTime = millis(); reactionTime = 1000; } }