Project tutorial
Whac-A-Mole (Button Edition)

Whac-A-Mole (Button Edition) © LGPL

Custom Whac-A-Mole game with light- and sound effects.

  • 5,480 views
  • 6 comments
  • 11 respects

Components and supplies

Ardgen mega
Arduino Mega 2560 & Genuino Mega 2560
×1
Adafruit 1.4" 4-digit 7-segment display with I2C backpack
Choose the color you want
×2
Adafruit Big arcade button with led (60mm)
Choose 5 different colors
×5
Adafruit Audio fx board 16mb
×1
Adafruit 2.5w amp
×2
Adafruit industries ada1314 image
Speaker: 3W, 4 ohms
×2
Adafruit on/off button with led
×1
Adafruit potentiometer 10 k
×2
Adafruit potentiometer knob
×2
wire
×1
Countersunk screws M3 + nuts
×1
Countersunk screws M4 + nuts
×1
All 3d printed parts
×1
All lasercut MDF parts
×1
Custom Arduino mega shield
×1
Arduino mega stackable headers (for spacing)
×1
Adafruit DC step-down converter 5v 3A
×1
12v PSU
×1
Female psu bus
×1
USB-B panelmount
×1

Necessary tools and machines

3drag
3D Printer (generic)
screwdriver
09507 01
Soldering iron (generic)

Apps and online services

Ide web
Arduino IDE
MDF lasercut service

About this project

This is a custom whac-a-mole game. For now it's programmed with a basic game, but it's possible to create updates in the feature.

Key features:

  • 5 bottons for the game itself. Each button has it's own bright led. Led's are PWM controlled (used for the game-over sequence).
  • 10 selectable levels. Level 1 (easy) through 10 (insane). During gameplay you have to hit the correct button within a certain time. This duration is variable inside each level (so sometimes you have to press is quicker than other steps).
  • (pseudo) random button selection during gameplay. The next step (button that needs to be pressed) is always an other button.
  • 2 LED digit displays. One for current score/countdown/level selection and one for displaying high score.
  • digital volume control
  • High score storing
  • Sound effects. During various states of the game sound loops are played through the build-in speakers.
  • Laser cut enclosure
  • Many 3D printed parts
  • Custom Arduino Mega shield

Enclosure

First, start with assembling the enclosure. 

Mount the 3D printed mounting brackets on the top cover.

If you wish, paint the enclosure.

Next, mount the LED display mounts. Don't forget to place the bolts underneath the mounts, otherwise the vertical bars can't be fixed.

Assemble the Arduino shield. I used an extra set of stackable headers, so there's more space between the shield and the Arduino.

Assemble the back control panel.

Assemble the top cover (buttons & LED panels).

The back control panel includes two potentiometer controls; one for the volume control, one for level selection.

Conclusions

Finished!

on the left led digit panel you can see the selected level. You can select level 1 (easy) through 10 (insane). On the right led digit panel you can see the high score for the corresponding level. During an active game the left digit panel will display the current score. Now press the green button to start the game. After a countdown the game will start.

Code

Whac_A_Mole_v1.0.0Arduino
Arduino Code, written within Visual Studio
/*
Whac_A_Mole_v1.0.0
By WT040
*/

#include "Wire.h"
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"

Adafruit_7segment matrix1 = Adafruit_7segment();
Adafruit_7segment matrix2 = Adafruit_7segment();

#define debounceTime 10  

//declare potmeter controls
#define potmeterVolume 0
#define potmeterGameLevel 1

//declare amp power pin
#define ampPower 32

//declare buttons
#define btn1Pin 22
#define btn2Pin 24
#define btn3Pin 26
#define btn4Pin 28
#define btn5Pin 30
byte btnArray[] = {
	btn1Pin,
	btn2Pin,
	btn3Pin,
	btn4Pin,
	btn5Pin
};

#define btn1 0	// for using the btn array
#define btn2 1	// for using the btn array
#define btn3 2	// for using the btn array
#define btn4 3	// for using the btn array
#define btn5 4	// for using the btn array

//declare button leds
#define btnLed1Pin 9
#define btnLed2Pin 10
#define btnLed3Pin 11
#define btnLed4Pin 12
#define btnLed5Pin 13 
byte btnLedArray[] = {
	btnLed1Pin,
	btnLed2Pin,
	btnLed3Pin,
	btnLed4Pin,
	btnLed5Pin
};

#define btnLed1 0	// for using the btn array
#define btnLed2 1	// for using the btn array
#define btnLed3 2	// for using the btn array
#define btnLed4 3	// for using the btn array
#define btnLed5 4	// for using the btn array

//declare audio fx digitals
#define fx0TriggerPin 29
#define fx1TriggerPin 31
#define fx2TriggerPin 33
#define fx3TriggerPin 35
#define fx4TriggerPin 37
#define fx5TriggerPin 39
#define fx6TriggerPin 41
#define fx7TriggerPin 43
#define fx8TriggerPin 45
#define fx9TriggerPin 47
#define fx10TriggerPin 49
#define fxVolUpPin 51
#define fxVolDnPin 53

byte fxTriggerArray[] = {
	fx0TriggerPin,
	fx1TriggerPin,
	fx2TriggerPin,
	fx3TriggerPin,
	fx4TriggerPin,
	fx5TriggerPin,
	fx6TriggerPin,
	fx7TriggerPin,
	fx8TriggerPin,
	fx9TriggerPin,
	fx10TriggerPin
};

byte fxVolumeArray[] = {
	fxVolUpPin,
	fxVolDnPin
};

#define fx0Trigger 0	// for using the btn array
#define fx1Trigger 1	// for using the btn array
#define fx2Trigger 2	// for using the btn array
#define fx3Trigger 3	// for using the btn array
#define fx4Trigger 4	// for using the btn array
#define fx5Trigger 5	// for using the btn array
#define fx6Trigger 6	// for using the btn array
#define fx7Trigger 7	// for using the btn array
#define fx8Trigger 8	// for using the btn array
#define fx9Trigger 9	// for using the btn array
#define fx10Trigger 10	// for using the btn array
#define fxVolUp 0	// for using the btn array
#define fxVolDn 1	// for using the btn array

signed int currentPlayingSound = -1;


byte btnPressed[5], ledState[5];

//blink led
unsigned long previousMillisLedBlink = 0; //for blinking led
#define intervalLedBlink 750 
//


//game start sequence
unsigned long previousMillisGameStarting = 0;
#define intervalCountDown 1000
byte gameStartCountDown;
//

//game steps
unsigned long previousMillisGameStep = 0;
int gameStepLength = 0; //length of an individual step
int gameStepBtn = 0; //the button that's need to be pushed 
//

int valPotmeterVolume = 0;
int volume = 0;
int actualVolume = 35; //volume of the adafruit fx board
int valPotmeterGameLevel = 0;
int gameLevel;
int currentScore;
int highScore;
int highScoreAddr = 1;

byte activeBtn;
bool debug; //enable debug or not

byte wackAMoleState; //state of the game
bool SystemInitialized = false;

#define stateUnknown 0
#define waitForStart 1
#define starting 2
#define running 3
#define gameOver 4
#define stopped 5


void setup()
{
	Serial.begin(9600);

	//init amp power pin
	pinMode(ampPower, OUTPUT);
	digitalWrite(ampPower, LOW);

	//init btn pins
	for (int Pin = 0; Pin < 5; Pin++) {
		pinMode(btnArray[Pin], INPUT);
	}

	//init btn led pins
	for (int Pin = 0; Pin < 5; Pin++) {
		pinMode(btnLedArray[Pin], OUTPUT);
	}


	//init btn fx pins
	for (int Pin = 0; Pin < 11; Pin++) {
		pinMode(fxTriggerArray[Pin], OUTPUT);
		digitalWrite(fxTriggerArray[Pin], HIGH);
	}

	//init btn fx volume pins
	for (int Pin = 0; Pin < 2; Pin++) {
		pinMode(fxVolumeArray[Pin], OUTPUT);
		digitalWrite(fxVolumeArray[Pin], HIGH);
	}

	matrix1.begin(0x70);
	matrix2.begin(0x71);



	while (!eeprom_is_ready()); // Wait for EEPROM to be ready
	cli();
	highScore = eeprom_read_word((uint16_t*)(gameLevel * 2));
	sei();

	matrix1.print(0);
	matrix1.writeDisplay();
	matrix2.print(0);
	matrix2.writeDisplay();

	initializeSystem();
}

void loop()
{
	checkButtonInputs();      // when we check the switches we'll get the current state
	readVolumePotmeter();


	if (wackAMoleState == waitForStart) {
		playSound(fx0Trigger);
		if (btnPressed[btn4]) {
			wackAMoleState = starting;
		}
		readGameLevelPotmeter();
		ledBlinkStart();
	}

	if (wackAMoleState == starting) {
		startGame();

	}

	if (wackAMoleState == running) {
		playSound(fx1Trigger);
		checkNextGameStep();
	}
}


void checkNextGameStep() {
	unsigned long currentMillisGameStep = millis();
	int amountOfButtonPresses = 0;

	for (int btn = 0; btn < 5; btn++) {
		if (btnPressed[btn]) {
			amountOfButtonPresses++;
		}
	}

	if (previousMillisGameStep == 0) {
		previousMillisGameStep = currentMillisGameStep;
		digitalWrite(btnLedArray[btnLed4], LOW);

		randomSeed(analogRead(2));
		gameStepBtn = random(0, 5);
		gameStepLength = random(3000 / gameLevel, 5000 / gameLevel);
		digitalWrite(btnLedArray[gameStepBtn], HIGH);	//light up new button light
	}

	if ((currentMillisGameStep - previousMillisGameStep >= gameStepLength) || (amountOfButtonPresses > 1) ||
		(amountOfButtonPresses == 1) && !btnPressed[gameStepBtn]) {

		//Game over...
		playSound(fx2Trigger);
		wackAMoleState = gameOver;
		previousMillisGameStep = 0;


		for (int counter = 0; counter < 5; counter++) {
			analogWrite(btnLedArray[counter], 255);
		}

		for (int brightness = 255; brightness > 0; brightness--) {
			analogWrite(btnLedArray[btnLed1], brightness);
			analogWrite(btnLedArray[btnLed2], brightness);
			analogWrite(btnLedArray[btnLed3], brightness);
			analogWrite(btnLedArray[btnLed4], brightness);
			analogWrite(btnLedArray[btnLed5], brightness);
			delay(10);
		}

		analogWrite(btnLedArray[btnLed1], 0);
		analogWrite(btnLedArray[btnLed2], 0);
		analogWrite(btnLedArray[btnLed3], 0);
		analogWrite(btnLedArray[btnLed4], 0);
		analogWrite(btnLedArray[btnLed5], 0);

		Serial.println("Game OVer");
		if (currentScore > highScore) {
			while (!eeprom_is_ready()); // Wait for EEPROM to be ready
			cli();
			eeprom_write_word((uint16_t*)(gameLevel * 2), currentScore); // Let's initialize our value into EEPROM
			sei();
		}

		int counter = 0;
		do
		{
			delay(1);
			checkButtonInputs();
			counter++;

		} while ((counter < 1000) && !btnPressed[btn1] && !btnPressed[btn2 && !btnPressed[btn3] &&
			!btnPressed[btn4] && !btnPressed[btn5]]);//wait for the release of the button

		matrix1.print(0);
		matrix1.writeDisplay();
		wackAMoleState = waitForStart;

	}

	else if (btnPressed[gameStepBtn] && (amountOfButtonPresses == 1)) {
		digitalWrite(btnLedArray[gameStepBtn], LOW); //turn off led previous button


		int counter = 0;
		do
		{
			delay(1);
			checkButtonInputs();
			counter++;

		} while ((counter < 1000) && btnPressed[gameStepBtn]);//wait for the release of the button

		previousMillisGameStep = currentMillisGameStep;

		int tempStepBtn = 0;
		do
		{
			tempStepBtn = random(0, 5);
			gameStepLength = random(3000 / gameLevel, 5000 / gameLevel);
		} while (gameStepBtn == tempStepBtn);

		gameStepBtn = tempStepBtn;

		digitalWrite(btnLedArray[gameStepBtn], HIGH);	//light up new button light

		currentScore++;
		matrix1.print(currentScore);
		matrix1.writeDisplay();

		if (currentScore > highScore) {
			matrix2.print(currentScore);
			matrix2.writeDisplay();
		}

	}
}

void ledBlinkStart() //blink a led without delay
{
	unsigned long currentMillisLedBlink = millis();
	if (currentMillisLedBlink - previousMillisLedBlink >= intervalLedBlink) {
		previousMillisLedBlink = currentMillisLedBlink;
		if (ledState[0] == LOW) {
			ledState[0] = HIGH;
		}
		else {
			ledState[0] = LOW;
		}
		digitalWrite(btnLedArray[btnLed4], ledState[0]); //green led/button
	}
}

void startGame() //start sequence of the game itself
{
	unsigned long currentMillisGameStarting = millis();
	digitalWrite(btnLedArray[btnLed4], 0); //turn green led/button off
	currentScore = 0;
	if (gameStartCountDown == 0) {
		playSound(fx3Trigger);
		previousMillisGameStarting = millis();
		gameStartCountDown = 4;
		delay(300);
		matrix1.print(gameStartCountDown, DEC);
		matrix1.writeDisplay();
	}

	if (currentMillisGameStarting - previousMillisGameStarting >= intervalCountDown) {
		previousMillisGameStarting = currentMillisGameStarting;
		if (gameStartCountDown > 0) {
			gameStartCountDown--;
			matrix1.print(gameStartCountDown, DEC);
			matrix1.writeDisplay();
			if (gameStartCountDown == 0) {
				wackAMoleState = running;
			}
		}
	}
}

void initializeSystem()
{
	//add init code here...
	digitalWrite(btnLedArray[btnLed1], HIGH);
	delay(75);
	digitalWrite(btnLedArray[btnLed2], HIGH);
	delay(75);
	digitalWrite(btnLedArray[btnLed3], HIGH);
	delay(75);
	digitalWrite(btnLedArray[btnLed4], HIGH);
	delay(75);
	digitalWrite(btnLedArray[btnLed5], HIGH);
	delay(1000);
	digitalWrite(btnLedArray[btnLed5], LOW);
	delay(75);
	digitalWrite(btnLedArray[btnLed4], LOW);
	delay(75);
	digitalWrite(btnLedArray[btnLed3], LOW);
	delay(75);
	digitalWrite(btnLedArray[btnLed2], LOW);
	delay(75);
	digitalWrite(btnLedArray[btnLed1], LOW);
	delay(1000);

	debug = true;
	wackAMoleState = waitForStart;
	initVolume();
	if (actualVolume > 0) {
		enableAmpPower(true);
	}

}

void checkButtonInputs() //check for button changes
{
	static byte previousstate[5];
	static byte currentstate[5];
	static long lasttime;
	byte index;

	//debounce
	if ((lasttime + debounceTime) > millis()) {
		return;
	}
	lasttime = millis();
	//

	for (index = 0; index < 5; index++) { 
		currentstate[index] = digitalRead(btnArray[index]);   // read the button
		if (currentstate[index] != btnPressed[index]) {
			if ((btnPressed[index] == HIGH) && (currentstate[index] == LOW)) {
				if (debug) {
					Serial.print("button released: ");
					Serial.println(index + 1);
				}
				btnPressed[index] = 0;
			}

			if ((btnPressed[index] == LOW) && (currentstate[index] == HIGH)) {
				if (debug) {
					Serial.print("button pressed: ");
					Serial.println(index + 1);
				}
				btnPressed[index] = 1;
			}
		}
	}
}

void initVolume()
{
	valPotmeterVolume = (analogRead(potmeterVolume) / 30);

	if (actualVolume > valPotmeterVolume) {
		do {
			digitalWrite(fxVolumeArray[fxVolDn], LOW);
			actualVolume--;
			delay(20);
			digitalWrite(fxVolumeArray[fxVolDn], HIGH);
			delay(20);
		} while (actualVolume > valPotmeterVolume);
	}

	else if (actualVolume < valPotmeterVolume) {
		do {
			digitalWrite(fxVolumeArray[fxVolUp], LOW);
			actualVolume++;
			delay(20);
			digitalWrite(fxVolumeArray[fxVolUp], HIGH);
			delay(20);
		} while (actualVolume < valPotmeterVolume);
	}
}

void readVolumePotmeter()
{
	valPotmeterVolume = (analogRead(potmeterVolume) / 30);


	if (actualVolume > valPotmeterVolume) {
		digitalWrite(fxVolumeArray[fxVolDn], LOW);
		actualVolume--;
		delay(40);
		digitalWrite(fxVolumeArray[fxVolDn], HIGH);
		if (actualVolume == 0) {
			enableAmpPower(false);
		}
	}
	else if (actualVolume < valPotmeterVolume) {

		digitalWrite(fxVolumeArray[fxVolUp], LOW);
		if (actualVolume == 0) {
			enableAmpPower(true);
		}
		actualVolume++;
		delay(40);
		digitalWrite(fxVolumeArray[fxVolUp], HIGH);
	}
}

void readGameLevelPotmeter()
{
	valPotmeterGameLevel = analogRead(potmeterGameLevel);

	if (valPotmeterGameLevel < 102) {
		gameLevel = 1;
	}
	else if (valPotmeterGameLevel < 204) {
		gameLevel = 2;
	}
	else if (valPotmeterGameLevel < 306) {
		gameLevel = 3;
	}
	else if (valPotmeterGameLevel < 408) {
		gameLevel = 4;
	}
	else if (valPotmeterGameLevel < 510) {
		gameLevel = 5;
	}
	else if (valPotmeterGameLevel < 612) {
		gameLevel = 6;
	}
	else if (valPotmeterGameLevel < 714) {
		gameLevel = 7;
	}
	else if (valPotmeterGameLevel < 816) {
		gameLevel = 8;
	}
	else if (valPotmeterGameLevel < 918) {
		gameLevel = 9;
	}
	else {
		gameLevel = 10;
	}

	matrix1.print(gameLevel);
	matrix1.writeDisplay();

	while (!eeprom_is_ready()); // Wait for EEPROM to be ready
	cli();
	highScore = eeprom_read_word((uint16_t*)(gameLevel * 2));
	sei();
	if (highScore > -1) {
		matrix2.print(highScore);
	}
	else {
		matrix2.print(0);
	}
	matrix2.writeDisplay();
}

void playSound(int sound)
{
	if (currentPlayingSound != sound) {
		for (int counter = 0; counter < 11; counter++) {
			digitalWrite(fxTriggerArray[counter], HIGH);
		}


		digitalWrite(fxTriggerArray[sound], LOW);
		currentPlayingSound = sound;
	}
}

void enableAmpPower(bool state)
{
	digitalWrite(ampPower, state);
}

Custom parts and enclosures

Lasercut MDF parts 6mm
Lasercut MDF parts 6mm
OC9CvHjuTzsYL9POZCJt.dxf
Lasercut MDF parts 9mm
Lasercut MDF parts 9mm
1irkeqvO4ABzhKqhmyG7.dxf
Lasercut Plexiglass parts 3mm
Lasercut Plexiglass parts 3mm
j4Tnmni6dQ0KXJtiHcy1.dxf
Custom Arduino Mega Shield
Custom Arduino Mega Shield
5yiRX1RdQuPX0gNXjYkm.zip
3D printed amp mount
3D printed amp mount, print 2x
3D printed arduino mega mount
3D printed arduino mega mount
3D printed back connection panel mount
3D printed back connection panel mount
3D printed back connection panel
3D printed back connection panel
3D printed Cable Clip
3D printed Cable Clip, print as many as you like
3D printed led display mount
3D printed led display mount, print 2x
3D printed led display mount lock
3D printed led display mount lock, print 4x
3D printed led display top frame
3D printed led display top frame, print 2x
3D printed top cover mount back
3D printed top cover mount back
3D printed top cover mount front
3D printed top cover mount front, print 2x
3D printed top cover mount side
3D printed top cover mount side, print 2x

Schematics

Schematic
Lv3rybtixbu8yoxfylky

Comments

Similar projects you might like

Arduino - Use Reset Button Like a Push Button

by RoHa

  • 1,896 views
  • 0 comments
  • 12 respects

Fade Lights and Push Button

Project tutorial by Phuong Vo

  • 1,637 views
  • 2 comments
  • 7 respects

Candy Dispenser with Google Assistant

Project tutorial by Arduino “having11” Guy

  • 15,482 views
  • 1 comment
  • 51 respects

Automated NERF Gun Shooting Gallery

Project showcase by Keegan Neave

  • 2,819 views
  • 1 comment
  • 12 respects

DIY Code Red Button for Healthcare Simulation

Project tutorial by David Escobar

  • 2,252 views
  • 3 comments
  • 5 respects

WW2 Tank Laser Tag Sherman & Panther

Project tutorial by Arduino “having11” Guy

  • 15,441 views
  • 1 comment
  • 57 respects
Add projectSign up / Login