jcoffey 231e8b325a Merge pull request #2 from jcoffey-dev/repo-urls-jcoffey-dev
Point the repository URLs at their new owner
2026-09-10 09:41:29 -07:00
2026-09-08 22:32:31 -07:00
2026-09-08 22:32:31 -07:00
2026-09-08 22:32:31 -07:00
2026-09-08 22:32:31 -07:00
2026-09-08 22:32:31 -07:00
2026-09-08 22:32:31 -07:00

Hunt the Wumpus

A browser recreation of the 1973 cave game — the one you played on a Teletype, by smell, with five arrows you could not aim straight.

It ships in two skins over one game. 1973 is the machine as it was: a printing terminal, black ink on fanfold paper, the print head hammering out one character at a time and nothing on screen you have not read. Remaster is the same cave with the lamp on — a cel-shaded map of the dodecahedron, warnings drawn as marks over the rooms they came from. The rules never differ between them, and neither does a single word the machine prints. Switch any time with the button under the frame.

React + TypeScript + Vite, and not one binary asset: every mark on the map is SVG generated from the graph in code, and every note is generated by a small synth on the Web Audio API.

Playing

npm install
npm run dev

The leaderboard is a separate service; run games-scores alongside this if you want one, or do not, and the board will say so rather than break.

You dial in rather than switch on, because in 1973 the game was not on your machine — it was on somebody else's, down a telephone line. Pressing DIAL is also what unlocks the audio: browsers want a gesture before they will make a sound, and that happens to be exactly the gesture the game already wanted.

One to four hunters share the keyboard, hot-seat style. A turn is one whole cave rather than one move — you walk yours until it kills you or you kill it, and only then does the next hunter sit down. Taking turns move by move would mean three people watching a fourth think.

  • The cave is always a dodecahedron: twenty rooms, three tunnels from each. That never changes. What changes is where things are.
  • Two bottomless pits. Walk into one and you keep going.
  • Two roosts of super bats. They will not hurt you. They will pick you up and put you down somewhere at random, and they do not care what is already there.
  • One wumpus, asleep. Nothing down there troubles it — it is too heavy to fall down a pit and too heavy for the bats to lift.
  • Standing one tunnel from any of them, you are told something is close. You are never told which room. Three tunnels and one warning is the whole puzzle.
  • Five crooked arrows. Each can be aimed through as many as five rooms in a row. Name a room the arrow cannot reach from where it is and it does not stop and does not fail — it takes a tunnel of its own choosing, and one of those leads back to you.
  • A shot that misses usually wakes the wumpus, which then moves one room or stays where it is. If it arrives where you are, that is the end of it.

Kill it and you go deeper: a fresh cave, the same twenty rooms, and whatever arrows you did not spend. Die and you are finished. CLIMB OUT closes the books whenever you like and shows the standings.

High scores

One board, shared by everybody. Ending an expedition posts every hunter in the party, and the top 50 comes back ranked best first: wumpodes bagged, then arrows still in the quiver, then fewest rooms walked. Runs from the expedition you just finished are picked out.

There is no way to clear it. A hunter leaves the list only by being pushed off the bottom by a better one.

The board is not in this repository and is not ours alone: every game on the site shares one service, games-scores, which is one container and one volume however many games there are. All this repository holds is the client in src/game/highscores.ts, which names the game on every call and is otherwise exactly what it would be if the board were ours — rows come back in this game's own field names.

It is the one part of the game that needs a line out. With the service unreachable the game plays exactly as normal and the board says so plainly rather than breaking; posting happens behind the closing standings, so a slow network never holds up the end of a run. That repository's README has the API, and what a board with no accounts can and cannot promise.

How it is put together

src/
  game/          pure simulation - no React, no DOM
    cave.ts        the dodecahedron: adjacency, and where to draw it
    constants.ts   hazard counts, arrows, probabilities, every line of text
    engine.ts      one turn: move, shoot, hazards, the wumpus waking
    reducer.ts     the turn/phase machine
    rng.ts         seeded mulberry32, so an expedition can be replayed
  audio/
    synth.ts       pulse voices, a drum kit, filters, look-ahead sequencer
    tunes.ts       the chiptune
    dread.ts       the remaster's band
  skin.ts        which of the two skins is on, and where it is kept
  components/
    Teletype       the transcript, printed a character at a time
    CaveMap        the remaster's map, generated from the graph
    HuntScreen     picks between them, so no screen needs to care

engine.ts takes a cave and a command and hands back the cave that follows, plus the lines the machine would have printed. Both skins render those lines; neither one decides anything. That is what makes it possible to run five hundred hunts in a test and check that a hunter walking blind dies almost every time.

The cave, and proving it is one

The adjacency table is written out by hand rather than generated, because a generated dodecahedron comes out with an arbitrary numbering and this one is the numbering every listing has used since 1973. Written out means it can be typed wrong, and a wrong table would not crash — it would quietly stop being a dodecahedron while the game still played. So cave.test.ts proves it: twenty rooms, three tunnels each, symmetric, connected, diameter exactly five, and every room on three pentagons.

The map, and what it is not allowed to know

The remaster must not be an easier game than the transcript. CaveMap may only draw what has already been printed: it reads the rooms the hunter has stood in and the warnings those rooms gave, and it marks the suspects — never the answer. The reveal on the report screen is the one exception, and the hunt is over by then.

That guarantee is asserted in engine.test.ts rather than in the component, because it is a property of the engine and would still have to hold if the map were thrown away tomorrow.

Drawing a solid on paper

A dodecahedron cannot be drawn flat without lying about something, so the map is its Schlegel diagram: one face pushed out to become the rim, the other eleven nested inside. Rings of 5, 10 and 5 — and the middle one is the part that is easy to get wrong, because rooms 6 to 15 are not two pentagons at different depths but a single ten-room ring alternating between the rooms wired inward and the rooms wired outward. Getting that wrong still draws, still scales, still looks broadly cave-shaped, and is much harder to read. So the test checks the property that actually matters: no tunnel crosses another.

Sound

Audio starts on the first gesture, as browsers require.

The 1973 skin has one instrument that is not a lie: the print head. An ASR-33 printed ten characters a second and you could hear every one of them, so the transcript is typed rather than shown, and every character is a solenoid thump with a little bandpassed clatter on it.

The dial-in is a real Bell 103 call, in order and at the real frequencies: dial tone at 350 + 440 Hz, Touch-Tone dialling off the DTMF grid, ringback at 440 + 480, the far modem putting up its 2225 Hz mark tone, and then both ends talking at once — the originating coupler keying 1070 and 1270 Hz while the answering machine keys 2025 and 2225, which is full duplex and is where most of the texture comes from. All of it through a 3003400 Hz telephone band with a resonance around 900 Hz for the two rubber cups.

Worth being clear that this is not the sound most people mean by "modem". That one is a V.32 or V.34 handshake from the 1990s — the ascending probe tones and the long grinding echo-cancel training — and none of it existed in 1973 or could have. A Model 33 at 110 baud through an acoustic coupler is Bell 103, and Bell 103 is a much quieter animal. The boot prints each stage as it happens, off the same schedule the audio is built from, and can be skipped.

The music is the one thing the period skin claims that the period did not have. The real game was silent. What plays instead is the music it would have got when it followed everybody home: two pulse voices, a triangle bass, filtered noise for a kit, and a minor key, which is roughly what a 1981 home-computer port of a cave game sounded like.

The remaster gets a different band, and deliberately not the lemonade stand's funk. Same engine, other end of the scale: D phrygian, no shuffle, a sub bass that sits on the root and refuses to move, a resonant filter sweep on a detuned saw, and toms instead of a backbeat — a snare on two and four would make it music to walk to, and nothing down there walks in time.

Both run off one scheduler that queues notes 200 ms ahead, so the groove does not stutter when React re-renders. MUSIC and SOUND toggle independently.

Provenance

The rules are Yob's and are free to use. The sentences he wrote to explain them are not, and none of them are here — every line this game prints was written for it. NOTICE.md sets out what is ours, what is not, and why.

S
Description
Imported from github.com during the 2026-09-20 standup (local dir: wumpus)
Readme AGPL-3.0
145 KiB
Languages
TypeScript 85.3%
CSS 10.3%
HTML 3.9%
Dockerfile 0.5%