DIY Whac-A-Mole Arcade Spel (Knoppen Editie) – Arduino Project
Onderdelen en Benodigdheden
| Onderdeel | Aantal |
|---|---|
| Arduino Mega 2560 | 1 |
| Adafruit 1,4" 4-digit 7-segment display met I2C backpack | 2 |
| Adafruit Grote arcade knop met LED (60mm) - 5 verschillende kleuren | 5 |
| Adafruit Audio FX board 16MB | 1 |
| Adafruit 2,5W versterker | 2 |
| Luidspreker: 3W, 4 ohm | 2 |
| Adafruit Aan/Uit knop met LED | 1 |
| Adafruit Potentiometer 10k | 2 |
| Adafruit Potentiometer knop | 2 |
| Draad | 1 set |
| Ingezette schroeven M3 + moeren | 1 set |
| Ingezette schroeven M4 + moeren | 1 set |
| Alle 3D-geprinte onderdelen | 1 set |
| Alle gelaserde MDF-onderdelen | 1 set |
| Custom Arduino Mega shield | 1 |
| Arduino Mega stackable headers (voor spacing) | 1 |
| Adafruit DC step-down converter 5V 3A | 1 |
| 12V voeding | 1 |
| Female PSU bus | 1 |
| USB-B paneelmontage | 1 |
Nodige Tools en Machines
- 3D-printer (generiek)
- Schroevendraaier
- Solderbout (generiek)
Apps en Online Services
- Arduino IDE
- MDF lasercut service
Over Dit Project
Dit is een custom Whac-A-Mole spel. Voor nu is het geprogrammeerd met een basisspel, maar updates zijn in de toekomst mogelijk.
Belangrijkste Kenmerken
- 5 knoppen voor het spel zelf. Elke knop heeft een eigen heldere LED. LED's zijn PWM-gestuurd (gebruikt voor game-over sequentie).
- 10 selecteerbare niveaus. Niveau 1 (makkelijk) tot 10 (onzinnig moeilijk). Tijdens het spel moet je de juiste knop binnen een bepaalde tijd indrukken. Deze duur is variabel binnen elk niveau (soms sneller, soms langzamer).
- (Pseudo) willekeurige knopselectie tijdens gameplay. De volgende stap (knop die ingedrukt moet worden) is altijd een andere knop.
- 2 LED-cijferdisplays. Één voor huidige score/aftellen/niveau selectie en één voor highscore weergave.
- Digitaal volumecontrole
- Highscore opslag
- Geluidseffecten. Tijdens verschillende spelstatussen worden geluidsloops afgespeeld via de ingebouwde luidsprekers.
- Gelaserde behuizing
- Veel 3D-geprinte onderdelen
- Custom Arduino Mega shield
Behuizing
Begin met het monteren van de behuizing.


Monteer de 3D-geprinte montagebeugels op de bovenkant.

Indien gewenst, verf de behuizing.
Monteer daarna de LED-display beugels. Vergeet niet de bouten onder de beugels te plaatsen, anders kunnen de verticale staven niet vastgezet worden.

Monteer de Arduino shield. Ik gebruikte een extra set stackable headers, zodat er meer ruimte is tussen de shield en de Arduino.

Monteer het achterpaneel met besturingselementen.

Monteer de bovenkant (knoppen & LED-panelen).




Het achterpaneel bevat twee potentiometerbesturingen: één voor volumecontrole, één voor niveauselectie.


Conclusies
Klaar!
Op het linker LED-cijferpaneel zie je het geselecteerde niveau. Je kunt niveau 1 (makkelijk) tot 10 (onzinnig moeilijk) selecteren. Op het rechter LED-cijferpaneel zie je de highscore voor het corresponderende niveau. Tijdens een actief spel toont het linker paneel de huidige score. Druk nu op de groene knop om het spel te starten. Na een aftelling begint het spel.

Code
Whac_A_Mole_v1.0.0 (Arduino)
Arduino-code, geschreven in 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
#define potmeterVolume 0
#define potmeterGameLevel 1
#define ampPower 32
#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
#define btn2 1
#define btn3 2
#define btn4 3
#define btn5 4
#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
#define btnLed2 1
#define btnLed3 2
#define btnLed4 3
#define btnLed5 4
#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
#define fx1Trigger 1
#define fx2Trigger 2
#define fx3Trigger 3
#define fx4Trigger 4
#define fx5Trigger 5
#define fx6Trigger 6
#define fx7Trigger 7
#define fx8Trigger 8
#define fx9Trigger 9
#define fx10Trigger 10
#define fxVolUp 0
#define fxVolDn 1
signed int currentPlayingSound = -1;
byte btnPressed[5], ledState[5];
unsigned long previousMillisLedBlink = 0;
#define intervalLedBlink 750
unsigned long previousMillisGameStarting = 0;
#define intervalCountDown 1000
byte gameStartCountDown;
unsigned long previousMillisGameStep = 0;
int gameStepLength = 0;
int gameStepBtn = 0;
int valPotmeterVolume = 0;
int volume = 0;
int actualVolume = 35;
int valPotmeterGameLevel = 0;
int gameLevel;
int currentScore;
int highScore;
int highScoreAddr = 1;
byte activeBtn;
bool debug;
byte wackAMoleState;
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);
pinMode(ampPower, OUTPUT);
digitalWrite(ampPower, LOW);
for (int Pin = 0; Pin < 5; Pin++) { pinMode(btnArray[Pin], INPUT); }
for (int Pin = 0; Pin < 5; Pin++) { pinMode(btnLedArray[Pin], OUTPUT); }
for (int Pin = 0; Pin < 11; Pin++) { pinMode(fxTriggerArray[Pin], OUTPUT); digitalWrite(fxTriggerArray[Pin], HIGH); }
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());
cli();
highScore = eeprom_read_word((uint16_t*)(gameLevel * 2));
sei();
matrix1.print(0);
matrix1.writeDisplay();
matrix2.print(0);
matrix2.writeDisplay();
initializeSystem();
}
void loop() {
checkButtonInputs();
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);
}
if ((currentMillisGameStep - previousMillisGameStep >= gameStepLength) || (amountOfButtonPresses > 1) || (amountOfButtonPresses == 1) && !btnPressed[gameStepBtn]) {
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());
cli();
eeprom_write_word((uint16_t*)(gameLevel * 2), currentScore);
sei();
}
int counter = 0;
do { delay(1); checkButtonInputs(); counter++; } while ((counter < 1000) && !btnPressed[btn1] && !btnPressed[btn2] && !btnPressed[btn3] && !btnPressed[btn4] && !btnPressed[btn5]);
matrix1.print(0);
matrix1.writeDisplay();
wackAMoleState = waitForStart;
} else if (btnPressed[gameStepBtn] && (amountOfButtonPresses == 1)) {
digitalWrite(btnLedArray[gameStepBtn], LOW);
int counter = 0;
do { delay(1); checkButtonInputs(); counter++; } while ((counter < 1000) && btnPressed[gameStepBtn]);
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);
currentScore++;
matrix1.print(currentScore);
matrix1.writeDisplay();
if (currentScore > highScore) {
matrix2.print(currentScore);
matrix2.writeDisplay();
}
}
}
void ledBlinkStart() {
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]);
}
}
void startGame() {
unsigned long currentMillisGameStarting = millis();
digitalWrite(btnLedArray[btnLed4], 0);
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() {
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() {
static byte previousstate[5];
static byte currentstate[5];
static long lasttime;
byte index;
if ((lasttime + debounceTime) > millis()) { return; }
lasttime = millis();
for (index = 0; index < 5; index++) {
currentstate[index] = digitalRead(btnArray[index]);
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());
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 Onderdelen en Behuizingen
Lasercut MDF onderdelen 6mm OC9CvHjuTzsYL9POZCJt.dxf
Lasercut MDF onderdelen 9mm 1irkeqvO4ABzhKqhmyG7.dxf
Lasercut Plexiglas onderdelen 3mm j4Tnmni6dQ0KXJtiHcy1.dxf
Custom Arduino Mega Shield 5yiRX1RdQuPX0gNXjYkm.zip
3D-geprinte versterker mount, print 2x
3D-geprinte Arduino Mega mount
3D-geprinte achterpaneel mount
3D-geprinte achterpaneel
3D-geprinte kabelklem, print zoveel je wilt
3D-geprinte LED-display mount, print 2x
3D-geprinte LED-display mount lock, print 4x
3D-geprinte LED-display top frame, print 2x
3D-geprinte bovenkant mount achter
3D-geprinte bovenkant mount voor, print 2x
3D-geprinte bovenkant mount zij, print 2x
Schema's
Productieproces
- De Knop: Van Oude Sluiting tot Moderne Innovatie
- Bouw een Beweging-gestuurde AWS IoT-knop met Raspberry Pi, PIR-sensor en MQTT
- UnifiedWater v1 – Complete Arduino Waterkwaliteit Sensor Kit
- Draagbaar stemstysteem met GSM-connectiviteit
- Moderne Jukebox: Raspberry Pi & Arduino LED Muziekspeler
- De juiste PCB-prototypefabrikant kiezen: een uitgebreide gids
- Selecteren van de Optimale Zaagsnelheid voor Bandsagen: Praktische Gids
- De juiste potgietpomp kiezen: expert tips & sleutelkenmerken
- Redenen Om Verticale Draaicentra Te Kiezen Voor Precisiebewerking
- Hoe de Juiste Instrumentenknoopen voor Uw Apparatuur te Kiezen
- De Juiste Ronde Kop Inbusschroeven Kiezen: Een Praktische Gids