DIY Whac-A-Mole Arcade-Spiel (Tasten-Edition) – Arduino Mega Projekt
Komponenten & Materialien
| Arduino Mega 2560 | ×1 |
| Adafruit 1.4″ 4-stelliges 7-Segment-Display (I2C) | ×2 |
| Adafruit Arcade-Taster 60mm mit LED (5 Farben) | ×5 |
| Adafruit Audio FX Board 16MB | ×1 |
| Adafruit 2.5W Verstärker | ×2 |
| Lautsprecher 3W/4Ω | ×2 |
| Adafruit Ein/Aus-Taster mit LED, 2× 10k Potis + Knöpfe | je ×1/2 |
| Custom Arduino Mega Shield, Stapel-Headers, DC-DC 5V/3A, 12V Netzteil | je ×1 |
| Lasergeschnittenes MDF (6mm, 9mm), Plexiglas 3mm, 3D-Druckteile | 1 Satz |
| Schrauben M3/M4, USB-B Panelmount, Kabel | diverse |
Werkzeuge
- 3D-Drucker, Lötkolben, Schraubendreher
Apps & Services
- Arduino IDE, MDF-Lasercut-Service
Über das Projekt
Ein individuelles Whac-A-Mole-Spiel. Aktuell mit Basisspiel programmiert, erweiterbar für Updates.
Hauptfeatures
- 5 beleuchtete Tasten (PWM für Game-Over-Animation)
- 10 Level (1=leicht … 10=insane), variable Reaktionszeit pro Level & Schritt
- Pseudo-zufällige Tastenfolge (nie dieselbe Taste doppelt)
- 2 Displays: Aktueller Score/Countdown/Level + Highscore pro Level
- Digitaler Volume-Regler, Highscore-Speicherung (EEPROM)
- Soundeffekte (Loops pro Spielzustand) über eingebaute Lautsprecher
- Lasercut-Gehäuse, viele 3D-Druckteile, Custom Mega Shield
Gehäuse montieren
- 3D-gedruckte Halterungen auf Deckel schrauben.
- Optional lackieren.
- Display-Halterungen montieren (Muttern unter Halterung, sonst blockieren Vertikalstreben).
- Custom Shield aufbauen (Stapel-Headers für Abstand).
- Rückwand-Bedienfeld zusammenbauen (Volume & Level-Potis).
- Deckel bestücken: 5 Tasten + 2 Display-Panels.

Fazit
Fertig! Linkes Display: Levelwahl (1–10), rechtes: Highscore für dieses Level. Grün-Taste startet Countdown → Spielbeginn.

Code (Whac_A_Mole_v1.0.0)
/* 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 btnLed1Pin 9
#define btnLed2Pin 10
#define btnLed3Pin 11
#define btnLed4Pin 12
#define btnLed5Pin 13
byte btnLedArray[] = { btnLed1Pin, btnLed2Pin, btnLed3Pin, btnLed4Pin, btnLed5Pin };
#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 };
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 actualVolume = 35;
int gameLevel;
int currentScore;
int highScore;
int highScoreAddr = 1;
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 i=0;i<5;i++) pinMode(btnArray[i], INPUT);
for (int i=0;i<5;i++) pinMode(btnLedArray[i], OUTPUT);
for (int i=0;i<11;i++) { pinMode(fxTriggerArray[i], OUTPUT); digitalWrite(fxTriggerArray[i], HIGH); }
for (int i=0;i<2;i++) { pinMode(fxVolumeArray[i], OUTPUT); digitalWrite(fxVolumeArray[i], 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[3]) wackAMoleState = starting; // btn4 = Index 3
readGameLevelPotmeter();
ledBlinkStart();
}
if (wackAMoleState == starting) startGame();
if (wackAMoleState == running) {
playSound(fx1Trigger);
checkNextGameStep();
}
}
void checkNextGameStep() {
unsigned long now = millis();
int presses = 0;
for (int i=0;i<5;i++) if (btnPressed[i]) presses++;
if (previousMillisGameStep == 0) {
previousMillisGameStep = now;
digitalWrite(btnLedArray[3], LOW);
randomSeed(analogRead(2));
gameStepBtn = random(0,5);
gameStepLength = random(3000/gameLevel, 5000/gameLevel);
digitalWrite(btnLedArray[gameStepBtn], HIGH);
}
if ((now - previousMillisGameStep >= gameStepLength) || (presses > 1) || (presses == 1 && !btnPressed[gameStepBtn])) {
// GAME OVER
playSound(fx2Trigger);
wackAMoleState = gameOver;
previousMillisGameStep = 0;
for (int i=0;i<5;i++) analogWrite(btnLedArray[i], 255);
for (int b=255;b>0;b--) {
for (int i=0;i<5;i++) analogWrite(btnLedArray[i], b);
delay(10);
}
for (int i=0;i<5;i++) analogWrite(btnLedArray[i], 0);
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[0] && !btnPressed[1] && !btnPressed[2] && !btnPressed[3] && !btnPressed[4]);
matrix1.print(0); matrix1.writeDisplay();
wackAMoleState = waitForStart;
}
else if (btnPressed[gameStepBtn] && presses == 1) {
digitalWrite(btnLedArray[gameStepBtn], LOW);
int counter = 0;
do { delay(1); checkButtonInputs(); counter++; }
while (counter < 1000 && btnPressed[gameStepBtn]);
previousMillisGameStep = now;
int temp;
do { temp = random(0,5); gameStepLength = random(3000/gameLevel, 5000/gameLevel); }
while (gameStepBtn == temp);
gameStepBtn = temp;
digitalWrite(btnLedArray[gameStepBtn], HIGH);
currentScore++;
matrix1.print(currentScore); matrix1.writeDisplay();
if (currentScore > highScore) { matrix2.print(currentScore); matrix2.writeDisplay(); }
}
}
void ledBlinkStart() {
unsigned long now = millis();
if (now - previousMillisLedBlink >= intervalLedBlink) {
previousMillisLedBlink = now;
ledState[0] = !ledState[0];
digitalWrite(btnLedArray[3], ledState[0]);
}
}
void startGame() {
unsigned long now = millis();
digitalWrite(btnLedArray[3], 0);
currentScore = 0;
if (gameStartCountDown == 0) {
playSound(fx3Trigger);
previousMillisGameStarting = millis();
gameStartCountDown = 4;
delay(300);
matrix1.print(gameStartCountDown, DEC); matrix1.writeDisplay();
}
if (now - previousMillisGameStarting >= intervalCountDown) {
previousMillisGameStarting = now;
if (gameStartCountDown > 0) {
gameStartCountDown--;
matrix1.print(gameStartCountDown, DEC); matrix1.writeDisplay();
if (gameStartCountDown == 0) wackAMoleState = running;
}
}
}
void initializeSystem() {
for (int i=0;i<5;i++) { digitalWrite(btnLedArray[i], HIGH); delay(75); }
delay(1000);
for (int i=4;i>=0;i--) { digitalWrite(btnLedArray[i], LOW); delay(75); }
delay(1000);
wackAMoleState = waitForStart;
initVolume();
if (actualVolume > 0) enableAmpPower(true);
}
void checkButtonInputs() {
static byte prev[5], curr[5];
static long last;
if (last + debounceTime > millis()) return;
last = millis();
for (int i=0;i<5;i++) {
curr[i] = digitalRead(btnArray[i]);
if (curr[i] != btnPressed[i]) {
if (btnPressed[i] && !curr[i]) { if (debug) Serial.println("released "+String(i+1)); btnPressed[i]=0; }
if (!btnPressed[i] && curr[i]) { if (debug) Serial.println("pressed "+String(i+1)); btnPressed[i]=1; }
}
}
}
void initVolume() {
int target = analogRead(potmeterVolume)/30;
while (actualVolume > target) { digitalWrite(fxVolumeArray[1],LOW); actualVolume--; delay(20); digitalWrite(fxVolumeArray[1],HIGH); delay(20); }
while (actualVolume < target) { digitalWrite(fxVolumeArray[0],LOW); actualVolume++; delay(20); digitalWrite(fxVolumeArray[0],HIGH); delay(20); }
}
void readVolumePotmeter() {
int target = analogRead(potmeterVolume)/30;
if (actualVolume > target) { digitalWrite(fxVolumeArray[1],LOW); actualVolume--; delay(40); digitalWrite(fxVolumeArray[1],HIGH); if (actualVolume==0) enableAmpPower(false); }
else if (actualVolume < target) { digitalWrite(fxVolumeArray[0],LOW); if (actualVolume==0) enableAmpPower(true); actualVolume++; delay(40); digitalWrite(fxVolumeArray[0],HIGH); }
}
void readGameLevelPotmeter() {
int v = analogRead(potmeterGameLevel);
if (v < 102) gameLevel=1; else if (v < 204) gameLevel=2; else if (v < 306) gameLevel=3;
else if (v < 408) gameLevel=4; else if (v < 510) gameLevel=5; else if (v < 612) gameLevel=6;
else if (v < 714) gameLevel=7; else if (v < 816) gameLevel=8; else if (v < 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();
matrix2.print(highScore > -1 ? highScore : 0); matrix2.writeDisplay();
}
void playSound(int s) {
if (currentPlayingSound != s) {
for (int i=0;i<11;i++) digitalWrite(fxTriggerArray[i], HIGH);
digitalWrite(fxTriggerArray[s], LOW);
currentPlayingSound = s;
}
}
void enableAmpPower(bool on) { digitalWrite(ampPower, on); }
CAD-Dateien & Gehäuse
- Lasercut: MDF 6mm (.dxf), MDF 9mm (.dxf), Plexiglas 3mm (.dxf)
- Custom Mega Shield (.zip)
- 3D-Druck: Amp-Halter (2×), Mega-Halter, Rückwand-Anschluss, Kabelclips, Display-Halter (2×), Display-Verriegelung (4×), Display-Rahmen (2×), Deckel-Halter (vorne 2×, seitlich 2×, hinten)
Schaltplan
Siehe beiliegendes Schaltplan-Bild (PNG).
Herstellungsprozess
- Die richtigen Instrumentenknöpfe für Ihre Geräte auswählen
- Die richtigen Linsenkopfschrauben mit Innensechskant wählen: Ein praktischer Leitfaden
- Der Knopf: Vom antiken Verschluss zur modernen Innovation
- Bewegungsgesteuerter AWS IoT Button mit Raspberry Pi, PIR-Sensor und MQTT
- UnifiedWater v1 – Komplettes Arduino Wasserqualitäts-Sensorkit
- Portables Abstimmungssystem mit GSM-Anbindung (Mobile Vote Box)
- Den richtigen PCB-Prototyp-Hersteller wählen: Ein umfassender Leitfaden
- Optimale Sägegeschwindigkeit für Bandsägen wählen: Praktischer Leitfaden
- Die richtige Tauchpumpe wählen: Expertentipps & Hauptmerkmale
- Gründe für Vertikaldrehzentren in der Präzisionsbearbeitung