sneak af3050b187 fix: bound wizard-created Which against its item table (closes #10)
createObj stored the raw 0-f nibble as Object.Which with no bounds
check, so wizard mode -> C -> / -> f made a wand numbered 15 against a
14-entry table and panicked in fixStick. Input outside 0-f overshoots
much further rather than going negative: readchar returns a byte, so
the int(ch-'a') + 10 branch is byte arithmetic and wraps, giving 234
for 'A' and 202 for '!', and panicked the same way. C's create_obj()
was equally unchecked, but its consumers were either switches (defined
for any value) or static-array reads past the end (undefined, and
survivable in practice). Since one game is now one process, the Go
panic kills the game outright and leaves the terminal in raw mode.

Reject at the two boundaries a bad Which can enter through. createObj
now refuses an out-of-range choice with a message drawn from C's own
type_name() vocabulary and adds nothing to the pack, a deliberate
divergence recorded in a comment because C had no defined behavior here
to be faithful to. Restore refuses a snapshot describing such an object
(ErrSaveCorrupt) rather than loading a game that would explode later. A
decoded snapshot is also the only source of a genuinely negative Which,
Which being a plain int off the wire, so it is what the Which >= 0 arm
of hasValidWhich defends against.

Behind those, whichLimit/hasValidWhich back defensive guards at every
dispatch the issue names: the quaffHandler/readHandler/zapHandler
accessors return no handler instead of indexing (for wands that is
exactly what non-MASTER C did, matching no case and still running
o_charges--), the callIt lore lookups, identifyType, armorClass for the
a_class[] reads, initWeapon against the missing init_dam[] row for
WeaponFlame, fixStick's ws_type[] read, and inventoryName and
objectWorth, hoisted so one check each covers the whole family of
per-kind name and appraisal tables. identifyType's bound is defensive
rather than live: readHandlers registers readIdentify only for the
identify scrolls, all of which sit inside the shorter idType table.

No in-range input changes behavior and no guard consumes a random
number: the rejection precedes every rnd() call. TestSeedCompatItemTables
stays green untouched.

New game/wizard_test.go covers the exact reproducer, a rejection sweep
over every indexed kind including the wrapped values from input outside
0-f, an acceptance sweep proving valid choices still build the right
item, one no-panic test per guarded family, the fixStick crash site, the
corrupt-save rejection over both the wrapped values and a negative
Which, and a check that whichLimit still agrees with the table sizes.
Each guard was confirmed load-bearing by reverting it and watching the
test fail.

TODO.md records the step; Next Step is deliberately left alone, since
this arrived out of band via an issue.
2026-08-09 05:24:40 +00:00
2016-07-23 03:24:54 +01:00
2016-07-23 03:24:54 +01:00
2016-07-23 03:24:54 +01:00

Rogue: Exploring the Dungeons of Doom (Go port)

License

Rogue is the original dungeon-crawling adventure game that spawned an entire genre. This branch is a faithful Go port of Rogue 5.4.4: explore procedurally generated dungeons, fight monsters, collect treasure, and attempt to retrieve the Amulet of Yendor.

Original authors: Michael Toy, Ken Arnold, and Glenn Wichman (19801983, 1985, 1999).

The port is function-by-function faithful to the classic C sources — same dungeon generation (seed-compatible RNG), same combat math, same item tables, same messages. The C reference implementation lives on the master and modern-rogue branches; ARCHITECTURE.md documents both the original program structure and the design of this port.

Building and running

Requires Go 1.25 or later and a terminal at least 80x24.

go build ./cmd/rogue
./rogue
# Restore a saved game
./rogue ~/rogue.save

# View high scores
./rogue -s

# Test the death screen (demo mode)
./rogue -d

In-game commands

Press ? in game for the full list.

  • arrows or h/j/k/l/y/u/b/n — move (shift to run, ctrl to run until adjacent)
  • . rest, s search for hidden doors and traps
  • i inventory, , pick up, d drop
  • q quaff potion, r read scroll, e eat food
  • w wield weapon, W wear armor, P/R put on / remove ring
  • t throw, z zap a wand, f/F fight
  • >/< take the stairs
  • S save, Q quit

Environment

# Game options, as in the original
export ROGUEOPTS="name=YourName,terse,jump,fruit=mango"

# Wizard (debug) mode, with a reproducible dungeon
ROGUE_WIZARD=1 SEED=12345 ./rogue

The scoreboard is kept in ~/.rogue.scores. Save files are Go gob snapshots and, as in the original, are deleted when restored.

Code layout

game/        the game engine: one Go file per original C file,
             function-by-function (see ARCHITECTURE.md for the mapping)
term/        tcell-backed terminal, replacing curses
cmd/rogue/   the executable

The engine package is fully headless-testable: make test runs scripted command sequences, dungeon-generation golden checks, and an RNG compatibility test against the original C generator.

For development, the Makefile wraps the toolchain: make fmt (gofmt + prettier), make lint (golangci-lint), make test (the suite, under the race detector with coverage and a timeout), and make check (all three). Use the targets rather than invoking go test directly — they carry the flags the project relies on.

License

BSD-style; see LICENSE.TXT.

Copyright (C) 1980-1983, 1985, 1999 Michael Toy, Ken Arnold and Glenn Wichman. All rights reserved.

Description
No description provided
Readme 3.4 MiB
Languages
Go 99.3%
Makefile 0.5%
Shell 0.2%