Introduction: Whack a Mole

About: Have built several Home Made arcade machine - virtual pinball, camel racing, coin pusher, skeeball, prize machines, Star Wars penny pusher and a replica Bells Cascade machine. In coming months I will be plann…

Initially started this project with a view to replicate an arcade Whack A Mole machine. However the mole mechanism proved too costly and buying spares from suppliers were around £80 a mole. I therefore opted for a version that used illuminated buttons to hit instead of moles.
In addition, I also wanted a machine that I could use on a table top or on a stand and the stand needed to be easily disassembled so it would take up less room for storage.

The game last 30 seconds and you get 10 points for hitting lit light and -5 points for hitting unlit light. Game initially starts with just one light at a time but this increases as score goes up. Time buttons are lit decreases as score goes up so game gets more difficult as score increases.
If you score 500 or more then you get an additional 15 seconds.
When game is started, there is a five second countdown and during last 5 seconds of game score flashes to warn time is running out.

Video footage of actual game can be found on Youtube

Supplies

Electronics:

1 x Arduino for controlling the game
8 x 50mm illuminated green LED arcade buttons
1 x arcade button for starting game
1 x 12V power supply (green button lights needed 12V supply)
8 x Relays (SRD-05VDC-SL-C)
7 segment LED display
Electrical connector Strips
Breadboard for Relays
Wiring


Miscellaneous:

8 Roofing Bolts and 8 Pronged Tee Nuts
Wood for frame
12mm plywood for top of Whack A Mole
3mm Hardboard for sides of game
Paint, Black Permanent Marker

Step 1: Stand for Whack a Mole

There are two lengths of wood (front and back panels) to support Whack A Mole unit when it is placed inside the stand. Pronged Tee nuts are used on the frame so when it is dismantled that the bolts are not lost.

Outside is stand is painted with gloss. Black outlines and black details were done using Black permanent marker.

Step 2: Underside of Whack a Mole

This shows the underside of the desktop unit. Arduino and relays are mounted on wooden rail at rear. The underside of the arcade buttons can be seen on the above photo

Step 3: Arduino Wiring

The photos above tend to indicate a bit of chaotic wiring but this is because I have wired everything through connector blocks to simplify any fault finding.
From the Arduino there are 9 inputs (eight for light switches and on for start button) and 8 outputs.

Input pins in this photo are pins 22, 24,26, 28, 30, 32, 34, 36 & 38
Output pins are 39, 41, 43, 45, 47, 49, 51, 53

The inputs are wired to the first connector block and a 10k Ohm resistor sits between that and the next connector block. There is a yellow wire from 2nd connector block for each of the connectors (after the resistor) connected back the GND connector.

Outputs are connected to the lower connector block that is then connected to the relays.

Circuit diagram is included on attached PDF

Step 4: Arduino Sketch Coding

This is the Arduino code that I have written for the game.
On powering up the first thing it does is rotate through the LED's on the 7 digit display to check that all is ok. Next it then switches on each LED one at a time so there is a visual check everything is okay on startup.

#include "Adafruit_LEDBackpack.h"

#include "Adafruit_GFX.h"

Adafruit_7segment matrix = Adafruit_7segment();

const int startLitTime = 3000; // How long Moles Stay Lit at Start of Games

const int Level2000ms = 50; // Score when Moles Lit Time changes to 2 seconds

const int Level1000ms = 200; // Score when Moles Lit Time changes to 1 second

const int Level500ms = 400; // Score when Moles Lit Time changes to half a second

const int MoleLevel2 = 100; // Score when number of Moles at a time changes from 1 to 2

const int MoleLevel3 = 300; // Score when number of Moles at a time changes from 2 to 3

int buttonPin[8] = {22, 24, 26, 28, 30, 32, 34, 36}; // Button Switches will be wired back to these pins

int buttonLight[8] = {39, 41, 43, 45, 47, 49, 51, 53}; // Pins to trigger relays for Button Lights

int buttonState[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // State of Input Switches to detect whe button is pressed

int lastButtonState[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // State of button last time looped.

int startButton = 40;

int startButtonState = 0;

boolean moleActive[8] = {false, false, false, false, false, false, false, false}; // Indicator if Mole should be active

long lastPressed[8]; // Time of when key was last pressed. Used to eliminate stuttering when switch contact is made

long moleStart[8]; // Start of mole lit time

long moleEnd[8]; // Expiry timeo mole's lit time

boolean Started = false;

boolean Finished;

int molesLit = 0; // Number of moles lit

int score = 0;

int hiScore = 100;

int newMole;

long startTimer;

int timeToPlay = 30;

long idleStart; // Used to calculate how long since idle on start up

boolean bonusUsed = false;

void setup() {

// initialize serial communication:

Serial.begin(9600);

//Adafruit 7-segment backpack

#ifndef __AVR_ATtiny85__

// Serial.begin(9600);

// Serial.println("7 Segment Backpack Test");

#endif

matrix.begin(0x70);

MatrixTest();

matrix.drawColon(false);

matrix.clear();

delay(1000);

// initialize the button pin as a input:

for (int i=0; i <= 7; i++){

Serial.println((String)"Initializing i="+i+". INPUT Pin="+buttonPin[5]);

pinMode (buttonPin[5], INPUT);

}

// Initialize Start button

pinMode (startButton, INPUT);

// Initialize the button pin as a input:

for (int i=0; i <= 7; i++){

Serial.print ("Initializing OUTPUT Pin ");

Serial.println (i);

pinMode (buttonLight[i], OUTPUT);

Serial.println ((String)"i="+i+" Pin:"+buttonLight[i]+" LOW");

digitalWrite(buttonLight[i], LOW);

delay (100);

Serial.println ((String)"i="+i+" Pin:"+buttonLight[i]+" HIGH");

digitalWrite(buttonLight[i], HIGH);

delay (510);

digitalWrite(buttonLight[i], LOW);

}

delay(3000);

idleStart = millis();

}

void loop() {

int secs;

int i8;

int i600;

secs = int((millis()-idleStart)/1000);

i8 = secs-(8*(int(secs/8))); // 0 to 7 depending on time since idle

if (i8<=3) {

// 0 to 3

matrix.clear();

matrix.print(hiScore, DEC);

matrix.writeDisplay();

matrix.blinkRate(2);

}

else {

// 4 to 7

UpdateScore();

matrix.blinkRate(0);

}

if (Started) {

StartGame();

Started = false;

}

startButtonState = digitalRead(startButton);

// Serial.println(startButtonState);

if (startButtonState == 1) {

Started = true;

}

i600 = secs-(600*(int(secs/600))); //every 10 minutes

//Serial.println(i600);

if (i600 == 599) {

// then idle for 10 minutes

// so make a noise to attract attention

//DUM 1

digitalWrite(buttonLight[0], HIGH);

delay (10);

digitalWrite(buttonLight[0], LOW);

delay (150);

//DIDDY 2 3

digitalWrite(buttonLight[2], HIGH);

delay (10);

digitalWrite(buttonLight[2], LOW);

delay (350);

digitalWrite(buttonLight[1], HIGH);

delay (10);

digitalWrite(buttonLight[1], LOW);

delay (250);

//DUMDUM 4 5

digitalWrite(buttonLight[5], HIGH);

delay (10);

digitalWrite(buttonLight[5], LOW);

delay (250);

digitalWrite(buttonLight[7], HIGH);

delay (10);

digitalWrite(buttonLight[7], LOW);

delay (650);

//DUM DUM 6 7

digitalWrite(buttonLight[6], HIGH);

delay (10);

digitalWrite(buttonLight[6], LOW);

delay (400);

digitalWrite(buttonLight[6], HIGH);

delay (10);

digitalWrite(buttonLight[6], LOW);

delay (3000);

}

}

void StartGame(){

int i;

int timeLeft;

long nowtime;

long keytime;

long startTimeLeft;

randomSeed(analogRead(0));

matrix.blinkRate(1);

matrix.print(5555, DEC);

matrix.writeDisplay();

delay(1000);

matrix.print(4444, DEC);

matrix.writeDisplay();

delay(1000);

matrix.print(3333, DEC);

matrix.writeDisplay();

delay(1000);

matrix.print(2222, DEC);

matrix.writeDisplay();

delay(1000);

matrix.print(1111, DEC);

matrix.writeDisplay();

delay(1000);

matrix.clear();

delay (1000);

matrix.blinkRate(0);

molesLit=0;

score=0;

timeToPlay = 30;

bonusUsed = false;

UpdateScore();

Finished = false;

Serial.println ("Starting Game");

for (int i=0; i <= 7; i++){

buttonState[i]=0;

lastButtonState[i]=0;

moleActive[i]=false;

}

startTimer=millis();

AddMole();

do

{

for (int i=0; i <= 7; i++){

buttonState[i] = digitalRead(buttonPin[i]);

if (buttonState[i]==1 && lastButtonState[i] == 0) { // then key has been pressed as state changed from 0 to 1

keytime=millis()-lastPressed[i];

if (keytime <= 400) { // then ignore as likely jitter\stutter

// do nothing

}

else {

lastPressed[i] = millis();

Serial.print ((String)"Button "+i+" pressed: "+buttonState[i]);

if (moleActive[i]==true) {

score=score+10;

UpdateScore();

// Set new mole first so current active one not selected;

AddMole();

molesLit = molesLit - 1;

moleActive[i]=0;

// const MoleLevel2 = 100;

// const MoleLevel3 = 300;

digitalWrite(buttonLight[i], LOW);

if (score>=MoleLevel2 && molesLit<=1) {

AddMole();

}

if (score>=MoleLevel3 && molesLit<=2) {

AddMole();

}

// Now check if score over 500 then add 15 seconds

if (bonusUsed == false && score>=500) {

bonusUsed=true;

timeToPlay = 45;

}

}

else {

// Penalty as Mole is not Active when button pressed

score=score-5;

if (score < 0) {

score=0;

}

UpdateScore();

} // End Else

}

}

// if (buttonState[i]==0 && lastButtonState[i] == 1)

// then button released and not need to deal with that event

lastButtonState[i]=buttonState[i];

} // end For

//Check Status of Moles

for (int i=0; i<=7; i++){

if (moleActive[i]) {

nowtime=millis();

if (moleEnd[i] < nowtime) {

Serial.println((String)"Mole "+i+" timed out.");

// Set new mole first so current active one not selected;

AddMole();

molesLit = molesLit - 1;

moleActive[i]=0;

digitalWrite(buttonLight[i], LOW);

}

}

} // End For

timeLeft=timeToPlay-int((millis()-startTimer)/1000);

//Serial.println(timeLeft);

if (timeLeft >=10) {

matrix.blinkRate(0);

}

else

if (timeLeft>=6) {

matrix.blinkRate(1);

}

else {

matrix.blinkRate(2);

}

if (timeLeft <= 0) {

Finished = true;

}

// When game ends, Finished is set to TRUE

} while (Finished == false);

// turn off all lights at end of game

for (int i=0; i <= 7; i++){

digitalWrite(buttonLight[i],LOW);

}

// Wait five seconds to show score

matrix.blinkRate(0);

UpdateScore();

delay(5000);

matrix.blinkRate(0);

UpdateScore();

if (score >= hiScore) {

matrix.blinkRate(2);

hiScore = score;

delay(5000);

}

}

void AddMole(){

int timetolive;

timetolive=startLitTime;

Serial.println((String)"Choosing new mole. Score= "+score+". Moles lit="+molesLit);

Serial.println("Choosing new Mole...");

ChooseMole();

Serial.print("Mole Active: ");

Serial.println(newMole);

moleActive[newMole]=1;

digitalWrite(buttonLight[newMole], HIGH);

// const Level2000ms = 50;

// const Level1000ms = 200;

// const Level500ms = 400;

if (score >= Level2000ms) {

timetolive = 2000;

if (score>= Level1000ms) {

timetolive = 1000;

if (score>=Level500ms) {

timetolive= 500;

}

}

}

moleStart[newMole]=millis();

moleEnd[newMole]=millis()+timetolive;

molesLit=molesLit+1;

//buttonLight[newMole]=HIGH;

}

void ChooseMole(){

int findMole;

boolean finished;

int state;

do {

finished=false;

findMole=random(8);

if (moleActive[findMole]==1) {

//Serial.println("Is Zero");

finished=false;

}

else {

//Serial.println("Is One");

finished=true;

}

state = digitalRead(buttonPin[findMole]);

if (state==1) {

finished=false;

}

} while (finished == false);

newMole=findMole;

}

void UpdateScore(){

matrix.clear();

matrix.print(score, DEC);

matrix.writeDisplay();

}

void MatrixTest(){

// A quick test that the 7-segment LED is working.

uint8_t counter = 0;

for (uint8_t d=0; d<25; d++) {

// paint one LED per row. The HT16K33 internal memory looks like

// a 8x16 bit matrix (8 rows, 16 columns)

for (uint8_t i=0; i<8; i++) {

// draw a diagonal row of pixels

matrix.displaybuffer[i] = _BV((counter+i) % 16) | _BV((counter+i+8) % 16) ;

}

// write the changes we just made to the display

matrix.writeDisplay();

delay(100);

counter++;

if (counter >= 16) counter = 0;

delay(1);

}

// Must execute .writeDisplay() after .clear()

matrix.clear();

matrix.writeDisplay();

}