Wiki · main

Snake Royale

Last edited by · ·

Snake Royale

Four snakes on a small rectangular grid. Every turn all four move one cell at the same time — nobody sees anybody else's move before it lands. Run into a wall, into anybody's body (your own included), or out of health, and you are gone. Run your head into somebody else's head and, on the default board, the longer snake lives and the shorter one does not. Food makes you longer and refills your health; length is the only thing that wins a head-on; and the board only ever gets smaller. The last snake alive takes the round. It takes about ninety seconds to watch and one sentence to explain.

The strategy this game is buying is "who blocks whom": with four snakes on 153 cells, cutting a rival's escape route is far cheaper than out-eating it, and cutting one rival's route usually helps a third snake more than it helps you. There is a public one-line say channel, so a snake can announce which lane it is taking — and everyone, including the snake it is about to seal in, reads it.

A policy is just a prompt. Both champions are PLAYER_PROMPT policies; the two fillers are scripted baselines; all four run the same image, switched by environment.

Rule modules

One engine, three presets — royale (17×9, walls, food, health, longer head wins), geese (the Kaggle 11×7 torus with the hunger shrink and both-die head-ons) and tron (21×9, no food, the trail never clears). See docs/MODULES.md.

Repo layout

PathWhat
src/snake/the sim: board.nim, rules.nim (the resolver), space.nim (one bounded BFS), upstream.nim (the transcribed upstream facts), plus the server, the commander layer and the replay
src/snake_royale.nimthe game server, /bin/snake-royale
src/snake_royale_player.nimthe thin seat registrar, /bin/snake-royale-player
client/the broadcast chrome: chrome_common.js (byte-identical to the starter's), broadcast_core.js (the grid renderer) and replay_broadcast.html (the starter's page plus the appended SNAKE-ROYALE block)
replay-viewer/the wasm entry, the emscripten link flags and the static shell
data/the board art: nano-banana renders of the Softmax cog, one kit per colourway
scripts/art/the source sheets and the split script that made data/
tools/ci/the CI harness: the docker smoke, the viewer smoke, the renderer fixture, the policy set
docs/the rules, the modules, the wire protocol, and the accepted design note

Playing a seat

coworld upload-policy coworld-snake-royale:latest \
  --name my-snake --run /bin/snake-royale-player \
  --secret-env PLAYER_PROMPT="take the direction with the most free space, \
and only eat when your health is under 12"

PLAYER_SCRIPTED=coil or PLAYER_SCRIPTED=forager seats one of the two shipped baselines instead. A seat that sets neither is coil.

Building

The game is Nim. ci.yml is the harness: it runs every tests/*.nim in debug and release, builds the production image and plays one real episode through raw docker (tools/ci/docker_smoke.sh), then builds the static replay-viewer bundle and opens it in headless chromium against the replay that episode produced.

nim c -r tests/tests.nim                      # the whole suite
docker build -t coworld-snake-royale:ci .     # the production image
./tools/ci/docker_smoke.sh coworld-snake-royale:ci
./tools/build_replay_viewer.sh "$PWD/dist/static-replay-viewer"

Watching

Replays are a static file plus a browser wasm viewer, never a pod: the manifest declares "replay_viewer": {"bundle": "static-replay-viewer"}, tools/build_replay_viewer.sh compiles the same sim module to wasm, and the viewer re-derives every frame from the recorded direction bytes in the browser. Everything the viewer needs — names, config, per-turn direction bytes, the seed — lives in the replay bytes; no server is contacted except S3 for the file.

python3 tools/replay_summary.py <file> prints one strict-UTF-8 JSON object describing any replay, using only the Python 3 standard library.