Robot Wars โ€“ Building a Programming Game With My 8-Year-Old

๐Ÿค– Robot Wars โ€“ Building a Programming Game With My 8-Year-Old

A few weeks ago my son Wessel (8) came home from school and told me they'd been learning to program with CT-3000. If you don't know it: CT-3000 is a brilliant little tool by Q42 where kids type lines of very simple Dutch โ€” robot = vooruit, lamp = aan โ€” and things happen immediately. No Run button, no syntax errors in your face, no Enter key. As soon as the line is a valid command, it executes.

Wessel loved it. He also loves Clash Royale. So the obvious question at the dinner table was:

"Papa, can we make a game where you program a robot to attack the other player's tower?"

Yes. Yes we can.


๐ŸŽฎ What is Robot Wars?

Robot Wars is an online game for kids of about 8โ€“12. Two players each control a robot on a 13ร—7 grid. Between them is a river with two bridges (hello, Clash Royale). Each player has a tower with 5 lives. You steer your robot by typing commands, CT-3000 style, and try to shoot the other tower down to 0. The fastest win goes to the top of the scoreboard.

You can play against another human online, or against Robo, the computer player.

The field looks like this:

  7  .  .  .  .  .  .  ~  .  .  .  .  .  .
  6  .  .  .  .  .  .  =  .  .  .  .  .  .   bridge
  5  .  .  .  .  .  .  ~  .  .  .  .  .  .
  4  B  R  .  .  .  .  ~  .  .  .  .  R  B   towers + start squares
  3  .  .  .  .  .  .  ~  .  .  .  .  .  .
  2  .  .  .  .  .  .  =  .  .  .  .  .  .   bridge
  1  .  .  .  .  .  .  ~  .  .  .  .  .  .
     1  2  3  4  5  6  7  8  9 10 11 12 13

Coordinates are (x, y) like a maths axis system: x along the bottom, y along the left, counting from the bottom up. Player 1 (blue) is on the left, player 2 (red) on the right. Player 2 sees the field mirrored, so "forward" is always to the right on your own screen and x = 1 is always your own tower.

Update 2026-09-21: the y-axis originally counted from the top; this and the bombs below were added the day after. See Robot Wars, One Day Later.


โŒจ๏ธ The language

One command per line, in plain Dutch. Case and spacing don't matter.

robot = vooruit          (forward)
robot = achteruit        (backward)
robot = omhoog           (up)
robot = omlaag           (down)
robot = schiet           (shoot, 4 squares forward)
schild = (4, 2)          (place a shield at x = 4, y = 2)
bom = (1, 0)             (drop a bomb at dx, dy from your robot; -1, 0 or 1)
herhaal 3 keer           (repeat 3 times)
  robot = vooruit
  robot = schiet
klaar                    (done)

That's the whole language. Move, shoot, shield, bomb, and a repeat loop that can be nested.

The rules Wessel and I agreed on:

  • Shooting fires 4 squares forward. The bullet flies over water and bridges and hits the first thing in its way: a shield, a robot or a tower. One hit = one damage.
  • Towers have 5 lives. At 0, the game is over.
  • Robots have 5 hearts. At 0 the robot disappears and respawns on its start square after 3 ticks.
  • Shields (3 per game) can only be placed on your own half and disappear after 8 hits.
  • Bombs (3 per game) go off immediately if a robot is on the square, otherwise they stay behind as a mine that destroys whoever steps on it.
  • Every tick (1 second) the robot performs one command from its queue.

โœจ The CT-3000 magic: no Enter key

The part Wessel cared about most was that it had to feel like CT-3000. So there is no Start button and you never press Enter.

You type on the bottom line. Every keystroke goes to the server. As soon as the line is a valid command, it freezes (grey, with a โœ“ in front), goes into the robot's queue, and a fresh empty line appears with the cursor already in it.

The marker at the start of the line tells you where you are:

Marker Meaning
(nothing) Still typing, could still become a command (robot = vo)
red ! + hint This can never become a command (robot = links)
yellow โ€ฆ Inside a herhaal block, waiting for klaar
green โœ“ Executed

The hints are in kid-friendly Dutch:

Ik ken "links" niet. Probeer vooruit, achteruit, omhoog, omlaag of schiet.

This turned out to be the single most important feature. An 8-year-old does not read error messages, but a red ! with a one-line suggestion right next to what they typed? That works.


๐Ÿง  Robo, the computer player

Playing against a stranger online isn't always an option, so we built Robo. Robo has no unfair advantage: same rules, same queue, same respawn, same 3 shields.

Two design decisions I'm happy with:

Robo only moves when you move. Every time the human's robot actually executes a step, Robo gets one step of "credit". If you stop typing, Robo stops too. If you press Stop, Robo stops. This means a kid who is thinking never gets steamrolled.

Robo decides per tick, not in advance. Each tick he looks at the current field and picks one step, first rule that matches:

  1. Enemy robot in front of me, same row, within 4 squares, no shield in between? โ†’ schiet
  2. Enemy tower within 4 squares, no shield in between? โ†’ schiet
  3. Enemy robot on my half and I still have shields? โ†’ put a shield in front of my tower
  4. Otherwise: walk to the nearest bridge, cross it, get back to row 4, advance until the tower is in range. If the next step is blocked, switch to the other bridge.

Because he re-evaluates every second, he reacts to whatever you do. It's about 80 lines of Python, and it's genuinely hard to beat quickly.


๐Ÿ› ๏ธ Tech stack

I wanted something I could explain to Wessel in one sentence per part, and that I could host on my own VPS without ceremony.

  • Python 3.12 + FastAPI + uvicorn โ€” the server
  • HTMX + the HTMX WebSocket extension โ€” the frontend. No custom JavaScript, no Node, no bundler.
  • One WebSocket per game page โ€” the server pushes HTML fragments, HTMX swaps them in out-of-band by id
  • SQLite โ€” only for the scoreboard. Running games live in memory.
  • Inline SVG sprites โ€” the robots ("Bolbot"), the towers ("Ruimtetoren") and the shields are <symbol>s reused via <use>, with the team colour as a CSS variable
  • uv for package management, pytest for tests
  • A reverse proxy in front for HTTPS; WebSockets pass through without any special configuration

The whole thing is about 1,100 lines of Python:

app/parser.py    โ€“ turns a typed line into a command (or Incomplete / Invalid + hint)
app/game.py      โ€“ the rules: field, moving, shooting, shields, bombs, winning
app/ai.py        โ€“ Robo
app/editor.py    โ€“ the CT-3000 style editor: freezing lines, repeat blocks, hints
app/lobby.py     โ€“ who plays against whom
app/main.py      โ€“ FastAPI + HTMX over a WebSocket, the 1-second tick loop

The tick loop is one asyncio task: every second it calls game.tick() for every running game and broadcasts the updated #veld, #status and #editor fragments to all open sockets.

Running it locally:

uv sync
uv run uvicorn app.main:app --reload

Open http://localhost:8000 in two different browsers (or one normal + one incognito window) and you have a two-player game.


๐Ÿง’ What Wessel did

This was a real collaboration, not "dad codes while kid watches":

  • He chose the theme, the field layout and the rules (the bridges were non-negotiable).
  • He picked the robot design (a floating ball robot with one big eye) and the tower design (a space tower with a glowing core) from a set of mockups.
  • He decided the hints had to be nice, not angry.
  • He was the tester. Every rule about "what happens ifโ€ฆ" came from him trying to break it.
  • He named the computer player.

And he can beat me. Consistently.

The best part, though, isn't the game. It's that Wessel is learning to type โ€” properly, both hands, because a robot that only moves when you spell vooruit correctly is a far better typing teacher than any typing course โ€” and he's picking up the basics of programming without noticing: commands execute in order, a loop repeats things, a wrong command gets a hint instead of a crash, and a queue means the robot does what you told it to do, not what you meant. Everything else is just decoration around that.


๐Ÿšง Known limitations

  • As soon as a line is valid, the server replaces the input with an empty one. Whatever you type in those few milliseconds is lost. On a slow connection it helps to wait for the โœ“.
  • The scoreboard is per server. There are no accounts or passwords โ€” your name is enough.
  • Only wins by humans go on the scoreboard, and only real wins (tower at 0). If your opponent walks away, you win the match but it doesn't count.

๐ŸŽฏ Try it

Go to robot.schut.me, type your name, and pick Speel tegen de computer. Then type:

herhaal 5 keer
  robot = vooruit
klaar
robot = schiet

โ€ฆand see how far you get before Robo gets you. The source is at github.com/martijnschut/robotwars.

If you have a kid who's just discovered CT-3000 at school โ€” this is a fun next step. And if you build something similar, let me know. Wessel is already asking for a level editor.