TFT Games
The playable games built for the ELEGOO TFT touch shield, with CPU-played demos.
Juno ships 14 games (and a paint demo) for the ELEGOO 2.8" TFT touch screen shield on the Arduino UNO R4 WiFi. Every one is plain Java in juno-examples (the paint demo lives in api/tft), compiled ahead of time by Juno to Cortex-M4 assembly, with no JVM or interpreter on the board: just the Java subset described in FEATURES.md and the TftTouchShield API described in TFT-TOUCH-SHIELD.md.
How to play
Set up arduino-cli and the UNO R4 core as described in ARDUINO.md, plug the shield onto the board, and install the reactor once so the example module can resolve the Juno plugin:
./mvnw install
Then flash a game by its class name. Every game in this page lists its own command; they all follow this pattern:
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.<Game>
juno:upload finds the board by itself, or takes -Djuno.port=<PORT>. To check that a game builds without touching any hardware, use juno:verify instead of juno:upload.
Most games target one board, so that single command is all they need. A few, like Tempest, declare
@Board({ArduinoUnoQ.class, ArduinoUnoR4WiFi.class}) because they use nothing board-specific — for
those, add -Djuno.board=<id> (arduino-uno-q or arduino-uno-r4-wifi) to say which one to build for; omitting it
when more than one board is declared fails with the list of choices instead of guessing.
The screenshots were rendered on a desktop by running each game's unmodified code, including the real TftTouchShield driver and its font, against an emulation of the shield's ILI9341 display controller (see Testing without the board). The real panel's colors vary slightly.
Contents
- Arcade: Pac-Man, Missile Command, Tempest, Star Wars, The Empire Strikes Back, Red Baron, Star Trek, Space Paranoids, DOOM, Lunar Lander
- Board and strategy: Chess, Battleship
- Cards and casino: Blackjack, Texas Hold'em
- Network: Gladiator
Arcade
Real-time games driven by touch.
Pac-Man
Eat every dot in the maze while four ghosts chase you. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.pacman.PacMan
Missile Command
Defend six cities from falling warheads by tapping the sky to launch interceptors. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.missilecommand.MissileCommand
Tempest
Move a claw around the rim of a tube and shoot the enemies climbing up it. Play it yourself or watch the CPU. Runs on both the UNO Q and the UNO R4 WiFi; pick one with -Djuno.board:
# Arduino UNO Q
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.tempest.Tempest \
-Djuno.board=arduino-uno-q
# Arduino UNO R4 WiFi
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.tempest.Tempest \
-Djuno.board=arduino-uno-r4-wifi
Star Wars
Fly an X-wing against TIE fighters, then down the Death Star trench to hit the exhaust port. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.starwars.StarWars
The Empire Strikes Back
Fly a snowspeeder over Hoth against probe droids and AT-AT walkers, then the Millennium Falcon through an asteroid field. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.empirestrikesback.EmpireStrikesBack
Red Baron
Dogfight enemy biplanes and strafe ground targets from the cockpit of a First World War plane. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.redbaron.RedBaron
Star Trek
Command the Enterprise through sectors of Klingon battlecruisers with phasers, photon torpedoes and warp. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.startrek.StarTrek
Space Paranoids
Drive a tank through a wireframe maze and destroy the flying hunters before time runs out. Play it yourself or watch the CPU. Runs on the Arduino UNO Q.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.spaceparanoids.SpaceParanoids
DOOM
Wireframe DOOM in landscape, drawn with DOOM's own BSP renderer: walls, doors, lifts and monsters from all four episodes. Pick HUMAN (touch to turn, walk and fire) or CPU (an autopilot that plays like a person and sometimes dies), then choose the episode and skill. Movement keeps DOOM's rules: the marine climbs steps of at most 24 units, slides along walls and round corners, cannot pass the windows and other lines the map marks impassable, and keeps a small body's distance from walls, so the camera never sees through them. Lifts lower when the marine comes near one of their lines, wait and rise again, and doors opened by a switch or trigger elsewhere open as the marine approaches. Tap the marine's face in the status bar to pause: continue, or quit to the title. Runs on the Arduino UNO Q.
The marine carries DOOM's arms: fist and chainsaw (slot 1), pistol, shotgun, chaingun, rocket launcher, plasma rifle and BFG 9000 (slots 2-7), fed by bullets, shells, rockets and cells with DOOM's pickup amounts and limits, doubled by a backpack. Weapons and ammo lie in the map, zombiemen drop clips and sergeants their shotguns, and the status bar shows the ammo in hand and lights the weapons carried. Tap a number in the ARMS panel to raise that weapon, or the ARMS label to swap between fist and chainsaw; a weapon that runs dry gives way to the best one left. Rockets blast everything near where they burst, the marine included, and the BFG's ball sprays what he faces. He keeps his arms from map to map and loses them when he dies.
At startup the game scans your own DOOM1.WAD on the shield's SD card with java.io.RandomAccessFile (see Storage), but does not load a map yet. After the menus it loads the selected episode's first map, with a progress bar and the seconds left. Reaching an exit opens a DOOM-style intermission with kills, items, discovered secrets and elapsed time; while it shows, the next map is read and the CPU's route planned (LOADING E1M2 with the bar), and a tap on TAP TO PLAY E1M2 starts it. When the marine dies, choose to restart the map or quit to the title. Finishing E?M8 opens an episode story card and then returns to the title; tapping the title opens the pilot, episode and skill menus again. Difficulty controls the WAD's easy, medium and hard monster and pickup placements. With no card, no file, or too little arena, it plays a small original test map built into flash instead; DOOM's levels are never committed. Copy DOOM1.WAD to the card's root, then build with a bigger arena, since the largest supported map tables are held in RAM:
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.doom.Doom -Djuno.Xmx=92k
The CPU plans its route through the map's subsectors to the exit, riding lifts and opening doors on the way, steps off it for weapons it lacks, ammo, health and armor it can see, and fights with the weapon that hits hardest at each monster's distance (the chainsaw when one comes close, never a rocket that would blast itself), and finishes E1M1, E1M2, E1M5, E1M6 and E1M7. Maps whose exit needs stairs that build or floors that move are not finished yet: there the CPU walks as close to the exit as it can reach. Reading a map from the SD card takes 10 to 35 seconds on the UNO Q, and planning its route a few more. juno:monitor shows what the game is doing over serial: each map's read and planning time, and with every frame report the frame rate, the marine's position, the waypoint the CPU walks to, its health, the weapon in hand with its ammo, and the routes it planned.
Lunar Lander
Land a spacecraft softly on a pad before the fuel runs out. Play it yourself or watch the CPU.
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.lunarlander.LunarLander
Board and strategy
Turn-based games against a computer opponent that searches ahead.
Chess
Play White against a 3-ply chess engine, or watch it play itself. Runs on both the UNO Q and the UNO R4 WiFi; select the connected board explicitly.
# Arduino UNO Q
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.chess.Chess \
-Djuno.board=arduino-uno-q
# Arduino UNO R4 WiFi
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.chess.Chess \
-Djuno.board=arduino-uno-r4-wifi
Battleship
Sink the enemy fleet on a grid before it sinks yours. Play it yourself or watch two CPU commanders. Runs on both the UNO Q and the UNO R4 WiFi; select the connected board explicitly.
# Arduino UNO Q
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.battleship.Battleship \
-Djuno.board=arduino-uno-q
# Arduino UNO R4 WiFi
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.battleship.Battleship \
-Djuno.board=arduino-uno-r4-wifi
Cards and casino
Card games and games of chance, played for chips or credits only.
Blackjack
Beat the dealer to 21 with chips. Play it yourself or watch an automated player. Runs on both the UNO Q and the UNO R4 WiFi; select the connected board explicitly.
# Arduino UNO Q
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.blackjack.Blackjack \
-Djuno.board=arduino-uno-q
# Arduino UNO R4 WiFi
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.blackjack.Blackjack \
-Djuno.board=arduino-uno-r4-wifi
Texas Hold'em
No-limit Texas Hold'em against three CPU opponents. Play a seat yourself or watch all four play. Runs on both the UNO Q and the UNO R4 WiFi; select the connected board explicitly.
# Arduino UNO Q
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.texasholdem.TexasHoldem \
-Djuno.board=arduino-uno-q
# Arduino UNO R4 WiFi
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.texasholdem.TexasHoldem \
-Djuno.board=arduino-uno-r4-wifi
Network
Gladiator
After the opening, SOLO runs an entire CPU-versus-CPU duel locally on one UNO Q: choose a historical name (Spartacus, Crixus, Flamma or Priscus) and a class. A versus view rotates that gladiator beside a random opponent CPU with a different historical name and a random class; tap PLAY there to begin the bout. The chosen arms appear in the player's first-person view while its CPU drives it. When one gladiator falls, a result view names the winner for five seconds and the game returns to the laurel title.
Each class fights with its own arms, and its class decides how its CPU fights:
- Murmillo (gladius and big scutum) is slow, but the scutum takes a quarter off a blow from in front.
- Thraex (sica and parmula) swings fastest, and the curved sica does half again as much damage into the victim's side.
- Retiarius (trident and net) reaches farthest, and once a round casts its net, which tangles the opponent so it can neither walk nor strike for a second and a half. It fights as an ambusher: it hides behind pillars and in smoke, and throws from cover.
- Secutor (gladius and helmet) takes a tenth off every blow and slips a net in half the time, but its helmet narrows its view, so it is surprised more often.
Every gladiator has stamina: running spends it, walking restores it, and an exhausted gladiator can only walk. Every bout stands five pillars at random places on the sand, an oval in the 87 by 55 proportions of the Colosseum's arena. Two javelins stand in the sand, one at a random place in each half: a gladiator picks one up by walking over it and throws it at an opponent in range, and a missed javelin lies where it fell. Twenty seconds into a fight, and every forty after, a lion leaves its gate in the north wall and hunts the nearest gladiator for twenty seconds: it runs faster than a gladiator walks but slower than one runs, and a gladiator it kills hands the bout to the other.
MULTIPLAYER keeps the original three-board game. Two gladiators fight among the pillars while a third board keeps the arena. Put three boards with the shield on the same Wi-Fi network; every board runs the same program and picks its role:
- ARENA (meant for the UNO Q, one per network) keeps the battle and shows the coliseum from above: the sand, the five red pillars, the wall and the tiers of seats, each gladiator with its field of view, and a dotted line while one sees the other. It sends each gladiator the map once and judges every strike, net, javelin and lion bite.
- PLAYER 1 fights as an aggressive Secutor: it runs at its opponent and strikes as soon as it is in reach, chases to where it saw it last, and otherwise patrols round the pillars.
- PLAYER 2 fights as an ambushing Retiarius: it creeps behind a pillar or into smoke, out of sight, and lurks there. It charges only an opponent that comes close.
RETURN goes back to the game menu.
The battle starts when the arena has two gladiators: the first enters by the left gate, the second by the right. Each gladiator's board shows the arena in first-person wireframe, drawn like DOOM. A gladiator sees its opponent only with its own eyes: the arena reports the opponent's position only while it is in front of the gladiator and no pillar is in the way. A blow the victim did not see coming does double damage, which is what the ambusher plays for. Each gladiator has 100 HP and a blow does about 10, changed by both classes' arms; the first to fall loses the round, and the next round starts from the gates. Tap CPU/HUMAN under the view to take over a gladiator: the left and right thirds of the view turn, the upper middle walks and the lower middle swings the gladius.
Build it with a 10 KB arena (the default is 8 KB) for each board. Solo is enabled only in the UNO Q build; the R4 menu marks it as unavailable:
# Arduino UNO Q, for the ARENA
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.gladiator.Gladiator \
-Djuno.board=arduino-uno-q -Djuno.Xmx=10k
# Arduino UNO R4 WiFi, for a gladiator
./mvnw -f juno-examples/pom.xml compile juno:upload \
-Djuno.main=io.github.jabrena.juno.games.gladiator.Gladiator \
-Djuno.board=arduino-uno-r4-wifi -Djuno.Xmx=10k
The boards talk over UDP port 4211. An UNO R4 WiFi joins the network named in application.properties on the shield's microSD card; without the file it says so and goes back to the menu. The UNO Q uses the connection configured on its Linux side.
wifi.ssid=MyNetwork
wifi.password=MyPassword
With juno:monitor at 115200 baud, the arena logs every join, hit, lion bite and round, and each gladiator logs the blows it takes.
The emulator runs one board at a time with no network, so the picture above comes from GladiatorRecordingTest: it plays a match through the arena's and both gladiators' own code in one program, replacing the network with direct calls. Record it again with ./mvnw -pl juno-examples test -Dtest=GladiatorRecordingTest -Djuno.recording.gladiator=true.
Testing without the board
./mvnw test plays every game on an emulated shield, on any machine and in CI. The tests in
juno-examples/src/test/java replace the handful of native hardware classes with plain Java test
doubles, which come first on the test classpath (the juno:compile/juno:upload goals still use
the real ones):
Gpiodecodes the ILI9341 bus that the realTftTouchShielddriver bit-bangs into a framebuffer, and answers the touch panel's analog reads, so tests can tap the screen.ClockandDelayrun on simulated time: waiting is instant and each clock reading costs a millisecond, so frame loops and time-budgeted searches behave the same on every run.Randomis a seededjava.util.Random, andSerialdiscards its output.Wifiis always connected, andUdpis a network with no other boards: sends go nowhere and nothing arrives.
On top of these:
GameScreenshotTestplays each game for a few simulated seconds with scripted taps and compares the screen with its picture injuno-examples/src/test/resources/screenshots/games. A mismatch writes the actual screen and a diff tojuno-examples/target/screenshots. After an intended visual change, regenerate the pictures with./mvnw -pl juno-examples test -Dtest=GameScreenshotTest -Djuno.updateScreenshots=true.CardGamesTest,PacManTest,MissileCommandTest,TempestTest,StarWarsTest,EmpireStrikesBackTest,RedBaronTest,StarTrekTest,SpaceParanoidsTest,DoomTest,LunarLanderTestandGladiatorTestcheck rules and computer players: the poker hand ranking against a brute-force reference, no chip lost across all-ins and side pots, and that autopilots clear Missile Command waves, Tempest levels, Space Paranoids and Star Trek sectors, whole Red Baron and Empire Strikes Back waves, nearly every dot of a Pac-Man maze, and a Gladiator duel fought to the end through the arena's own rules and packets.
This runs the games' Java on the JVM, not the code Juno generates for the board.
Compiling with the real Arduino toolchain in Docker
juno:verify compiles a game with the real toolchain, and you can run it on any game. The compiler's own
suite, ArduinoCliCompileIT in the juno-compiler module, does the same for small programs that each exercise one
API or shield (GPIO, Serial, Servo, I2C, TFT touch, LCD keypad, ...) once per board they declare —
Juno generates the sketch, then arduino-cli compile builds and links it with the real UNO R4 or UNO Q
core — inside a Docker container started with Testcontainers,
so you need Docker but no local Arduino installation. It is opt-in:
./mvnw -pl juno-compiler -am -Parduino-cli verify
The first run builds the image from
juno-compiler/src/test/docker/arduino-cli/Dockerfile
(arduino-cli plus the arduino:renesas_uno and arduino:zephyr cores, several hundred MB) and Docker caches it for
later runs. To use an image you built or pulled yourself, add
-Djuno.arduinoCliImage=<image>. Without Docker the test is skipped. If your network blocks
Docker Hub, also set TESTCONTAINERS_RYUK_DISABLED=true, since Testcontainers otherwise pulls its
cleanup container from there.
What neither test covers is the program running on the chip itself: timing, touch feel and the
panel's real colors still need the board and juno:upload.