For Sistemas Digitales II (SDG2) at ETSIT UPM, my classmate Alicia and I built one of the most fun projects of my bachelor’s degree: a robotic combat game for the Raspberry Pi. Players drive a laser-equipped tricycle tank with an Xbox 360 controller — left stick to drive, right stick to aim the turret, trigger to fire an IR “laser” at your opponent. Each tank has limited lives, and the last one standing wins.
The whole thing is written in C (gnu99) on top of wiringPi, with a small Python
bridge that reads the controller through evdev and hands the state to the C program over a file
polled every 10 ms. The full source is open on GitHub:
github.com/alejp1998/tricycle-tank
📺 Demo video from the SDG2 DIE-UPM course channel, showing the physical tank in action.
🔧 The hardware
- Raspberry Pi (40-pin GPIO, wiringPi) as the brain
- Xbox 360 controller — wired or wireless, via the Python bridge
- L298N dual H-bridge driving the two rear wheels (tricycle steering via hardware PWM)
- 2× SG90-class servos on a pan/tilt turret (software PWM)
- IR emitter + receiver for the “laser” and hit detection (pin 9 out, pin 11 in as a rising-edge interrupt)
- Buzzer for sound effects via
softTone(pin 23)
⚙️ Four state machines on one 10 ms clock
The architecture is the part I am proudest of: four parallel finite-state machines running on a
shared 10 ms clock — the xbox360 FSM (input/debounce/joysticks), the player FSM
(sound effects), the ruedas FSM (wheels/steering) and the torreta FSM
(turret + laser shooting) — all built on a tiny reusable FSM engine (fsm.c) with the hit
detection handled by a wiringPi interrupt service routine. The IR hit logic includes debounce and shoot-timeout
so a single shot can never double-count.
The theory, in plain words: a finite-state machine is a flowchart the program actually lives inside — exactly one state active at a time, and every arrow between states has a condition. Formally, \[ \delta : S \times I \rightarrow S \] is a transition function mapping (current state, input event) to the next state. And PWM — how you control a motor with 0s and 1s — fakes “in between” by switching very fast: \[ \text{duty} = \frac{t_{\text{on}}}{T} \times 100\% \] The duty cycle sets the effective voltage, i.e. the motor speed; the wheels use hardware PWM while the turret servos use software PWM, where the pulse width itself encodes the angle (1 ms ≈ 0°, 1.5 ms ≈ 90°, 2 ms ≈ 180°).
Sound effects were a highlight: the buzzer plays Despacito, Game of Thrones, Tetris and Star Wars
note tables — selectable from the controller’s D-Pad and played with the A button. Because the FSM
engine and timer modules are pure C, the project ships with hardware-free tests: make test runs the
FSM unit tests and make check syntax-checks every source on any host using wiringPi stubs
— no Raspberry Pi required.
🎮 Play it in the browser
Years later I ported the game to the browser: a PixiJS web edition deployed on GitHub Pages with the same rules as the real tank — 10-bullet magazine (auto-reloads), 10 hits to win, 5 hull points to lose. Drive with WASD/arrows, aim with the mouse, fire with click/Space and reload with R, while AI tanks hunt you across three difficulty levels — 🟢 Training Grounds (2 target tanks), 🟡 Industrial Zone (3 hunters) and 🔴 Fortress Siege (4 relentless tanks, 9 walls) — and crates absorb laser fire. It even plays the original Tetris and Star Wars buzzer tables through WebAudio:
alejp1998.github.io/tricycle-tank
The in-game Guide modal explains the whole project — hardware, FSMs and controller mapping — and the web core ships its own test suite verifying the browser port against the original game logic.
At the other end of the scale, Training Grounds (easy) is where you learn the ropes — two target tanks, an open grid and a relaxed pace:
GitHub Repository Play the Game Video Raspberry Pi · wiringPi