99 lines
4.8 KiB
Markdown
99 lines
4.8 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
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.ts` the spell book and monster stats
|
|
- `src/lib/game/gestures.ts` sequence matching, threat detection, gesture-sharing conflicts
|
|
- `src/lib/game/state.ts` the duel's state and the seeded random source
|
|
- `src/lib/game/resolve.ts` simultaneous turn resolution
|
|
- `src/lib/game/bot.ts` the opponent
|
|
- `src/lib/game/duel.svelte.ts` reactive store binding the engine to the UI, for a local duel or a seat at a room
|
|
- `src/lib/game/client.ts` the browser's side of the duel server, and the seats it holds
|
|
- `src/lib/game/view.ts` what one seat may know
|
|
- `server/` the duel server: rooms, ledgers, the HTTP and WebSocket front door
|
|
- `src/lib/components/` the ledger, hand pickers, status panels, result strip, chronicle, spell sheet, and the sticky bar for phones
|
|
- `src/routes/rules/` the rules page, rendered from the spell book and the transcription in `docs/`
|
|
|
|
## 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
|
|
|
|
```sh
|
|
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."
|