Have you ever felt the need to blow off some steam, unleash your inner child, or just relive the thrill of smashing things from your arcade glory days? Enter Whack!T, a high-tech reimagining of the classic Whack-a-Mole game, blending nostalgia with engineering.
We built this arcade game from the ground up: a laser-cut wooden box houses nine pneumatic cylinders. But this isn’t just about brute force, we replaced traditional mechanical switches with magnets and 3D magnetic sensors, making hit detection fast and precise. And, of course, we couldn’t just use any hammer we 3D printed our own, built to withstand even the most intense gameplay. We also provide a full build guide: Assembly, coding, and calibration. Ready to dive in?
Game OverviewEver played Whack-A-Mole at a carnival? You know, a fake mole pops out of different holes and each time it pops out, you try to whack it. Whack!T modernizes this concept with:Players smash 9 pneumatic pistons that pop up randomly, driven by compressed air. But this isn’t your grandpa’s arcade game:
Smart Hit Detection: Magnets on the pistons interact with the latest series of Infineon's TLE493D magnetic sensors, replacing mechanical switches for instant, precise scoring.
Automated Game Flow: TheXMC4700 Relax Lite Kitmicrocontroller kit is the brain of the game and randomizes piston timing using the OPTIGA™ TrustMsecurity chip. The pneumatic valves for the pistons are controlled by a TLE94112ES 12-fold-half-bridge using a single chip! On top of that a DPS368 pressure sensor in the game and in the 3D-printed hammer triggers the game start as soon as the hammer is lifted.
Time & Score Tracking: A dual-digit 7-segment display shows the remaining play time and your final score.
How It Works
Start: Lift the hammer → DPS368 measures the height difference between hammer and game base and triggers the 5s countdown for game start.
Play: 15s countdown starts → OPTIGA™ Trust M chip randomizes pistons → TLE94112ES activates valves → pistons pop up.
Score: Strike a piston → TLE493D detects magnetic shifts → XMC4700 fires the next piston and increments the score.
What’s Next? We’ll start by building and testing the 9-piston pneumatic system, then integrate the 3D magnetic sensing mechanism, add the dual 7-segment displays, and finally we 3D-print the smart hammer and assemble everything into a polished arcade-ready game.
HardwarePneumatic System:Step 1: Drive a Pneumatic System Using a Single ChipThe pneumatic system in this build is based on the Hackster project: Drive a Pneumatic System Using a Single Chip. Follow this guide to create the backbone of your Whack!T game.
Step 2: Turning the Pneumatic System into Moving Pistons
To prepare for sensor testing, we’ll now move on to assembly. Make sure all your laser-cut parts and 3D-printed components are ready, as they’ll be used in every step from now on (refer to the attachments for design files).
Let’s start by assembling the piston enclosure, which consists of laser-cut parts glued together with cutouts for the 9 pistons and the sensors. After that, we place the 3D-printed guides for the pistons to lock them in place and prevent rotation. We also add some padding to the piston supports to help absorb shocks.
Next, we place the 9 pistons into their respective slots and glue the magnets to them, as they will play an important role in the next part of the build. Notice in the photo that we’ve also assembled the side walls, which are laser-cut as well and can also be attached later during the final assembly.
Next, we place the 9 piston cushions and screw the wooden plate that supports the pistons. This plate acts as the middle layer between the moving pistons and the pneumatic system. Finally, you can place the piston system onto the pneumatic system, and it’s ready for testing.
Now you’ve successfully built the pistons and the pneumatic system! Later on, we’ll add the OPTIGA™ Trust M chip to randomize piston behavior. You’ll notice there are some cutouts in the wooden housing of the pistons. These cutouts are meant for the holder of the 3D magnetic sensors🧲
The 3D magnetic sensors can measure the magnetic field in three directions: X, Y, and Z. We will place four sensors between the 9 pistons, making it possible to detect changes in the magnetic field from any of the pistons when they are hit.
On the bottom of the cylinders, we’ve placed magnets. These magnets allow us to detect when a cylinder is hit.
The magnets are placed as close as possible to the 3D magnetic sensors to ensure the magnetic field is strong enough to be detected accurately. For the sensors, we use the cutout board from the 3D magnetic sensor. Wiring instructions are provided in the attached Fritzing files.
The four sensors are mounted on a holder, as shown in the following picture.
Now, we can place the sensor holder inside the pneumatic system.
To make assembly more practical, we’ve created a small board with connectors. The wiring is shown in the attached Fritzing files; follow these for proper connections.
Now we can put the base plate with the 9 pneumatic cylinders on top of the assembly and close everything up. With this step, the mechanical part of the game is complete and ready for testing.
Finally, we can test the 9 pistons and the magnetic sensors (a test code is provided in the attachment files). After that, we can move on to the display part with the 7-segment screens.
Score DisplayWe use two 7-segment digits that we connected to each other to show the remaining time during the game and the final score.We’ll mount the display on the second part of the Whack!T game, which is designed to hold it. Start by assembling the laser-cut wooden parts as shown below. Once the frame is ready, mount the 7-segment display onto the wooden plate and route the wires through the wooden plate, where they’ll connect to the XMC microcontroller.
The hammer is a key part of the Whack!T game. To detect when the hammer is lifted, we use two DPS368 air pressure sensors: One sensor is placed inside the hammer. The other is placed inside the Whack!T enclosure as a reference.
By measuring the pressure difference between the two sensors, we can measure the height difference between hammer and game with an accuracy of ±2 cm and detect when the hammer is lifted. Wiring schematics for the pressure sensors are provided in the project files.
For the hammer assembly, you can 3D-print the parts using the provided.stl files. Here are some tips: Print the hammer horizontally for better stability. Increase the infill to 50% to add weight and improve durability and increase wall thickness to 2.4mm to increase robustness additionally. Add a support blocker in the cable tunnel area (shaft of the hammer) to avoid difficult-to-remove supports.
The hammer consists of three parts: The main hammer body and two flexible TPU caps. These caps protect the game from damage during intense gameplay.
At the top of the hammer, there’s space for the DPS368 pressure sensor. Simply push the sensor into place, as shown in the following picture:
The other sensor can be placed anywhere in the Whack!T game base.
Powering the GameThe XMC4700 Relax Kit requires a 5 V power supply, the display operates at 12 V, and both, the compressor and the valves, need a 9 V supply. To achieve the necessary voltage conversions, a buck converter is required to step down from 9 V to 5 V and step up from 9 V to 12 V 🤯
For this purpose, we are utilizing two TLD5190 VOLT DEMO evaluation boards. These boards allow you to select the desired output voltage by adjusting the on-board trimmer. For detailed instructions on setting up and using these evaluation boards, the user manual of the TLD5190 VOLT DEMO evaluation board provides a thorough explanation.
You can increase the voltage by turning the trimmer to the left and decrease the voltage by turning it to the right.
Now let's break down our Arduino code step by step! First, we will clarify how to use and test each component individually and then we will explain the whole application code for the Whack!T game.
Part 1: Code for ComponentsCode for 7-Segment Display
Before the game starts a 5s countdown is displayed. The game begins after this countdown and another 15s countdown starts from there. During this time, you have to hit as many cylinders as possible. At the end of the second countdown, your final score is displayed — all of this is shown using the 7-segment display.To test the two 7-segment digits, we used this well-explained example code. The only modification we made was changing the pin assignments to 50 (segmentLatch), 51 (segmentData) and 52 (segmentClock).
/*
Controlling large 7-segment displays
By: Nathan Seidle
SparkFun Electronics
Date: February 25th, 2015
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
This code demonstrates how to post two numbers to a 2-digit display usings two large digit driver boards.
Here's how to hook up the Arduino pins to the Large Digit Driver IN
There are two connectors on the Large Digit Driver. 'IN' is the input side that should be connected to
your microcontroller (the Arduino). 'OUT' is the output side that should be connected to the 'IN' of addtional
digits.
Each display will use about 150mA with all segments and decimal point on.
*/
//GPIO declarations
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
byte segmentClock = 52;
byte segmentLatch = 50;
byte segmentData = 51;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void setup()
{
Serial.begin(9600);
Serial.println("Large Digit Driver Example");
pinMode(segmentClock, OUTPUT);
pinMode(segmentData, OUTPUT);
pinMode(segmentLatch, OUTPUT);
digitalWrite(segmentClock, LOW);
digitalWrite(segmentData, LOW);
digitalWrite(segmentLatch, LOW);
}
int number = 0;
void loop()
{
showNumber(number); //Test pattern
number++;
number %= 100; //Reset x after 99
Serial.println(number); //For debugging
delay(500);
}
//Takes a number and displays 2 numbers. Displays absolute value (no negatives)
void showNumber(float value)
{
int number = abs(value); //Remove negative signs and any decimals
//Serial.print("number: ");
//Serial.println(number);
for (byte x = 0 ; x < 2 ; x++)
{
int remainder = number % 10;
postNumber(remainder, false);
number /= 10;
}
//Latch the current segment data
digitalWrite(segmentLatch, LOW);
digitalWrite(segmentLatch, HIGH); //Register moves storage register on the rising edge of RCK
}
//Given a number, or '-', shifts it out to the display
void postNumber(byte number, boolean decimal)
{
// - A
// / / F/B
// - G
// / / E/C
// -. D/DP
#define a 1<<0
#define b 1<<6
#define c 1<<5
#define d 1<<4
#define e 1<<3
#define f 1<<1
#define g 1<<2
#define dp 1<<7
byte segments;
switch (number)
{
case 1: segments = b | c; break;
case 2: segments = a | b | d | e | g; break;
case 3: segments = a | b | c | d | g; break;
case 4: segments = f | g | b | c; break;
case 5: segments = a | f | g | c | d; break;
case 6: segments = a | f | g | e | c | d; break;
case 7: segments = a | b | c; break;
case 8: segments = a | b | c | d | e | f | g; break;
case 9: segments = a | b | c | d | f | g; break;
case 0: segments = a | b | c | d | e | f; break;
case ' ': segments = 0; break;
case 'c': segments = g | e | d; break;
case '-': segments = g; break;
}
if (decimal) segments |= dp;
//Clock these bits out to the drivers
for (byte x = 0 ; x < 8 ; x++)
{
digitalWrite(segmentClock, LOW);
digitalWrite(segmentData, segments & 1 << (7 - x));
digitalWrite(segmentClock, HIGH); //Data transfers to the register on the risi ng edge of SRCK
}
}
Code for Optiga™ Trust M Shield2Go
The Optiga™ Trust M chip is used here to generate (real and cryptographic safe) random values between 1 and 9, a crucial step in our project since it detects what cylinder will pop up making the game unpredictable and challenging. But before we get there, we need to understand how the chip works.
We began by modifying an example from the Optiga™Trust M Arduino library, adapting it to fit our needs. First, let's take a look at the foundation of this code:
Header Files and Definitions
To get started, we need to include the "OPTIGATrustM.h" library, which gives us access to the Trust M functionalities. We define the maximum byte length for random number generation — setting it to 8 ensures that we can generate up to 8 random bytes at a time.
One additional tweak: the default code includes colored output (from "fprint.h"
), but for simplicity, we chose to disable it.
include "OPTIGATrustM.h"
#define RND_MAXLENGTH 8
// Disable colored output
#define SUPPRESSCOLORS
#include "fprint.h"
Setup Function
With everything in place, it's time to initialize the chip. The setup function ensures that the Trust M board is ready to generate secure random numbers.
One interesting feature here is the current limit—this setting affects how fast the board runs. If we increase the current limit, the board speeds up. For this setup, I’ve chosen a limit of 8 mA (setCurrentLimit(8)
), for a better balance between performance and stability.
void setup()
{
uint32_t ret = 0;
// Initialise a serial port for debug output
Serial.begin(115200);
delay(1000); // Wait for the serial port to initialize
Serial.println("Initializing ... ");
// Initialise an OPTIGA™ Trust X Board
printGreen("Begin to trust ... ");
ret = trustM.begin();
if (ret) {
printlnRed("Failed");
while (true); // Stop execution if initialization fails
}
printlnGreen("OK");
// Speed up the board (from 6 mA to 15 mA)
printGreen("Setting current limit to 15 mA ... ");
ret = trustM.setCurrentLimit(8);
if (ret) {
printlnRed("Failed");
while (true); // Stop execution if setting current limit fails
}
printlnGreen("OK");
}
Loop Function:Generating Random Numbers
Every time the loop runs, the Optiga™Trust M module provides 8 bytes of random data with the function getRandom(RND_MAXLENGTH, rnd)
. But we need a number between 1 and 9, so we convert the raw byte array into an int64_t number( convertByteArrayToInt64()
) and map it into our desired range using modulo 9 + 1. This ensures we always get a result within our target range.
void loop()
{
uint32_t ret = 0;
uint8_t *rnd = new uint8_t[RND_MAXLENGTH]; // Allocate memory for the random number array
// Initialize memory area to 0
memset(rnd, 0, RND_MAXLENGTH);
// Get a random number from the Trust M module
ret = trustM.getRandom(RND_MAXLENGTH, rnd);
if (ret) {
printlnRed("Failed");
while (true); // Stop execution if getting random number fails
}
// Convert the byte array to an int64_t number
int64_t random_value = 0;
random_value = convertByteArrayToInt64(rnd, 8);
// Map the value to the range 1-9
uint8_t random_number = 0;
random_number = (random_value % 9) + 1;
if(random_number > 9)
{
random_number = random_number % 9 + 1; // Ensure the random number is between 1 and 9
}
Serial.print("Random number between 1 and 9: ");
Serial.println(random_number);
Serial.println();
delete rnd; // Free the allocated memory
}
The result will look like this:
Code for 3D Magnetic Sensor
This part is well-documented in our Hackster article: Next Generation TLE493D: Your Guide to 3D Magnetic Sensing, but for this specific project, we need to adapt it to work with the XMC4700 microcontroller.
Before diving into the game mechanics, we need to test all four sensors to verify that they are correctly wired and functioning. The following code is designed specifically for quick diagnostics, allowing us to check whether each sensor is operational. With the four #define
statements, we can activate or deactivateindividual sensors for testing purposes. We also define numavg
, which determines how many times we measure each sensor before calculating an average value.
#include "TLx493D_inc.hpp"
#define SENSOR_1_ACTIVE
#define SENSOR_2_ACTIVE
#define SENSOR_3_ACTIVE
#define SENSOR_4_ACTIVE
#define numavg 50
using namespace ifx::tlx493d;
const uint8_t CHIP_SELECT_PIN_1 = 88;
const uint8_t CHIP_SELECT_PIN_2 = 91;
const uint8_t CHIP_SELECT_PIN_3 = 89;
const uint8_t CHIP_SELECT_PIN_4 = 92;
TLx493D_P3I8 sensor1(SPI2);
TLx493D_P3I8 sensor2(SPI2);
TLx493D_P3I8 sensor3(SPI2);
TLx493D_P3I8 sensor4(SPI2);
void setup() {
Serial.begin(115200);
delay(3000);
SPI2.begin();
#ifdef SENSOR_1_ACTIVE
sensor1.setSelectPin(CHIP_SELECT_PIN_1, OUTPUT, INPUT, LOW, HIGH, 0, 0, 0, 5);
sensor1.begin(true, true);
#endif
#ifdef SENSOR_2_ACTIVE
sensor2.setSelectPin(CHIP_SELECT_PIN_2, OUTPUT, INPUT, LOW, HIGH, 0, 0, 0, 5);
sensor2.begin(true, true);
#endif
#ifdef SENSOR_3_ACTIVE
sensor3.setSelectPin(CHIP_SELECT_PIN_3, OUTPUT, INPUT, LOW, HIGH, 0, 0, 0, 5);
sensor3.begin(true, true);
#endif
#ifdef SENSOR_4_ACTIVE
sensor4.setSelectPin(CHIP_SELECT_PIN_4, OUTPUT, INPUT, LOW, HIGH, 0, 0, 0, 5);
sensor4.begin(true, true);
#endif
Serial.print("setup done.\n");
}
void loop() {
double avg_temp = 0, avg_x = 0, avg_y = 0, avg_z = 0;
#ifdef SENSOR_1_ACTIVE
sensor1.printRegisters();
average_sensor_value(1, &avg_temp, &avg_x, &avg_y, &avg_z);
Serial.print("Sensor 1 - Temp: "); Serial.print(avg_temp); Serial.print(" °C, ");
Serial.print("Magnetic Field - X: "); Serial.print(avg_x); Serial.print(" mT, ");
Serial.print("Y: "); Serial.print(avg_y); Serial.print(" mT, ");
Serial.print("Z: "); Serial.println(avg_z); Serial.print(" mT\n");
#endif
#ifdef SENSOR_2_ACTIVE
average_sensor_value(2, &avg_temp, &avg_x, &avg_y, &avg_z);
Serial.print("Sensor 2 - Temp: "); Serial.print(avg_temp); Serial.print(" °C, ");
Serial.print("Magnetic Field - X: "); Serial.print(avg_x); Serial.print(" mT, ");
Serial.print("Y: "); Serial.print(avg_y); Serial.print(" mT, ");
Serial.print("Z: "); Serial.println(avg_z); Serial.print(" mT\n");
#endif
#ifdef SENSOR_3_ACTIVE
average_sensor_value(3, &avg_temp, &avg_x, &avg_y, &avg_z);
Serial.print("Sensor 3 - Temp: "); Serial.print(avg_temp); Serial.print(" °C, ");
Serial.print("Magnetic Field - X: "); Serial.print(avg_x); Serial.print(" mT, ");
Serial.print("Y: "); Serial.print(avg_y); Serial.print(" mT, ");
Serial.print("Z: "); Serial.println(avg_z); Serial.print(" mT\n");
#endif
#ifdef SENSOR_4_ACTIVE
average_sensor_value(4, &avg_temp, &avg_x, &avg_y, &avg_z);
Serial.print("Sensor 4 - Temp: "); Serial.print(avg_temp); Serial.print(" °C, ");
Serial.print("Magnetic Field - X: "); Serial.print(avg_x); Serial.print(" mT, ");
Serial.print("Y: "); Serial.print(avg_y); Serial.print(" mT, ");
Serial.print("Z: "); Serial.println(avg_z); Serial.print(" mT\n");
#endif
Average Sensor Values
Once initialized, the loop function continuously reads data from the sensors. The function average_sensor_value(),
measures and averages the values over multiple readings to get a more stable output.
The sensor’s built-in functions getTemperature()
and getMagneticField()
are used to extract data, while a timing function measures how long it takes to collect 50 values.
void average_sensor_value(uint sensor, double *sumTemp, double *sumX, double *sumY, double *sumZ)
{
// Initialize sum variables
double sumTemp1 = 0.0, sumTemp2 = 0.0, sumTemp3 = 0.0, sumTemp4 = 0.0;
double sumX1 = 0, sumY1 = 0, sumZ1 = 0;
double sumX2 = 0, sumY2 = 0, sumZ2 = 0;
double sumX3 = 0, sumY3 = 0, sumZ3 = 0;
double sumX4 = 0, sumY4 = 0, sumZ4 = 0;
double temp, valX, valY, valZ;
// Start time measurement
unsigned long startTime = millis();
// Loop for averaging
for (int i = 0; i < numavg; ++i) {
// Sensor 1
if(sensor == 1) {
sensor1.getTemperature(&temp);
sensor1.getMagneticField(&valX, &valY, &valZ);
sumTemp1 += temp;
sumX1 += valX;
sumY1 += valY;
sumZ1 += valZ;
if(i == numavg-1) {
*sumTemp = sumTemp1 / numavg;
*sumX = sumX1 / numavg;
*sumY = sumY1 / numavg;
*sumZ = sumZ1 / numavg;
}
}
// Sensor 2
else if(sensor == 2) {
sensor2.getTemperature(&temp);
sensor2.getMagneticField(&valX, &valY, &valZ);
sumTemp2 += temp;
sumX2 += valX;
sumY2 += valY;
sumZ2 += valZ;
if (i == numavg-1) {
*sumTemp = sumTemp2 / numavg;
*sumX = sumX2 / numavg;
*sumY = sumY2 / numavg;
*sumZ = sumZ2 / numavg;
}
}
// Sensor 3
else if(sensor == 3) {
sensor3.getTemperature(&temp);
sensor3.getMagneticField(&valX, &valY, &valZ);
sumTemp3 += temp;
sumX3 += valX;
sumY3 += valY;
sumZ3 += valZ;
if (i == numavg-1) {
*sumTemp = sumTemp3 / numavg;
*sumX = sumX3 / numavg;
*sumY = sumY3 / numavg;
*sumZ = sumZ3 / numavg;
}
}
// Sensor 4
else if(sensor == 4) {
sensor4.getTemperature(&temp);
sensor4.getMagneticField(&valX, &valY, &valZ);
sumTemp4 += temp;
sumX4 += valX;
sumY4 += valY;
sumZ4 += valZ;
if (i == numavg-1) {
*sumTemp = sumTemp4 / numavg;
*sumX = sumX4 / numavg;
*sumY = sumY4 / numavg;
*sumZ = sumZ4 / numavg;
}
}
}
// End time measurement
unsigned long endTime = millis();
unsigned long loopDuration = endTime - startTime;
Serial.print("Loop duration: ");
Serial.println(loopDuration);
}
Code forDPS Sensor
In our game, timing is everything. The game shouldn’t start until the player is ready — and that’s where the DPS sensors come in. These sensors are used to detect when the hammer is lifted, signaling the beginning of the count down.
- To achieve this, we use two DPS sensors:One sensor is placed inside the hammer to track its movement.
- The second sensor remains fixed inside the Whack!T setup, serving as a reference point.
A detailed explanation on how to use the sensor can also be found in this Hackster post , but here’s how we implemented it in our project.
Header Files and Objects
Dps3xx.h is included with #include <Dps3xx.h>
and the two Dps sensor objects are created:
#include <Dps3xx.h>
/**
* @details This example shows how to read temperature and pressure in a loop with a
* high oversampling rate.
* The oversampling rate can be set between 0 and 7 the higher the value the
* more precise the value is.
*/
// Dps3xx Object
Dps3xx Dps3xxPressureSensor = Dps3xx();
Dps3xx Dps3xxPressureSensor2 = Dps3xx();
Setup / Loop
Before we can track the hammer’s movement, we need to initialize the sensors and establish a baseline reading.
- The setup function ensures both sensors are properly initialized.
- Since the two sensors are in different positions, their pressure readings will naturally differ. This difference can be an equivalent to a height difference of up to eight meters, so we first calculate an initial reference difference to use as a baseline.
- The height difference is calculated in the
hight()
function, which we use later to detect movement. If the height difference is bigger then 10 cm the game will start.
void setup()
{
Serial.begin(9600);
while (!Serial);
/*
* Call begin to initialize Dps3xxPressureSensor
* The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given.
* Dps3xxPressureSensor.begin(Wire, 0x76);
* Use the line below instead of the one above to use the default I2C address.
* if you are using the Pressure 3 click Board, you need 0x76
*/
Dps3xxPressureSensor.begin(Wire1, 0x76);
Dps3xxPressureSensor2.begin(Wire1);
Serial.println("Init complete!");
delay(5000);
difference_sensor=calc_difference();
}
void loop()
{
float hight_now=hight();
Serial.println(difference_sensor);
Serial.println(hight_now);
if(abs(hight_now)>=10)
{
Serial.print("Hammer up");
}
else
{
Serial.print("Hammer down");
}
delay(500);
}
By using these two DPS sensors, we have created an intuitive game start mechanism. Players don’t need to press a button or flip a switch — the game automatically begins when they lift the hammer high enough.
Part 2: Code for Whack!T GameThe display is set up to show game time and scores, while the DPS and 3D magnetic sensors are configured to detect hammer lifting and successful hits, respectively. To establish a reliable reference, initial sensor values are recorded before the game begins.
DPS Sensors – Detect when the hammer is lifted, signaling the start of the game.
3D Magnetic Sensors – Track hits when the pistons are hit.
Multi-Half-Bridge & Actuators– Control the pistons that pop up for players to hit.
Compressor– Provides air pressure to move the pistons.
Display– Shows game time and score updates.
Setup
Before the action begins, all components of the system need to be initialized. The setup()
function takes care of this, preparing the display, sensors, and actuators.
void setup() {
Serial.begin(115200);
delay(6000);
init_display();
init_dps_sensor();
init_3d_sensor();
delay(1000);
measure_initial_values();
init_mhb();
kompressor.start(255);
delay(2000);
Serial.print("init done");
test = 1;
delay(500);
}
The display is set up to show game time and scores, while the DPS and 3D sensors are configured to detect hammer movements and successful hits. To establish a reliable reference, initial sensor values are recorded before the game begins (measure_initial_values()
). The multi-half-bridge (MHB), which controls the actuators responsible for moving the pistons is powered on, and the compressor starts building up air pressure (kompressor.start()
), ensuring that the pistons can move properly.
A short delay allows all components to stabilize before the game enters its main loop. Several key files manage different aspects of the game: display.hpp
handles the display functions, airpressure_sensor.hpp
processes data from air pressure sensors, 3d_sensor.hpp
detects when a cylinder is hit, push.hpp
controls the actuators, and game_matrix.hpp
manages the core game logic for piston movement.
GameLoop
Once everything is set up, the game enters its main loop, where it continuously monitors player actions, updates the display, and manages the game logic.
void loop() {
gametime = getRemainingSeconds();
if (start_counting == true && gametime >= 0) {
if (gametime >= 0) {
showNumber(gametime);
}
if (gametime == 0) {
hit_merken = game_hit_counter;
game_hit_counter = 0;
start_counting = false;
gametime = 15;
}
calculate_average(piston_now);
if (compare_difference(piston_now, 0.1) == true) {
hit_marker = true;
game_hit_counter++;
}
if (hit_marker == true) {
push(random(1, 10));
hit_marker = false;
}
} else {
showNumber(hit_merken);
start_game();
merken = 0;
}
return;
}
How the Game Works in the Loop:
1️⃣ Game Timer Management
- The function
getRemainingSeconds()
checks how much time is left. - If the game is active (
start_counting == true
) and time is remaining, it displays the countdown on the screen. - If the timer reaches zero, the game stores the final score, resets the hit counter, and gets ready for a new round.
2️⃣ Hit Detection and Scoring
- Hit detection is based on changes in the magnetic field. A hit is registered when the difference between the top position of a cylinder and its current position exceeds 0.1 mT.(
compare_difference(piston_now, 0.1)
) - The Score is implemented with the
game_hit_counter
.
3️⃣Cylinder Popping Mechanism
- The game continuously checks piston movement using
calculate_average(piston_now)
. - Once a hit is detected, the system randomly selects a piston (1 to 10) and activates it using
push(random(1, 10))
. - This makes the game unpredictable, keeping the player engaged by ensuring they don’t know which cylinder will pop up next.
4️⃣Game End and Restart
- If the timer runs out, the final score is displayed, and the game waits for the hammer to be lifted again before restarting.
- This smooth transition ensures a seamless gaming experience without needing extra buttons or manual resets.
Countdown Timer
The Whack!T game operates on a countdown timer, ensuring each round lasts for a fixed duration (15s). The getRemainingSeconds()
function calculates the remaining time by measuring how many milliseconds have passed since the game started. If the elapsed time exceeds the countdown duration, it returns 0.
int getRemainingSeconds() {
long currentMillis = millis();
long elapsedMillis = currentMillis - startMillis;
if (elapsedMillis >= countdownDuration) {
return 0;
} else {
return (countdownDuration - elapsedMillis) / 1000;
}
}
Starting a New Game
In the start_game()
function we check if the game is inactive (start_counting ==false
), then initiate a countdown. Once the countdown reaches 0, the game starts by recording the start time (startMillis
), setting the game duration, and enabling the countdown.
void start_game() {
if (start_counting == false) {
if (hammer_lift()) {
for (int i = 5; i >= 0; i--) {
showNumber(i);
delay(1000);
}
startMillis = millis();
gametime = 15;
start_counting = true;
}
}
}
This system ensures a smooth and automatic start, eliminating the need for manual input.
Now we can wrap up the Whack!T game code so, with everything in place, the game is ready to start! Get your hammer and aim for some cylinders!!
The Whack!T game combines Infineon’s sensors and actuators to create an interactive and dynamic retro gaming experience. With precise hit detection, automated game flow, and seamless hardware integration, it showcases the power of smart engineering. Good luck, and happy whacking!🔨🎯
Comments
Please log in or sign up to comment.