A small retro vector-arcade game built on suit —
the Scala Native + SDL3 + Cairo UI toolkit. The whole game is one suit canvas widget:
a useFrame callback steps an immutable GameState each frame and the painter draws it
as white line-work on a black field, the classic arcade look.
brew install --cask edadma/tap/asteroids
This installs Asteroids.app (in Launchpad / Applications) and a CLI asteroids command, pulling
the runtime libraries (sdl3, cairo, freetype). Launch it from Launchpad/Spotlight, or run
asteroids from a Terminal in your desktop session. The binary is unsigned, so the first launch
needs a right-click → Open to get past Gatekeeper. (A GUI app only opens a window from the
logged-in desktop session — running it over SSH or from a non-console shell won't show a window.)
sbt run
This compiles, links a native binary, and launches the window. To produce the standalone executable without running it:
sbt nativeLink
# -> target/scala-3.8.4/asteroids
Requires the Scala Native toolchain (Clang) plus SDL3, Cairo and FreeType, which suit
links against transitively.
| Key | Action |
|---|---|
| ← / → or A / D | rotate |
| ↑ or W | thrust |
| Space | fire |
| Enter | start / play again (title and game-over screens) |
| P | pause / resume |
| R | restart (fresh game) |
| Esc | stop — return to the title screen |
- Shoot rocks to score: large 20, medium 50, small 100. Large rocks split into two mediums, mediums into two smalls, smalls vanish.
- Clear every rock to advance a level — each level spawns more, faster rocks.
- You have 3 lives. A collision costs a life and respawns you at centre with a brief blinking invulnerability. Lose them all and it's game over.
- A flying saucer crosses the screen now and then and shoots at you: a big one (200) with sloppy aim, a small one (1000) that tracks you tightly and sharpens as your score climbs. Its shots break rocks too, and ramming it costs a life.
- The screen wraps: fly off one edge, reappear on the opposite one.
Every effect is synthesised at runtime — no audio files — and played through sdl3's core audio. The shot, the size-scaled explosions, the ship's thruster rumble, the saucer's continuous warble, and the level-clear chime are all generated as PCM on the fly. The warble follows the original cabinet's analog circuit (a continuously swept oscillator) rather than switching between fixed tones.
Vec2.scala— toolkit-free 2D vector math; the simulation is pure arithmetic over it.Model.scala—GameStateand the purestepfunction (ship, bullets, rocks, the saucer and its shots, collisions, wave/level progression). No toolkit types.Sound.scala— procedural audio:Synthgenerates each effect's PCM;Soundplays them through an sdl3 voice pool, with dedicated topped-up streams for the continuous thruster and saucer sounds.Main.scala— the suit app (Game.App), input handling, andRender, which draws the whole frame onto suit'sCanvas(shapes viastrokePath).