In Nushell
This implementation is a long-running experiment in seeing how much sophistication comes out of a few hundred lines of shell script.
The stack.
- http-nu -- a Nushell-scriptable HTTP server. Routes are closures; the request and response live in the pipeline.
- cross.stream (xs) -- an append-only event store. Every move, every snapshot, every player session is a frame.
- Datastar -- server-sent events drive the UI. The server pushes HTML fragments; the client morphs them in place via morphdom. View-transitions animate tile slides at the browser level.
The actor. A snapshot-actor watches the stream. When a
game.<id>.move frame lands, it resumes the game state from prior
frames, applies the move, and appends a game.<id>.snapshot with the
new state in the frame's meta. The actor is the single writer for
snapshots; every reader pulls them by topic.
The pipeline. The /play page subscribes to game.<id>.* via SSE.
The server tails the topic, threshold-gates pulses, renders state to
HTML, wraps each render in a Datastar patch event, and writes the SSE
stream. Nu pipelines all the way down -- .cat --follow | pulse-keepalive | frames-to-states | threshold-gate-states | states-to-html | html-to-patches | to sse.
The insight. Spectating a live game uses the same pipeline as playing one. There's no separate "watch mode" -- /sse/<id> just tails the same stream the player's session is tailing. Tee the pipeline, get a spectator for free.
Why this is interesting. Event-sourcing usually arrives wrapped in Kafka and ceremony. Here the entire system is a few hundred lines of Nushell, a single binary for the server, and a streaming append-log for the database. The whole game is a stream of frames; the UI is a stream of patches; the player is a stream of intents.
The source: examples/2048 on GitHub. Browse the /design component viewer for the live mirror of every server-rendered piece.
Next: back to the rules, and round again.
