Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Waving Hands
A browser remake of Richard Bartle's 1977 pencil-and-paper wizard duel, also known as Spellbinder and Spellcaster, and the game behind Andrew Plotkin's Spellcast for X Windows.
Each turn both wizards secretly choose a gesture for each hand, then reveal them together.
The right run of gestures on one hand is a spell. Gestures can overlap, so F-F-F casts
Paralysis and following it with S-S-D-D finishes a Fireball. Both palms at once is surrender.
The full rules, as transcribed from the Duel Purpose fanzine, are in
docs/waving-hands-rules.txt.
Playing
npm install
npm run dev
The hall at / offers two ways in: play the bot at /play, or duel a person. A duel between
people has a four-letter room code; /join/CODE is both the invite link and the place it is
played. Turns are taken whenever each player has a moment, and the page refreshes itself when
the other wizard moves. An empty seat can be given to the bot. Each turn, choose a gesture per
hand, pick which completed spell (if any) each hand casts and at whom, and reveal. The rules
are at /rules.
Around the two hands:
- the ledger of every gesture both wizards have made, with completed spells named;
- beneath each hand, the spells it is part-way through, one click from the next gesture, with a pin to keep one in view; every spell-sheet entry can also be planned with either hand;
- warnings citing the opponent's gestures that could finish a hostile spell, which light those gestures up in the ledger when selected;
- after each reveal, a strip of what changed each wizard's health and everything else worth knowing, and a one-time explanation when a rule decided something surprising;
- the full chronicle and the spell sheet in the sidebar.
A duel against the bot is saved to the browser's local storage after every change, so it survives reading the rules, a refresh, or a phone putting the tab to sleep. Starting a new one asks first if one is under way. The seats this browser holds at shared duels are remembered the same way and listed in the hall with whose move it is. Nothing is rendered on the server.
The duel server
server/ is a small Node process, run from source with tsx (npm run server beside npm run dev; Vite proxies /api and /ws to it). A room is an append-only ledger of seats and turns
in data/rooms/CODE.jsonl; the engine is deterministic given the seed and the recorded inputs,
so a restart replays every room. A turn resolves once every seat that must move has moved, bots
included, and each seat is sent a view filtered to what the rules let it see. The engine seats
two to four wizards; the interface is built for two.
Layout
src/lib/game/spells.tsthe spell book and monster statssrc/lib/game/gestures.tssequence matching, threat detection, gesture-sharing conflictssrc/lib/game/state.tsthe duel's state and the seeded random sourcesrc/lib/game/resolve.tssimultaneous turn resolutionsrc/lib/game/bot.tsthe opponentsrc/lib/game/duel.svelte.tsreactive store binding the engine to the UI, for a local duel or a seat at a roomsrc/lib/game/client.tsthe browser's side of the duel server, and the seats it holdssrc/lib/game/view.tswhat one seat may knowserver/the duel server: rooms, ledgers, the HTTP and WebSocket front doorsrc/lib/components/the ledger, hand pickers, status panels, result strip, chronicle, spell sheet, and the sticky bar for phonessrc/routes/rules/the rules page, rendered from the spell book and the transcription indocs/
Simplifications
Raise dead only heals; there is no one left to raise in a two-wizard duel once someone dies. Paralysis and charm person pick the hand at cast time rather than after the reveal. A hastened wizard's second pair of gestures is bound by the same enchantments as the first. Under time stop the frozen wizard's ledger shows a row of question marks, so they know a stopped moment passed even if nothing touched them.
The bot
The opponent scores every pair of gestures by the spells they finish, the spells they move toward, and what your visible gestures threaten. Spells it cast in the last few turns are discounted so it does not fall into a rut, and each named opponent weights the spell book a little differently.
Deploying
See deploy/README.md: one small droplet running Caddy serves the static build.
Tests
npm test
Credits
The gesture silhouettes on the buttons and the favicon are adapted from the bitmaps in Andrew Plotkin's Spellcast for X Windows (1993). Its notice: "This implementation is by Andrew Plotkin. It is copyright 1993 by Andrew Plotkin. The source code may be freely copied, distributed, and modified, as long as this copyright notice is retained. The source code and any derivative works may not be sold for profit without the permission of Andrew Plotkin and Richard Bartle."