Introduction: Handheld Whack-a-mole Game Powered With Arduino UNO

A handheld console for playing whack-a-mole. press the correct keypad number (1-9) to whack the moles (LED's 1-9) the more moles you hit in a row the harder the game gets, if you miss a mole then the whole screen will flash. Here you can see a final demo of the project.

Supplies

1x Arduino UNO

9x WS2812B Digital 5050 RGB LED (make sure it is the single LED you can solder)

1x Membrane 4x4 Matrix keypad switch (OT2023-B116)

12x long Male to Male cable (for connecting LED strip and keypad)

6x Medium Male to Male cable (long turns in the led strip)

18x Short Male to Male cable (for creating the led strip)

1x Resistor 470Ω

Wooden Case made from 3mm thick plywood

USB 2.0 Cable Type A/B connected to a power source

Step 1: Design Phase

My first idea for the whack-a-mole game was with a resistance ladder since I thought this was the only way to get enough buttons in my data slots. This is my sketch for the whack-a-mole game with resistance ladders built-in, underneath you see this resistance ladder.


After searching around for a bit I found that I could use a keypad instead of single buttons, this would save up a lot of slots in the Arduino and would also be a lot easier and less messy to make. I still would not have enough slots to power every single individual LED. For this, I could solder my LED strip from individual LEDs. Here is the sketch for the new version.

Step 2: Creating the Project

Step 3: The Code

This is the code needed to run the project. There are a couple of features:

  • making use of the keypad
  • making use of the LED strip
  • logic to select random moles
  • logic to bind keypad numbers to LEDs
  • Keypad numbers 1-9 control the LEDs
  • * as start button and # as stop button
  • logic for mole hit detection
  • Speed increases the more moles correctly hit
  • flashes all LED's when the wrong mole is hit or the mole is not hit in time
#include <Adafruit_NeoPixel.h>
#include <Keypad.h>


// NeoPixel related constants
const int neoPixelPin = 11;  // control pin for NeoPixel
const int numPixels = 9;     // number of pixels
Adafruit_NeoPixel strip = Adafruit_NeoPixel(numPixels, neoPixelPin, NEO_GRBW + NEO_KHZ800);


// Keypad setup
const byte ROWS = 4;  // four rows
const byte COLS = 4;  // four columns


// Map the buttons to an array for the Keymap instance
char hexaKeys[ROWS][COLS] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};


byte rowPins[ROWS] = { 2, 3, 4, 5 };  // Pins used for the rows of the keypad
byte colPins[COLS] = { 6, 7, 8, 9 };  // Pins used for the columns of the keypad


// Initialise the Keypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);


static int score = 0;  //score counter
char keyValue;
int mark = -1;
bool gameActive = false;
int gameSpeed = 1000;  // Initial speed


void lightUpMole(int mole, bool on = true) {            //activates the leds and gives them colour
  uint32_t color = on ? strip.Color(255, 0, 0, 0) : 0;  // Red if on, off if not
  strip.setPixelColor(mole - 1, color);                 // Adjust for 1-based index
  strip.show();
  Serial.print("Mole ");                //code to check functionality in the serial monitor
  Serial.print(mole);                   //serial monitor check
  Serial.println(on ? " ON" : " OFF");  //serial monitor check
}


void blinkAllRed() {  //flashes all the leds when the wrong mole is hit
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < numPixels; j++) {
      strip.setPixelColor(j, strip.Color(255, 0, 0, 0));
    }
    strip.show();
    delay(150);
    strip.clear();
    strip.show();
    delay(150);
  }
}


void setup() {  //code on startup or connection with power
  delay(500);
  Serial.begin(9600);


  strip.begin();  // initialize pixel strip
  strip.clear();  // turn all LEDs off
  strip.show();   // refresh strip


  showStartupSequence();
}


void loop() {                        //code that loops the entire time
  keyValue = customKeypad.getKey();  //makes keyvalue the pressed button on the keypad


  if (keyValue) {
    if (keyValue == '*') {  //starts the actual game after * is pressed on the keypad
      gameActive = true;
      Serial.println("Game Started");  //serial monitor check
      score = 0;                       // Reset score when game starts
      gameSpeed = 1000;                // Reset speed when game starts
    } else if (keyValue == '#') {      //stops the game when # is pressed
      gameActive = false;
      Serial.println("Game Stopped");  //serial monitor check
      strip.clear();
      strip.show();
    }
  }


  if (gameActive) {  //selects random leds(moles) to set active
    int mole = random(1, numPixels + 1);
    if (mole == mark) {
      mole = random(1, numPixels + 1);
    }


    lightUpMole(mole);


    unsigned long time = millis();
    bool moleHit = false;
    while (millis() - time < gameSpeed) {  // gamespeed makes the game go faster if multiple moles are hit correctly in a row
      keyValue = customKeypad.getKey();
      if (keyValue) {
        Serial.print("Key pressed: ");  //serial monitor check
        Serial.println(keyValue);       //serial monitor check
        int keyNum = keyValueToIndex(keyValue);
        Serial.print("Key number: ");  //serial monitor check
        Serial.println(keyNum);        //serial monitor check
        if (keyNum == mole) {          //code to check if mole is hit
          moleHit = true;
          break;
        }
      }
      delay(10);
    }


    if (moleHit) {                   //code that triggers when a correct mole is hit
      for (int i = 0; i < 2; i++) {  // Blinks twice if the mole is hit
        lightUpMole(mole, false);
        delay(100);
        lightUpMole(mole, true);
        delay(100);
      }
      score++;
      gameSpeed = max(200, gameSpeed - 50);  // Decrease gameSpeed, but not below 200ms
    } else {                                 //code that triggers if the correct mole is not hit
      blinkAllRed();                         // Blink all lights red if no mole is hit
      gameSpeed = 1000;                      // Reset speed when a wrong mole is hit
    }
    lightUpMole(mole, false);


    if (score == 30) {
      score = 0;
      gameSpeed = 1000;  // Reset speed after winning (a win is a score above 30)
    }
    mark = mole;
  }
}


void showStartupSequence() {  //checks all the leds on startup
  for (int i = 0; i < numPixels; i++) {
    strip.setPixelColor(i, strip.Color(255, 255, 255, 0));  // sets the leds to white
    strip.show();
    delay(100);
  }
  strip.clear();
  strip.show();
}


int keyValueToIndex(char keyValue) {  //gives each number on the keypad a corresponding mole/led
  switch (keyValue) {
    case '1': return 6;  // Button 1 maps to LED 1
    case '2': return 6;  // Button 2 maps to LED 2
    case '3': return 7;  // Button 3 maps to LED 3
    case '4': return 5;  // Button 4 maps to LED 4
    case '5': return 4;  // Button 5 maps to LED 5
    case '6': return 3;  // Button 6 maps to LED 6
    case '7': return 1;  // Button 7 maps to LED 7
    case '8': return 2;  // Button 8 maps to LED 8
    case '9': return 3;  // Button 9 maps to LED 9
    default: return -1;
  }
}


Step 4: The Box

The final step is a box to contain everything in, I created this box that is easy to put together like a puzzle. This file is completely ready for a laser cutter, I used 3mm thick plywood. After putting all the pieces together (the sides are named in the file) you need to make sure the LEDs keep in place, I used clear tape for this so I could see the position clearly.

After that you need to put the 4x4 keypad through the hole and with the sticker side you can secure it to the top of the box. Then all that is left is to put the power cable through. If that is all done it should look something like this.

Step 5:

Here is a final showcase of the project