tavyn-run
v0.1.0
Published
A wobbly-legged stickman endless runner for your terminal. Written in Rust, compiled to WebAssembly.
Maintainers
Readme
Tavyn Run
An endless runner for your terminal starring a stickman whose legs are far too thin. It's like the offline dino game, except the hero wobbles, stumbles, talks to himself, and keeps getting bonked by pigeons.
The game is written in Rust and compiled to WebAssembly. A small Node script runs it in your terminal, so one npm package works on Windows, macOS and Linux.
npx tavyn-run TAVYN RUN SCORE 00024 HI 00000 SPEED 24
.-~~~-.
.-~~~-.
\o/
|
/ \ \_)
===============================================================
. ` ,. , ' . ` ,. , ' . ` ,. , ' . ` ,. , ' . `How to play
| Key | Action |
|---|---|
| Space / ↑ / W | Jump. Press again in mid-air for a weaker, arm-flailing double jump |
| ↓ / S | Duck. In mid-air it slams you down, and you're still ducking when you land |
| P | Pause |
| Q / Esc / Ctrl-C | Quit |
Terminals don't report key releases, so a duck lasts about half a second per press.
Obstacles
- Banana peels, trash cans, wet floor signs and traffic cones: jump over them.
- Open manholes: jump across the gap.
- Pigeons at head height: duck under them.
- Low pigeons: jump over them.
Chaos events. Every few hundred points one of these lasts 8 seconds:
- HICCUPS: you hop on your own
- OPPOSITE DAY: jump and duck are swapped
- BLACKOUT: you can only see near yourself
- PIGEON RAID: lots of birds
- WINDY: the world speeds up and slows down
Pickups
[E]energy drink: steady legs and 4 s of invincibility.{~}noodle legs: 5 s of floaty moon jumps.
Near-miss bonus: clearing an obstacle by a hair gives +25 PHEW!
Speed keeps rising up to a cap. Your best score is saved in ~/.tavyn-run.json. Set NO_COLOR=1 to turn colors off.
World leaderboard
The title screen shows the world top 5. If you make the top 10, the game-over screen asks for a name: type one and press Enter, or press Esc to skip.
What gets sent, and only when you press Enter: the name you typed, plus a replay of your run (the random seed and when you pressed jump or duck). The server re-plays the run to compute the score itself, so made-up scores don't work. Nothing else is sent; the only other request is fetching the board.
TAVYN_OFFLINE=1 turns all network access off, and TAVYN_SERVER=<url> uses a different server. If the server can't be reached, the game simply plays offline. The server lives in server/.
Development
You need Rust (with the wasm32-unknown-unknown target), wasm-pack, and Node 18 or newer.
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
cargo run --release # play the native build (fastest edit-play loop; offline only)
cargo test --workspace # all tests, including a bot that checks every seed is survivable
wasm-pack test --node -- --test determinism # same run must give identical results in WASM
npm run build # compile to WebAssembly into pkg/
npm start # play the WASM build, which is exactly what npm users get
cargo run -p tavyn-server # local leaderboard server
TAVYN_SERVER=http://127.0.0.1:8787 npm start # play against itIf you change gameplay (physics, spawning, scoring), bump RULES_VERSION in src/game.rs. Replays recorded under old rules won't verify anymore, and tests/determinism.rs fails as a reminder.
Layout
src/
game.rs game state machine, spawning, scoring, collisions, chaos events
player.rs stickman physics (jump, flail-jump, duck, slam)
obstacles.rs obstacle kinds and hitboxes
events.rs chaos events and power-ups
render.rs character grid -> ANSI frame
sprites.rs all the ASCII art
text.rs death messages, mumbling, name cleaning
rng.rs tiny seeded RNG (keeps the engine deterministic and WASM-friendly)
math.rs platform-independent sine (gameplay math must match in WASM and native)
replay.rs re-simulate a recorded run (used by the server)
bot.rs autopilot used by tests
wasm.rs wasm-bindgen API used by js/cli.js
main.rs native terminal runner (crossterm)
js/cli.js npm entry point: keyboard, frame loop, settings file, leaderboard requests
server/ leaderboard server (axum + SQLite) and deploy files
tests/ cross-platform determinism testThe engine is pure logic: hosts call press(key), tick(dt) and frame(cols, rows) and print the string it returns. The same code runs natively and in WASM, and every run is fully determined by its seed and inputs.
Publishing to npm
First set DEFAULT_SERVER in js/cli.js to your leaderboard's HTTPS URL. Then:
npm login
npm publish # `prepack` runs `npm run build`, so pkg/ is always freshUse npm pack first to check exactly what gets shipped. It should be js/cli.js, pkg/tavyn_run.js and pkg/tavyn_run_bg.wasm, about 36 kB in total. Bump the version in both package.json and Cargo.toml before each release.
