The Mills · Games

invaders

Invaders on the scribbler — mills/invaders

Space Invaders in a scribbler window

Summary

invaders opens a 480×640 scribbler window and puts you at the bottom of it with a cannon. Left and Right arrows move, Space fires, Q or Escape quits. Aliens descend at random. A laser that reaches one clears both and scores. An alien that reaches the cannon line ends the game. The buzzer supplies the sound — the shot, the hit, and the end each get their own effect.

The Original

Space Invaders is a trademark of Taito Corporation. This mill is an independent educational homage, not affiliated with or endorsed by the trademark owner. See THIRD-PARTY-NOTICES.md.

Space Invaders — Tomohiro Nishikado, Taito, 1978 — is the game that made the descending-alien loop a genre. Its accelerating heartbeat taught a generation what game feel was: the aliens speed up as their ranks thin. That was originally a hardware accident — fewer sprites (moving screen shapes) meant faster frames. This mill is not a cabinet-accurate port. It's the arcade idea reduced to its essentials — descend, shoot, survive — and rebuilt as a demonstration of how a purely functional game loop works when a window, a frame clock, and a sound queue are involved.

Running It

bin/mill run mills/invaders/invaders.shoddy

or, from mills/invaders/, use the wrapper — ./build.sh, or ./build.ps1 on Windows:

./build.sh          # run the game (also: ./build.sh run)
./build.sh test     # run the headless simulation smoke check

One thing to know: invaders is a scribbler program, so it must run under mill run. A woven dotnet FILE.dll has no window backend — the platform code that actually opens a window. The exception is a host that brings its own backend. The game also ships on the Shoddy Reckoner's games shelf, where the app presents the scribbler as a page and feeds it events — the same woven program, unmodified.

How It's Built

The pure model is invaders-core.shoddy: the state record, the per-frame simulation, and the input actions. It includes no scribbler, so it runs headless — with no window at all — and is unit-tested that way, by test.shoddy in the folder and src/Shoddy.Tests/InvadersTests.cs in the golden suite.

The window half is invaders.shoddy: drawing, sound, and the event loop. Frames come from ScribblerTick at a fixed FPS (frames per second), so the loop sits at zero CPU between frames, and input arrives on the same queue. Every branch of the loop's Select Case calls Loop directly, or closes the window. That direct call is a genuine self tail call — a call made as the function's very last act — which the compiler turns into a loop. A helper that called Loop back would be mutual recursion — two functions calling each other — and would grow the stack one frame per tick.

The design point worth stealing is how sound stays out of the core. Effects are inferred by comparing the game state before and after a step: a laser appearing is a shot, the score rising is a hit, Ended flipping on is the end. So the pure model needs no sound hooks and still tests headless. The shell reads the diff — the before-and-after difference; the core never knows the buzzer exists.

Why More Than One File

The seam between invaders-core.shoddy and invaders.shoddy is where the window begins. The core is everything the game is — record, simulation, input actions. It could not open a window if it wanted to: it never includes the scribbler. The shell is everything the game presents: drawing, sound, and the event loop, none of which decides anything.

The split earns its keep in the tests: the golden suite steps the simulation frame by frame with no window and no frame clock. The sound design above also enforces it. Because effects are inferred from state diffs rather than fired from inside the rules, nothing ever pulls the buzzer (or any other effect) down into the core. This is the simplest of the three game mills, so it's the one to read first if you want the core/shell pattern at its clearest.

The Machines It Uses

MachineWhy
seqIn the core: Any answers the collision and end-of-game questions — has any laser reached any alien, has any alien reached the cannon line.
mathIn the core: RandInt rolls the alien spawns, Clamp keeps the cannon on screen, and Dist is the circle-vs-circle hit test.
keysIn the core: GameKey folds raw key codes into the friendly GameKeys the input actions consume — so "left arrow" is a value the pure model can match on.
scribblerIn the shell: the window itself — the drawing surface and ScribblerTick, the fixed-FPS queue that delivers both frames and input, letting the loop idle at zero CPU between ticks.
buzzerIn the shell: the shot, the hit, and the game-over sound — triggered by diffing game state, never from inside the rules.