Update golangci-lint to v2.12.2 with canonical config #1
@@ -1,5 +1,9 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
|
|
||||||
|
# Config schema uses the golangci-lint v2 layout (settings live under
|
||||||
|
# linters.settings, not top-level linters-settings) so that the
|
||||||
|
# thresholds below are actually applied by golangci-lint >= v2.
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
modules-download-mode: readonly
|
modules-download-mode: readonly
|
||||||
@@ -14,25 +18,17 @@ linters:
|
|||||||
- wsl # Deprecated, replaced by wsl_v5
|
- wsl # Deprecated, replaced by wsl_v5
|
||||||
- wrapcheck # Too verbose for internal packages
|
- wrapcheck # Too verbose for internal packages
|
||||||
- varnamelen # Short names like db, id are idiomatic Go
|
- varnamelen # Short names like db, id are idiomatic Go
|
||||||
# Repo-specific exceptions approved by sneak (2026-07-06)
|
settings:
|
||||||
- paralleltest # Requires t.Parallel() in every test
|
lll:
|
||||||
# Approved by sneak 2026-07-07
|
line-length: 88
|
||||||
- testpackage # Tests use internal package game to reach unexported state
|
funlen:
|
||||||
- exhaustive # C-faithful switches handle only the cases C handled
|
lines: 80
|
||||||
- mnd # C-faithful gameplay literals; naming them hurts C-greppability
|
statements: 50
|
||||||
|
cyclop:
|
||||||
linters-settings:
|
max-complexity: 15
|
||||||
lll:
|
dupl:
|
||||||
line-length: 88
|
threshold: 100
|
||||||
funlen:
|
|
||||||
lines: 80
|
|
||||||
statements: 50
|
|
||||||
cyclop:
|
|
||||||
max-complexity: 15
|
|
||||||
dupl:
|
|
||||||
threshold: 100
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
|||||||
13
TODO.md
13
TODO.md
@@ -34,6 +34,19 @@ wizard commands).
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-08-07 Canonical linter config (`golangci-v2.12.2`): replaced
|
||||||
|
`.golangci.yml` with the shared canonical config (v2 schema; settings now live
|
||||||
|
under `linters.settings`, so the `lll`/`funlen`/`cyclop`/`dupl` thresholds
|
||||||
|
actually apply — the old top-level `linters-settings` block was silently
|
||||||
|
ignored). The four repo-specific disables (`mnd`, `exhaustive`,
|
||||||
|
`paralleltest`, `testpackage`) moved out of the config into targeted in-code
|
||||||
|
`//nolint` directives carrying the original approval dates, so the config
|
||||||
|
stays byte-identical to canonical. Real fixes: `t.Parallel()` in all 32 tests,
|
||||||
|
24 long lines wrapped or their comments tightened, control bytes in
|
||||||
|
`term/tcell.go` as character literals, and two `wsl_v5` defer cuddles. The
|
||||||
|
repo has no golangci-lint version pin to bump (no Dockerfile or CI;
|
||||||
|
`make lint` runs the host `golangci-lint`, currently v2.12.2).
|
||||||
|
|
||||||
- 2026-07-24 Seed compatibility — item tables (seed-compat): instrumented the C
|
- 2026-07-24 Seed compatibility — item tables (seed-compat): instrumented the C
|
||||||
reference on modern-rogue with a DUMP mode (testdata/c_seedcompat.patch) that
|
reference on modern-rogue with a DUMP mode (testdata/c_seedcompat.patch) that
|
||||||
forces the RNG seed and prints the per-seed item appearance tables (potion
|
forces the RNG seed and prints the per-seed item appearance tables (potion
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ func chooseSeed(wizard bool) int32 {
|
|||||||
|
|
||||||
// The C game computed `lowtime + getpid()` in int; the truncation to
|
// The C game computed `lowtime + getpid()` in int; the truncation to
|
||||||
// 32 bits is the same wraparound the C int arithmetic performed.
|
// 32 bits is the same wraparound the C int arithmetic performed.
|
||||||
|
//nolint:mnd // C-faithful: the C int wraparound mask
|
||||||
return int32(time.Now().Unix()&0x7fffffff) +
|
return int32(time.Now().Unix()&0x7fffffff) +
|
||||||
int32(os.Getpid()&0x7fffffff)
|
int32(os.Getpid()&0x7fffffff)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// chase.c — code for one creature to chase another.
|
// chase.c — code for one creature to chase another.
|
||||||
@@ -157,7 +158,9 @@ func (g *RogueGame) chaseRooms(th *Monster) (*Room, *Room, bool) {
|
|||||||
// nearest exit toward its desire when it is in a different room, or the
|
// nearest exit toward its desire when it is in a different room, or the
|
||||||
// desire itself. shot means a dragon breathed flame instead of moving
|
// desire itself. shot means a dragon breathed flame instead of moving
|
||||||
// (the goal loop of chase.c do_chase).
|
// (the goal loop of chase.c do_chase).
|
||||||
func (g *RogueGame) chaseGoal(th *Monster, rer, ree *Room, door bool, mindist int) (Coord, bool) {
|
func (g *RogueGame) chaseGoal(
|
||||||
|
th *Monster, rer, ree *Room, door bool, mindist int,
|
||||||
|
) (Coord, bool) {
|
||||||
var this Coord
|
var this Coord
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// command.c — read and execute the user commands.
|
// command.c — read and execute the user commands.
|
||||||
@@ -794,6 +795,7 @@ func (g *RogueGame) call() {
|
|||||||
func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) {
|
func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) {
|
||||||
it := &g.Items
|
it := &g.Items
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case KindRing:
|
case KindRing:
|
||||||
return &it.Rings[obj.Which], it.RingStones[obj.Which], nil, true
|
return &it.Rings[obj.Which], it.RingStones[obj.Which], nil, true
|
||||||
@@ -815,7 +817,9 @@ func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) {
|
|||||||
// callPrelude resolves the item's current name, reporting a previous
|
// callPrelude resolves the item's current name, reporting a previous
|
||||||
// guess; ok is false when the item is already identified (command.c
|
// guess; ok is false when the item is already identified (command.c
|
||||||
// call).
|
// call).
|
||||||
func (g *RogueGame) callPrelude(op *ObjInfo, elsewise string, guess *string) (string, *string, bool) {
|
func (g *RogueGame) callPrelude(
|
||||||
|
op *ObjInfo, elsewise string, guess *string,
|
||||||
|
) (string, *string, bool) {
|
||||||
fromGuess := false
|
fromGuess := false
|
||||||
|
|
||||||
if op != nil {
|
if op != nil {
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ package game
|
|||||||
|
|
||||||
// Creature is the _t arm of the C THING union: the player or a monster.
|
// Creature is the _t arm of the C THING union: the player or a monster.
|
||||||
type Creature struct {
|
type Creature struct {
|
||||||
Pos Coord // position
|
Pos Coord // position
|
||||||
Turn bool // if slowed, is it a turn to move
|
Turn bool // if slowed, is it a turn to move
|
||||||
Type byte // what it is: 'A'..'Z' for monsters, '@' for the player
|
Type byte // what it is: 'A'..'Z' for monsters, '@' for the player
|
||||||
Disguise byte // what mimic looks like
|
Disguise byte // what mimic looks like
|
||||||
OldCh byte // character that was where it was
|
OldCh byte // character that was where it was
|
||||||
Dest *Coord // where it is running to — aliases live coords (hero pos, room gold, another monster's pos)
|
// Dest is where it is running to — aliases live coords (hero pos,
|
||||||
Flags CreatureFlags
|
// room gold, another monster's pos).
|
||||||
Stats Stats
|
Dest *Coord
|
||||||
Room *Room // current room for thing
|
Flags CreatureFlags
|
||||||
Pack []*Object // what the thing is carrying
|
Stats Stats
|
||||||
|
Room *Room // current room for thing
|
||||||
|
Pack []*Object // what the thing is carrying
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monster is a hostile creature on the level.
|
// Monster is a hostile creature on the level.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// daemons.c — the daemon and fuse callbacks, dispatched by DaemonID.
|
// daemons.c — the daemon and fuse callbacks, dispatched by DaemonID.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
@@ -6,6 +7,8 @@ import "testing"
|
|||||||
// including its junk-tolerant edges: the bestiary placeholder "%%%x0" and
|
// including its junk-tolerant edges: the bestiary placeholder "%%%x0" and
|
||||||
// the flytrap reset "000x0" both mean a single 0x0 attack.
|
// the flytrap reset "000x0" both mean a single 0x0 attack.
|
||||||
func TestParseDice(t *testing.T) {
|
func TestParseDice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
in string
|
in string
|
||||||
want string
|
want string
|
||||||
@@ -32,6 +35,8 @@ func TestParseDice(t *testing.T) {
|
|||||||
// The bestiary and weapon tables must parse to at least one attack each so
|
// The bestiary and weapon tables must parse to at least one attack each so
|
||||||
// every creature and weapon actually swings.
|
// every creature and weapon actually swings.
|
||||||
func TestTablesHaveDice(t *testing.T) {
|
func TestTablesHaveDice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
data := newGameData()
|
data := newGameData()
|
||||||
for i, m := range data.monsterTable {
|
for i, m := range data.monsterTable {
|
||||||
if len(m.Stats.Dmg) == 0 {
|
if len(m.Stats.Dmg) == 0 {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
@@ -37,6 +38,8 @@ func setInput(t *testing.T, g *RogueGame, input ...byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQuaffHealingPotion(t *testing.T) {
|
func TestQuaffHealingPotion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGameInput(t)
|
g := mkGameInput(t)
|
||||||
pot := newObject()
|
pot := newObject()
|
||||||
pot.Kind = KindPotion
|
pot.Kind = KindPotion
|
||||||
@@ -61,6 +64,8 @@ func TestQuaffHealingPotion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGameInput(t)
|
g := mkGameInput(t)
|
||||||
pot := newObject()
|
pot := newObject()
|
||||||
pot.Kind = KindPotion
|
pot.Kind = KindPotion
|
||||||
@@ -88,6 +93,8 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadEnchantArmor(t *testing.T) {
|
func TestReadEnchantArmor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGameInput(t)
|
g := mkGameInput(t)
|
||||||
scr := newObject()
|
scr := newObject()
|
||||||
scr.Kind = KindScroll
|
scr.Kind = KindScroll
|
||||||
@@ -105,6 +112,7 @@ func TestReadEnchantArmor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
|
func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Note: this must not use a greedy monster ('O' orc, ISGREED): the C
|
// Note: this must not use a greedy monster ('O' orc, ISGREED): the C
|
||||||
// wake_monster gold-guarding check has no ISHELD guard, so the
|
// wake_monster gold-guarding check has no ISHELD guard, so the
|
||||||
// look(TRUE) at the end of read_scroll immediately re-wakes greedy
|
// look(TRUE) at the end of read_scroll immediately re-wakes greedy
|
||||||
@@ -133,6 +141,8 @@ func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
|
|||||||
// (orc) held by a scroll is re-woken by the look(TRUE) that read_scroll
|
// (orc) held by a scroll is re-woken by the look(TRUE) that read_scroll
|
||||||
// performs, ending up both held and running again.
|
// performs, ending up both held and running again.
|
||||||
func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGameInput(t)
|
g := mkGameInput(t)
|
||||||
tp := spawnAdjacent(g, 'O')
|
tp := spawnAdjacent(g, 'O')
|
||||||
tp.Flags.Set(Awake)
|
tp.Flags.Set(Awake)
|
||||||
@@ -158,6 +168,8 @@ func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestZapSlowMonster(t *testing.T) {
|
func TestZapSlowMonster(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGameInput(t)
|
g := mkGameInput(t)
|
||||||
tp := spawnAdjacent(g, 'Z')
|
tp := spawnAdjacent(g, 'Z')
|
||||||
stick := newObject()
|
stick := newObject()
|
||||||
@@ -183,6 +195,8 @@ func TestZapSlowMonster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseOpts(t *testing.T) {
|
func TestParseOpts(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := New(Params{Seed: 1})
|
g := New(Params{Seed: 1})
|
||||||
g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow")
|
g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow")
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
@@ -25,6 +26,8 @@ func spawnAdjacent(g *RogueGame, typ byte) *Monster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRollEmParsesMultiAttackDice(t *testing.T) {
|
func TestRollEmParsesMultiAttackDice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 42)
|
g := mkGame(t, 42)
|
||||||
att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: dice("1x4/1x4/1x4")}}
|
att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: dice("1x4/1x4/1x4")}}
|
||||||
def := &Creature{Stats: Stats{ArmorClass: 10, HP: 1000}}
|
def := &Creature{Stats: Stats{ArmorClass: 10, HP: 1000}}
|
||||||
@@ -43,6 +46,8 @@ func TestRollEmParsesMultiAttackDice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFightKillsMonster(t *testing.T) {
|
func TestFightKillsMonster(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 7)
|
g := mkGame(t, 7)
|
||||||
tp := spawnAdjacent(g, 'B') // bat: 1 hit die
|
tp := spawnAdjacent(g, 'B') // bat: 1 hit die
|
||||||
tp.Stats.HP = 1
|
tp.Stats.HP = 1
|
||||||
@@ -64,6 +69,8 @@ func TestFightKillsMonster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAttackHurtsPlayer(t *testing.T) {
|
func TestAttackHurtsPlayer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 9)
|
g := mkGame(t, 9)
|
||||||
tp := spawnAdjacent(g, 'T') // troll: 1x8/1x8/2x6
|
tp := spawnAdjacent(g, 'T') // troll: 1x8/1x8/2x6
|
||||||
tp.Stats.Lvl = 20 // always hits
|
tp.Stats.Lvl = 20 // always hits
|
||||||
@@ -80,6 +87,8 @@ func TestAttackHurtsPlayer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunnersChaseHero(t *testing.T) {
|
func TestRunnersChaseHero(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 3)
|
g := mkGame(t, 3)
|
||||||
// Place a hobgoblin a few squares away in the hero's room and set it
|
// Place a hobgoblin a few squares away in the hero's room and set it
|
||||||
// running at the hero.
|
// running at the hero.
|
||||||
@@ -104,6 +113,8 @@ func TestRunnersChaseHero(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKilledLeprechaunDropsGoldViaFall(t *testing.T) {
|
func TestKilledLeprechaunDropsGoldViaFall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 21)
|
g := mkGame(t, 21)
|
||||||
tp := spawnAdjacent(g, 'L')
|
tp := spawnAdjacent(g, 'L')
|
||||||
tp.Stats.HP = 0
|
tp.Stats.HP = 0
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// ItemLore is the per-game item identity state: the randomized appearance
|
// ItemLore is the per-game item identity state: the randomized appearance
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
@@ -169,7 +170,8 @@ func (g *RogueGame) initProbs() {
|
|||||||
sumProbs(g.Items.Scrolls[:])
|
sumProbs(g.Items.Scrolls[:])
|
||||||
sumProbs(g.Items.Rings[:])
|
sumProbs(g.Items.Rings[:])
|
||||||
sumProbs(g.Items.Sticks[:])
|
sumProbs(g.Items.Sticks[:])
|
||||||
sumProbs(g.Items.Weapons[:NumWeaponTypes]) // C sums MAXWEAPONS, excluding the flame entry
|
// C sums MAXWEAPONS, excluding the flame entry.
|
||||||
|
sumProbs(g.Items.Weapons[:NumWeaponTypes])
|
||||||
sumProbs(g.Items.Armors[:])
|
sumProbs(g.Items.Armors[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// Place describes a spot on the level map (rogue.h PLACE).
|
// Place describes a spot on the level map (rogue.h PLACE).
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// misc.c — look() display maintenance, direction input, eating, level-ups,
|
// misc.c — look() display maintenance, direction input, eating, level-ups,
|
||||||
@@ -130,7 +131,9 @@ func lookForeignPassage(s *lookScan, fp PlaceFlags, ch byte) bool {
|
|||||||
|
|
||||||
// lookDiagonalBlocked hides diagonal door/passage squares the hero could
|
// lookDiagonalBlocked hides diagonal door/passage squares the hero could
|
||||||
// not actually step to (misc.c look).
|
// not actually step to (misc.c look).
|
||||||
func (g *RogueGame) lookDiagonalBlocked(s *lookScan, fp PlaceFlags, ch byte, y, x int) bool {
|
func (g *RogueGame) lookDiagonalBlocked(
|
||||||
|
s *lookScan, fp PlaceFlags, ch byte, y, x int,
|
||||||
|
) bool {
|
||||||
if !fp.Has(FPassage) && ch != Door {
|
if !fp.Has(FPassage) && ch != Door {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -167,7 +170,9 @@ func (g *RogueGame) lookShow(s *lookScan, tp *Monster, ch byte, y, x int) bool {
|
|||||||
// lookCellChar picks what the square shows: trip rendering for empty
|
// lookCellChar picks what the square shows: trip rendering for empty
|
||||||
// squares, waking and disguises for monsters. skip means the square is
|
// squares, waking and disguises for monsters. skip means the square is
|
||||||
// not drawn at all (the monster switch of the look loop).
|
// not drawn at all (the monster switch of the look loop).
|
||||||
func (g *RogueGame) lookCellChar(s *lookScan, tp *Monster, y, x int, ch byte) (byte, bool) {
|
func (g *RogueGame) lookCellChar(
|
||||||
|
s *lookScan, tp *Monster, y, x int, ch byte,
|
||||||
|
) (byte, bool) {
|
||||||
p := &g.Player
|
p := &g.Player
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// monsters.c — monster creation and saving throws.
|
// monsters.c — monster creation and saving throws.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// move.c — hero movement commands.
|
// move.c — hero movement commands.
|
||||||
@@ -60,7 +61,9 @@ func (g *RogueGame) moveHero(dy, dx int) {
|
|||||||
// moveResolve acts on the square the hero stepped at: a wall may turn a
|
// moveResolve acts on the square the hero stepped at: a wall may turn a
|
||||||
// passage runner (reported with the new deltas); anything else completes
|
// passage runner (reported with the new deltas); anything else completes
|
||||||
// or refuses the move (the switch of move.c do_move).
|
// or refuses the move (the switch of move.c do_move).
|
||||||
func (g *RogueGame) moveResolve(nh Coord, ch byte, fl PlaceFlags, dy, dx int) (bool, int, int) {
|
func (g *RogueGame) moveResolve(
|
||||||
|
nh Coord, ch byte, fl PlaceFlags, dy, dx int,
|
||||||
|
) (bool, int, int) {
|
||||||
switch ch {
|
switch ch {
|
||||||
case ' ', '|', '-':
|
case ' ', '|', '-':
|
||||||
if turn, ndy, ndx := g.passageTurn(dy, dx); turn {
|
if turn, ndy, ndx := g.passageTurn(dy, dx); turn {
|
||||||
@@ -343,7 +346,8 @@ func (g *RogueGame) trapMystery(Coord) {
|
|||||||
case 0:
|
case 0:
|
||||||
g.msg("you are suddenly in a parallel dimension")
|
g.msg("you are suddenly in a parallel dimension")
|
||||||
case 1:
|
case 1:
|
||||||
g.msg("the light in here suddenly seems %s", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
g.msg("the light in here suddenly seems %s",
|
||||||
|
g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
||||||
case 2:
|
case 2:
|
||||||
g.msg("you feel a sting in the side of your neck")
|
g.msg("you feel a sting in the side of your neck")
|
||||||
case 3:
|
case 3:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// new_level.c — dig and draw a new level.
|
// new_level.c — dig and draw a new level.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -35,6 +36,8 @@ func renderMap(g *RogueGame) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewLevelInvariants(t *testing.T) {
|
func TestNewLevelInvariants(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
for _, seed := range []int32{1, 12345, 2026, 99999} {
|
for _, seed := range []int32{1, 12345, 2026, 99999} {
|
||||||
g := genLevel(t, seed)
|
g := genLevel(t, seed)
|
||||||
|
|
||||||
@@ -142,6 +145,8 @@ func checkStartingKit(t *testing.T, g *RogueGame, seed int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewLevelDeterministic(t *testing.T) {
|
func TestNewLevelDeterministic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
a := renderMap(genLevel(t, 12345))
|
a := renderMap(genLevel(t, 12345))
|
||||||
|
|
||||||
b := renderMap(genLevel(t, 12345))
|
b := renderMap(genLevel(t, 12345))
|
||||||
@@ -158,6 +163,8 @@ func TestNewLevelDeterministic(t *testing.T) {
|
|||||||
// TestDeeperLevels exercises generation across many depths and seeds —
|
// TestDeeperLevels exercises generation across many depths and seeds —
|
||||||
// mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep.
|
// mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep.
|
||||||
func TestDeeperLevels(t *testing.T) {
|
func TestDeeperLevels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
for _, seed := range []int32{7, 42, 1000, 31337} {
|
for _, seed := range []int32{7, 42, 1000, 31337} {
|
||||||
g := New(Params{Seed: seed})
|
g := New(Params{Seed: seed})
|
||||||
for depth := 1; depth <= 30; depth++ {
|
for depth := 1; depth <= 30; depth++ {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
|
|
||||||
// Glyph returns the map/display character for this kind of object.
|
// Glyph returns the map/display character for this kind of object.
|
||||||
func (k ObjectKind) Glyph() byte {
|
func (k ObjectKind) Glyph() byte {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch k {
|
switch k {
|
||||||
case KindPotion:
|
case KindPotion:
|
||||||
return Potion
|
return Potion
|
||||||
@@ -64,6 +65,7 @@ func (k ObjectKind) Glyph() byte {
|
|||||||
|
|
||||||
// String names the category the way the C type_name() did.
|
// String names the category the way the C type_name() did.
|
||||||
func (k ObjectKind) String() string {
|
func (k ObjectKind) String() string {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch k {
|
switch k {
|
||||||
case KindPotion:
|
case KindPotion:
|
||||||
return potionName
|
return potionName
|
||||||
@@ -118,6 +120,7 @@ func (k ObjectKind) MergesInPack() bool {
|
|||||||
// stringRest names the remaining kinds, including the ring-or-stick
|
// stringRest names the remaining kinds, including the ring-or-stick
|
||||||
// prompt pseudo-kind (the tail of the C type_name switch).
|
// prompt pseudo-kind (the tail of the C type_name switch).
|
||||||
func (k ObjectKind) stringRest() string {
|
func (k ObjectKind) stringRest() string {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch k {
|
switch k {
|
||||||
case KindWand:
|
case KindWand:
|
||||||
return "wand or staff"
|
return "wand or staff"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
@@ -118,6 +119,7 @@ func boolStr(b bool) string {
|
|||||||
// getOpt reads a new value for an option (options.c get_bool/get_sf/
|
// getOpt reads a new value for an option (options.c get_bool/get_sf/
|
||||||
// get_inv_t/get_str dispatch).
|
// get_inv_t/get_str dispatch).
|
||||||
func (g *RogueGame) getOpt(op *optDesc) int {
|
func (g *RogueGame) getOpt(op *optDesc) int {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch op.kind {
|
switch op.kind {
|
||||||
case optBool:
|
case optBool:
|
||||||
return g.getBool(op.boolP)
|
return g.getBool(op.boolP)
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ func packScanWhich(pack []*Object, obj *Object, i, lp int) (int, int) {
|
|||||||
// anything else marks the insertion point (the matched-subtype switch
|
// anything else marks the insertion point (the matched-subtype switch
|
||||||
// of pack.c add_pack). It returns the resulting entry, the insert-after
|
// of pack.c add_pack). It returns the resulting entry, the insert-after
|
||||||
// index, whether a merge happened, and ok false when the pack is full.
|
// index, whether a merge happened, and ok false when the pack is full.
|
||||||
func (g *RogueGame) packMatch(obj, op *Object, i int, fromFloor bool) (*Object, int, bool, bool) {
|
func (g *RogueGame) packMatch(
|
||||||
|
obj, op *Object, i int, fromFloor bool,
|
||||||
|
) (*Object, int, bool, bool) {
|
||||||
switch {
|
switch {
|
||||||
case op.Kind.MergesInPack():
|
case op.Kind.MergesInPack():
|
||||||
if !g.packRoom(fromFloor, obj) {
|
if !g.packRoom(fromFloor, obj) {
|
||||||
@@ -175,7 +177,9 @@ func (g *RogueGame) packMatch(obj, op *Object, i int, fromFloor bool) (*Object,
|
|||||||
|
|
||||||
// packMatchGroup rejoins a grouped missile bundle with its group entry
|
// packMatchGroup rejoins a grouped missile bundle with its group entry
|
||||||
// (the o_group arm of pack.c add_pack).
|
// (the o_group arm of pack.c add_pack).
|
||||||
func (g *RogueGame) packMatchGroup(obj *Object, i int, fromFloor bool) (*Object, int, bool, bool) {
|
func (g *RogueGame) packMatchGroup(
|
||||||
|
obj *Object, i int, fromFloor bool,
|
||||||
|
) (*Object, int, bool, bool) {
|
||||||
p := &g.Player
|
p := &g.Player
|
||||||
|
|
||||||
lp := i
|
lp := i
|
||||||
@@ -333,6 +337,7 @@ func (g *RogueGame) pickUp(ch byte) {
|
|||||||
// everything, KindCallable takes anything nameable (not food, not the
|
// everything, KindCallable takes anything nameable (not food, not the
|
||||||
// amulet), KindRingOrStick takes rings and wands.
|
// amulet), KindRingOrStick takes rings and wands.
|
||||||
func matchesFilter(kind ObjectKind, item *Object) bool {
|
func matchesFilter(kind ObjectKind, item *Object) bool {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch kind {
|
switch kind {
|
||||||
case KindNone:
|
case KindNone:
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// passages.c — draw the connecting passages.
|
// passages.c — draw the connecting passages.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
@@ -261,6 +262,7 @@ func (g *RogueGame) applyPotionFuse(kind PotionKind, knowit bool) {
|
|||||||
|
|
||||||
// isMagic reports whether an object radiates magic (potions.c is_magic).
|
// isMagic reports whether an object radiates magic (potions.c is_magic).
|
||||||
func (g *RogueGame) isMagic(o *Object) bool {
|
func (g *RogueGame) isMagic(o *Object) bool {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch o.Kind {
|
switch o.Kind {
|
||||||
case KindArmor:
|
case KindArmor:
|
||||||
return o.Flags.Has(Protected) || o.ArmorClass != g.data.aClass[o.Which]
|
return o.Flags.Has(Protected) || o.ArmorClass != g.data.aClass[o.Which]
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func (g *RogueGame) ringOn() {
|
|||||||
p.CurRing[ring] = obj
|
p.CurRing[ring] = obj
|
||||||
|
|
||||||
// Calculate the effect it has on the poor guy.
|
// Calculate the effect it has on the poor guy.
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.RingKind() {
|
switch obj.RingKind() {
|
||||||
case RingAddStrength:
|
case RingAddStrength:
|
||||||
g.changeStrength(obj.Bonus)
|
g.changeStrength(obj.Bonus)
|
||||||
@@ -171,6 +172,7 @@ func ringNum(_ *RogueGame, obj *Object) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.RingKind() {
|
switch obj.RingKind() {
|
||||||
case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity:
|
case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity:
|
||||||
return fmt.Sprintf(" [%s]", num(obj.Bonus, 0, Ring))
|
return fmt.Sprintf(" [%s]", num(obj.Bonus, 0, Ring))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -98,8 +99,10 @@ func (g *RogueGame) totalWinner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g.standend()
|
g.standend()
|
||||||
g.scr.Std.MvAddStr(10, 0, "You have joined the elite ranks of those who have escaped the")
|
g.scr.Std.MvAddStr(10, 0,
|
||||||
g.scr.Std.MvAddStr(11, 0, "Dungeons of Doom alive. You journey home and sell all your loot at")
|
"You have joined the elite ranks of those who have escaped the")
|
||||||
|
g.scr.Std.MvAddStr(11, 0,
|
||||||
|
"Dungeons of Doom alive. You journey home and sell all your loot at")
|
||||||
g.scr.Std.MvAddStr(12, 0, "a great profit and are admitted to the Fighters' Guild.")
|
g.scr.Std.MvAddStr(12, 0, "a great profit and are admitted to the Fighters' Guild.")
|
||||||
g.mvaddstr(NumLines-1, 0, "--Press space to continue--")
|
g.mvaddstr(NumLines-1, 0, "--Press space to continue--")
|
||||||
g.refresh()
|
g.refresh()
|
||||||
@@ -132,6 +135,7 @@ func (g *RogueGame) objectWorth(obj *Object) int {
|
|||||||
it := &g.Items
|
it := &g.Items
|
||||||
worth := 0
|
worth := 0
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case KindFood:
|
case KindFood:
|
||||||
worth = 2 * obj.Count
|
worth = 2 * obj.Count
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// Rng is the original Rogue linear congruential generator. The C RN macro is
|
// Rng is the original Rogue linear congruential generator. The C RN macro is
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
@@ -6,6 +7,8 @@ import "testing"
|
|||||||
// (seed = seed*11109+13849; rnd(range) = abs(RN) % range) compiled and run
|
// (seed = seed*11109+13849; rnd(range) = abs(RN) % range) compiled and run
|
||||||
// on this machine. They lock in seed compatibility with the C game.
|
// on this machine. They lock in seed compatibility with the C game.
|
||||||
func TestRndMatchesCImplementation(t *testing.T) {
|
func TestRndMatchesCImplementation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
golden := map[int32][]int{
|
golden := map[int32][]int{
|
||||||
1: {0, 30, 79, 7, 87, 1, 23, 7, 57, 98},
|
1: {0, 30, 79, 7, 87, 1, 23, 7, 57, 98},
|
||||||
12345: {92, 92, 45, 98, 24, 39, 92, 67, 3, 7},
|
12345: {92, 92, 45, 98, 24, 39, 92, 67, 3, 7},
|
||||||
@@ -23,6 +26,8 @@ func TestRndMatchesCImplementation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRollMatchesCImplementation(t *testing.T) {
|
func TestRollMatchesCImplementation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
golden := map[int32][]int{
|
golden := map[int32][]int{
|
||||||
1: {6, 8, 10},
|
1: {6, 8, 10},
|
||||||
12345: {10, 10, 15},
|
12345: {10, 10, 15},
|
||||||
@@ -42,6 +47,8 @@ func TestRollMatchesCImplementation(t *testing.T) {
|
|||||||
// rnd(0) must return 0 without stepping the generator: the C macro
|
// rnd(0) must return 0 without stepping the generator: the C macro
|
||||||
// short-circuits before evaluating RN, and BEFORE/AFTER depend on it.
|
// short-circuits before evaluating RN, and BEFORE/AFTER depend on it.
|
||||||
func TestRndZeroDoesNotStep(t *testing.T) {
|
func TestRndZeroDoesNotStep(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Rng{Seed: 42}
|
r := &Rng{Seed: 42}
|
||||||
if got := r.Rnd(0); got != 0 {
|
if got := r.Rnd(0); got != 0 {
|
||||||
t.Fatalf("rnd(0) = %d, want 0", got)
|
t.Fatalf("rnd(0) = %d, want 0", got)
|
||||||
@@ -55,6 +62,8 @@ func TestRndZeroDoesNotStep(t *testing.T) {
|
|||||||
// spread(1)==1 and spread(2)==2 deterministically; the C BEFORE/AFTER
|
// spread(1)==1 and spread(2)==2 deterministically; the C BEFORE/AFTER
|
||||||
// constants rely on this.
|
// constants rely on this.
|
||||||
func TestSpreadSmallValues(t *testing.T) {
|
func TestSpreadSmallValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := &RogueGame{Rng: &Rng{Seed: 7}}
|
g := &RogueGame{Rng: &Rng{Seed: 7}}
|
||||||
if got := g.spread(1); got != 1 {
|
if got := g.spread(1); got != 1 {
|
||||||
t.Errorf("spread(1) = %d, want 1", got)
|
t.Errorf("spread(1) = %d, want 1", got)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// rooms.c — create the layout for the new level.
|
// rooms.c — create the layout for the new level.
|
||||||
@@ -330,7 +331,9 @@ func floorChar(rp *Room) byte {
|
|||||||
return Floor
|
return Floor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *RogueGame) findFloorImpl(rp *Room, limit int, monst, pickroom bool) (Coord, bool) {
|
func (g *RogueGame) findFloorImpl(
|
||||||
|
rp *Room, limit int, monst, pickroom bool,
|
||||||
|
) (Coord, bool) {
|
||||||
var compchar byte
|
var compchar byte
|
||||||
if !pickroom {
|
if !pickroom {
|
||||||
compchar = floorChar(rp)
|
compchar = floorChar(rp)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -39,6 +40,7 @@ func driveTurns(t *testing.T, g *RogueGame, n int) {
|
|||||||
// TestRunDownStairs stands the hero on the staircase and descends via the
|
// TestRunDownStairs stands the hero on the staircase and descends via the
|
||||||
// '>' command through the real turn loop, then checks the level changed.
|
// '>' command through the real turn loop, then checks the level changed.
|
||||||
func TestRunDownStairs(t *testing.T) {
|
func TestRunDownStairs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// '>' is a free action (After=false), so it is followed by a paying
|
// '>' is a free action (After=false), so it is followed by a paying
|
||||||
// rest ('.') to end the command() call; without a paying action the
|
// rest ('.') to end the command() call; without a paying action the
|
||||||
// turn loop would spin forever on the auto-fed prompt input.
|
// turn loop would spin forever on the auto-fed prompt input.
|
||||||
@@ -65,6 +67,8 @@ func TestRunDownStairs(t *testing.T) {
|
|||||||
// quit and death paths that normally show it now exit the process, so the
|
// quit and death paths that normally show it now exit the process, so the
|
||||||
// display is exercised through score() directly.
|
// display is exercised through score() directly.
|
||||||
func TestScoreRendersList(t *testing.T) {
|
func TestScoreRendersList(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := New(Params{Seed: 1, Term: &testTerm{}})
|
g := New(Params{Seed: 1, Term: &testTerm{}})
|
||||||
g.Player.Purse = 100
|
g.Player.Purse = 100
|
||||||
|
|
||||||
@@ -90,6 +94,8 @@ func TestScoreRendersList(t *testing.T) {
|
|||||||
// The hero is fortified so no death exits the process (step 8), and the fixed
|
// The hero is fortified so no death exits the process (step 8), and the fixed
|
||||||
// seed keeps it deterministic.
|
// seed keeps it deterministic.
|
||||||
func TestDeepPlaythrough(t *testing.T) {
|
func TestDeepPlaythrough(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := New(Params{Seed: 4242, Wizard: true, Term: &testTerm{}})
|
g := New(Params{Seed: 4242, Wizard: true, Term: &testTerm{}})
|
||||||
g.startLevel()
|
g.startLevel()
|
||||||
g.prePlay()
|
g.prePlay()
|
||||||
@@ -155,6 +161,7 @@ func TestDeepPlaythrough(t *testing.T) {
|
|||||||
// and traps. The hero is fortified each turn so nothing exits the process,
|
// and traps. The hero is fortified each turn so nothing exits the process,
|
||||||
// and the fixed seeds keep it deterministic; the point is to surface panics.
|
// and the fixed seeds keep it deterministic; the point is to surface panics.
|
||||||
func TestTurnLoopCrashSweep(t *testing.T) {
|
func TestTurnLoopCrashSweep(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// A generous mix of movement, search, and rest. The spaces between
|
// A generous mix of movement, search, and rest. The spaces between
|
||||||
// commands double as answers to any --More-- prompt (wait_for eats
|
// commands double as answers to any --More-- prompt (wait_for eats
|
||||||
// everything up to a space); without them one prompt would swallow the
|
// everything up to a space); without them one prompt would swallow the
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -686,6 +687,7 @@ func Restore(path string, params Params) (*RogueGame, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = f.Close() }() // read-only handle
|
defer func() { _ = f.Close() }() // read-only handle
|
||||||
|
|
||||||
var st SaveState
|
var st SaveState
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,6 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSaveRestoreRoundTrip(t *testing.T) {
|
func TestSaveRestoreRoundTrip(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 4242)
|
g := mkGame(t, 4242)
|
||||||
// Dirty up some state so the round trip is meaningful.
|
// Dirty up some state so the round trip is meaningful.
|
||||||
g.Player.Purse = 123
|
g.Player.Purse = 123
|
||||||
@@ -134,6 +137,8 @@ func checkEquipmentAliasing(t *testing.T, g, h *RogueGame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRestoreRejectsWrongVersion(t *testing.T) {
|
func TestRestoreRejectsWrongVersion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := mkGame(t, 1)
|
g := mkGame(t, 1)
|
||||||
path := filepath.Join(t.TempDir(), "rogue.save")
|
path := filepath.Join(t.TempDir(), "rogue.save")
|
||||||
st := g.snapshot()
|
st := g.snapshot()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -36,6 +37,7 @@ func (g *RogueGame) rdScore() []ScoreEnt {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return topTen
|
return topTen
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = f.Close() }() // read-only handle
|
defer func() { _ = f.Close() }() // read-only handle
|
||||||
|
|
||||||
var onDisk []ScoreEnt
|
var onDisk []ScoreEnt
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// scrolls.c — read a scroll and let it happen.
|
// scrolls.c — read a scroll and let it happen.
|
||||||
@@ -148,7 +149,8 @@ func (g *RogueGame) createMonsterSpot() (Coord, bool) {
|
|||||||
// Or anything else nasty
|
// Or anything else nasty
|
||||||
if ch := g.Level.VisibleChar(y, x); stepOk(ch) {
|
if ch := g.Level.VisibleChar(y, x); stepOk(ch) {
|
||||||
if ch == Scroll {
|
if ch == Scroll {
|
||||||
if fo := g.Level.ObjectAt(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster {
|
if fo := g.Level.ObjectAt(y, x); fo != nil &&
|
||||||
|
fo.ScrollKind() == ScrollScareMonster {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -51,6 +52,8 @@ func dumpItemTables(seed int32, g *RogueGame) string {
|
|||||||
// match C byte for byte. The golden is captured from an instrumented build
|
// match C byte for byte. The golden is captured from an instrumented build
|
||||||
// of the C game (testdata/README.md).
|
// of the C game (testdata/README.md).
|
||||||
func TestSeedCompatItemTables(t *testing.T) {
|
func TestSeedCompatItemTables(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
golden, err := os.ReadFile("testdata/item_tables.golden")
|
golden, err := os.ReadFile("testdata/item_tables.golden")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read golden: %v", err)
|
t.Fatalf("read golden: %v", err)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
@@ -272,6 +273,7 @@ func slowTarget(tp *Monster) {
|
|||||||
func (g *RogueGame) zapBolt(obj *Object) bool {
|
func (g *RogueGame) zapBolt(obj *Object) bool {
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.WandKind() {
|
switch obj.WandKind() {
|
||||||
case WandLightning:
|
case WandLightning:
|
||||||
name = "bolt"
|
name = "bolt"
|
||||||
@@ -434,7 +436,9 @@ func boltBounces(ch byte, heroPos, pos Coord) bool {
|
|||||||
// boltStrikesMonster resolves a bolt arriving on a monster's square (the
|
// boltStrikesMonster resolves a bolt arriving on a monster's square (the
|
||||||
// monster arm of the fire_bolt loop). It reports whether the bolt was
|
// monster arm of the fire_bolt loop). It reports whether the bolt was
|
||||||
// used up.
|
// used up.
|
||||||
func (g *RogueGame) boltStrikesMonster(tp *Monster, bolt *Object, pos Coord, ch byte, name string, fromHero bool) bool {
|
func (g *RogueGame) boltStrikesMonster(
|
||||||
|
tp *Monster, bolt *Object, pos Coord, ch byte, name string, fromHero bool,
|
||||||
|
) bool {
|
||||||
tp.OldCh = g.Level.Char(pos.Y, pos.X)
|
tp.OldCh = g.Level.Char(pos.Y, pos.X)
|
||||||
if !g.saveThrow(VsMagic, &tp.Stats) {
|
if !g.saveThrow(VsMagic, &tp.Stats) {
|
||||||
bolt.Pos = pos
|
bolt.Pos = pos
|
||||||
@@ -506,6 +510,7 @@ func (g *RogueGame) fixStick(cur *Object) {
|
|||||||
|
|
||||||
cur.HurlDmg = dice("1x1")
|
cur.HurlDmg = dice("1x1")
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch cur.WandKind() {
|
switch cur.WandKind() {
|
||||||
case WandLight:
|
case WandLight:
|
||||||
cur.Charges = g.rnd(10) + 10
|
cur.Charges = g.rnd(10) + 10
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// tables.go — the static data tables the C game kept as file-scope globals
|
// tables.go — the static data tables the C game kept as file-scope globals
|
||||||
@@ -166,7 +167,10 @@ const ripWall = " | |"
|
|||||||
//nolint:funlen,maintidx // a single composite literal holding every C data table
|
//nolint:funlen,maintidx // a single composite literal holding every C data table
|
||||||
func newGameData() *gameData {
|
func newGameData() *gameData {
|
||||||
return &gameData{
|
return &gameData{
|
||||||
initStats: Stats{Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12, Dmg: dice("1x4"), MaxHP: 12},
|
initStats: Stats{
|
||||||
|
Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12,
|
||||||
|
Dmg: dice("1x4"), MaxHP: 12,
|
||||||
|
},
|
||||||
|
|
||||||
aClass: [NumArmorTypes]int{
|
aClass: [NumArmorTypes]int{
|
||||||
8, // LEATHER
|
8, // LEATHER
|
||||||
@@ -205,7 +209,8 @@ func newGameData() *gameData {
|
|||||||
{"dragon", 100, Mean, Stats{10, 5000, 10, -1, 1, dice("1x8/1x8/3x10"), 0}},
|
{"dragon", 100, Mean, Stats{10, 5000, 10, -1, 1, dice("1x8/1x8/3x10"), 0}},
|
||||||
{"emu", 0, Mean, Stats{10, 2, 1, 7, 1, dice("1x2"), 0}},
|
{"emu", 0, Mean, Stats{10, 2, 1, 7, 1, dice("1x2"), 0}},
|
||||||
{"venus flytrap", 0, Mean, Stats{10, 80, 8, 3, 1, dice("%%%x0"), 0}},
|
{"venus flytrap", 0, Mean, Stats{10, 80, 8, 3, 1, dice("%%%x0"), 0}},
|
||||||
{"griffin", 20, Mean | Flying | Regenerates, Stats{10, 2000, 13, 2, 1, dice("4x3/3x5"), 0}},
|
{"griffin", 20, Mean | Flying | Regenerates,
|
||||||
|
Stats{10, 2000, 13, 2, 1, dice("4x3/3x5"), 0}},
|
||||||
{"hobgoblin", 0, Mean, Stats{10, 3, 1, 5, 1, dice("1x8"), 0}},
|
{"hobgoblin", 0, Mean, Stats{10, 3, 1, 5, 1, dice("1x8"), 0}},
|
||||||
{"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, dice("0x0"), 0}},
|
{"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, dice("0x0"), 0}},
|
||||||
{"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, dice("2x12/2x4"), 0}},
|
{"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, dice("2x12/2x4"), 0}},
|
||||||
@@ -248,6 +253,7 @@ func newGameData() *gameData {
|
|||||||
{Name: "plate mail", Prob: 5, Worth: 150},
|
{Name: "plate mail", Prob: 5, Worth: 150},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//nolint:dupl // distinct C data tables that merely share their shape
|
||||||
basePotInfo: [NumPotionTypes]ObjInfo{
|
basePotInfo: [NumPotionTypes]ObjInfo{
|
||||||
{Name: "confusion", Prob: 7, Worth: 5},
|
{Name: "confusion", Prob: 7, Worth: 5},
|
||||||
{Name: "hallucination", Prob: 8, Worth: 5},
|
{Name: "hallucination", Prob: 8, Worth: 5},
|
||||||
@@ -265,6 +271,7 @@ func newGameData() *gameData {
|
|||||||
{Name: "levitation", Prob: 6, Worth: 75},
|
{Name: "levitation", Prob: 6, Worth: 75},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//nolint:dupl // distinct C data tables that merely share their shape
|
||||||
baseRingInfo: [NumRingTypes]ObjInfo{
|
baseRingInfo: [NumRingTypes]ObjInfo{
|
||||||
{Name: "protection", Prob: 9, Worth: 400},
|
{Name: "protection", Prob: 9, Worth: 400},
|
||||||
{Name: "add strength", Prob: 9, Worth: 400},
|
{Name: "add strength", Prob: 9, Worth: 400},
|
||||||
@@ -316,6 +323,7 @@ func newGameData() *gameData {
|
|||||||
{}, // DO NOT REMOVE: fake entry for dragon's breath
|
{}, // DO NOT REMOVE: fake entry for dragon's breath
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//nolint:dupl // distinct C data tables that merely share their shape
|
||||||
baseWsInfo: [NumWandTypes]ObjInfo{
|
baseWsInfo: [NumWandTypes]ObjInfo{
|
||||||
{Name: "light", Prob: 12, Worth: 250},
|
{Name: "light", Prob: 12, Worth: 250},
|
||||||
{Name: "invisibility", Prob: 6, Worth: 5},
|
{Name: "invisibility", Prob: 6, Worth: 5},
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
// badcheck from init.c: every probability table must sum to exactly 100.
|
// badcheck from init.c: every probability table must sum to exactly 100.
|
||||||
func TestProbabilitiesSumTo100(t *testing.T) {
|
func TestProbabilitiesSumTo100(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
sum := func(info []ObjInfo) int {
|
sum := func(info []ObjInfo) int {
|
||||||
s := 0
|
s := 0
|
||||||
for _, oi := range info {
|
for _, oi := range info {
|
||||||
@@ -32,6 +35,8 @@ func TestProbabilitiesSumTo100(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInitProbsCumulative(t *testing.T) {
|
func TestInitProbsCumulative(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := New(Params{Seed: 1})
|
g := New(Params{Seed: 1})
|
||||||
|
|
||||||
last := g.Items.Potions[NumPotionTypes-1].Prob
|
last := g.Items.Potions[NumPotionTypes-1].Prob
|
||||||
@@ -47,6 +52,8 @@ func TestInitProbsCumulative(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGameRandomizesAppearances(t *testing.T) {
|
func TestNewGameRandomizesAppearances(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
g := New(Params{Seed: 12345})
|
g := New(Params{Seed: 12345})
|
||||||
|
|
||||||
checkPotionColors(t, g)
|
checkPotionColors(t, g)
|
||||||
@@ -112,6 +119,8 @@ func checkWandMaterials(t *testing.T, g *RogueGame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMonsterTable(t *testing.T) {
|
func TestMonsterTable(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
data := newGameData()
|
data := newGameData()
|
||||||
if data.monsterTable[0].Name != "aquator" || data.monsterTable[25].Name != "zombie" {
|
if data.monsterTable[0].Name != "aquator" || data.monsterTable[25].Name != "zombie" {
|
||||||
t.Error("monster table order broken")
|
t.Error("monster table order broken")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// testTerm is a headless Terminal for tests: rendering is a no-op and
|
// testTerm is a headless Terminal for tests: rendering is a no-op and
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -15,13 +16,15 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string {
|
|||||||
which := obj.Which
|
which := obj.Which
|
||||||
it := &g.Items
|
it := &g.Items
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case KindPotion:
|
case KindPotion:
|
||||||
g.nameit(&pb, obj, potionName, it.PotColors[which], &it.Potions[which], nullstr)
|
g.nameit(&pb, obj, potionName, it.PotColors[which], &it.Potions[which], nullstr)
|
||||||
case KindRing:
|
case KindRing:
|
||||||
g.nameit(&pb, obj, ringName, it.RingStones[which], &it.Rings[which], ringNum)
|
g.nameit(&pb, obj, ringName, it.RingStones[which], &it.Rings[which], ringNum)
|
||||||
case KindWand:
|
case KindWand:
|
||||||
g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr)
|
g.nameit(&pb, obj, it.WandType[which], it.WandMade[which],
|
||||||
|
&it.Sticks[which], chargeStr)
|
||||||
case KindScroll:
|
case KindScroll:
|
||||||
g.nameScroll(&pb, obj)
|
g.nameScroll(&pb, obj)
|
||||||
case KindFood:
|
case KindFood:
|
||||||
@@ -250,6 +253,7 @@ func (g *RogueGame) dropRing(obj *Object) {
|
|||||||
|
|
||||||
p.CurRing[hand] = nil
|
p.CurRing[hand] = nil
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.RingKind() {
|
switch obj.RingKind() {
|
||||||
case RingAddStrength:
|
case RingAddStrength:
|
||||||
g.changeStrength(-obj.Bonus)
|
g.changeStrength(-obj.Bonus)
|
||||||
@@ -346,6 +350,7 @@ func (g *RogueGame) newRingThing(cur *Object) {
|
|||||||
cur.Kind = KindRing
|
cur.Kind = KindRing
|
||||||
|
|
||||||
cur.Which = pickOne(g, g.Items.Rings[:])
|
cur.Which = pickOne(g, g.Items.Rings[:])
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch cur.RingKind() {
|
switch cur.RingKind() {
|
||||||
case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage:
|
case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage:
|
||||||
if cur.Bonus = g.rnd(3); cur.Bonus == 0 {
|
if cur.Bonus = g.rnd(3); cur.Bonus == 0 {
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const (
|
|||||||
Known // ISKNOW: player knows details about the object
|
Known // ISKNOW: player knows details about the object
|
||||||
Missile // ISMISL: object is a missile type
|
Missile // ISMISL: object is a missile type
|
||||||
Stackable // ISMANY: object comes in groups
|
Stackable // ISMANY: object comes in groups
|
||||||
WasFound // ISFOUND (objects): object has been seen (ISFOUND shares the bit with creatures)
|
WasFound // ISFOUND (objects): seen; bit shared with creatures
|
||||||
Protected // ISPROT: armor is permanently protected
|
Protected // ISPROT: armor is permanently protected
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,18 +140,18 @@ type CreatureFlags int32
|
|||||||
// Creature state bits (rogue.h).
|
// Creature state bits (rogue.h).
|
||||||
const (
|
const (
|
||||||
CanConfuse CreatureFlags = 0o000001 // CANHUH: creature can confuse
|
CanConfuse CreatureFlags = 0o000001 // CANHUH: creature can confuse
|
||||||
CanSeeInvisible CreatureFlags = 0o000002 // CANSEE: creature can see invisible creatures
|
CanSeeInvisible CreatureFlags = 0o000002 // CANSEE: can see invisible creatures
|
||||||
Blind CreatureFlags = 0o000004 // ISBLIND: creature is blind
|
Blind CreatureFlags = 0o000004 // ISBLIND: creature is blind
|
||||||
Cancelled CreatureFlags = 0o000010 // ISCANC: creature has special qualities cancelled
|
Cancelled CreatureFlags = 0o000010 // ISCANC: special qualities cancelled
|
||||||
Levitating CreatureFlags = 0o000010 // ISLEVIT: hero is levitating
|
Levitating CreatureFlags = 0o000010 // ISLEVIT: hero is levitating
|
||||||
Found CreatureFlags = 0o000020 // ISFOUND: creature has been seen
|
Found CreatureFlags = 0o000020 // ISFOUND: creature has been seen
|
||||||
Greedy CreatureFlags = 0o000040 // ISGREED: creature runs to protect gold
|
Greedy CreatureFlags = 0o000040 // ISGREED: creature runs to protect gold
|
||||||
Hasted CreatureFlags = 0o000100 // ISHASTE: creature has been hastened
|
Hasted CreatureFlags = 0o000100 // ISHASTE: creature has been hastened
|
||||||
Targeted CreatureFlags = 0o000200 // ISTARGET: creature is the target of an 'f' command
|
Targeted CreatureFlags = 0o000200 // ISTARGET: target of an 'f' command
|
||||||
Held CreatureFlags = 0o000400 // ISHELD: creature has been held
|
Held CreatureFlags = 0o000400 // ISHELD: creature has been held
|
||||||
Confused CreatureFlags = 0o001000 // ISHUH: creature is confused
|
Confused CreatureFlags = 0o001000 // ISHUH: creature is confused
|
||||||
Invisible CreatureFlags = 0o002000 // ISINVIS: creature is invisible
|
Invisible CreatureFlags = 0o002000 // ISINVIS: creature is invisible
|
||||||
Mean CreatureFlags = 0o004000 // ISMEAN: creature can wake when player enters room
|
Mean CreatureFlags = 0o004000 // ISMEAN: wakes when player enters room
|
||||||
Hallucinating CreatureFlags = 0o004000 // ISHALU: hero is on acid trip
|
Hallucinating CreatureFlags = 0o004000 // ISHALU: hero is on acid trip
|
||||||
Regenerates CreatureFlags = 0o010000 // ISREGEN: creature can regenerate
|
Regenerates CreatureFlags = 0o010000 // ISREGEN: creature can regenerate
|
||||||
Awake CreatureFlags = 0o020000 // ISRUN: creature is running at the player
|
Awake CreatureFlags = 0o020000 // ISRUN: creature is running at the player
|
||||||
@@ -392,7 +392,7 @@ type Stone struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CTRL maps a letter to its control character, as the C CTRL() macro.
|
// CTRL maps a letter to its control character, as the C CTRL() macro.
|
||||||
func CTRL(c byte) byte { return c & 0o37 }
|
func CTRL(c byte) byte { return c & 0o37 } //nolint:mnd // the C CTRL() mask
|
||||||
|
|
||||||
// distance returns the squared distance between two points (chase.c dist).
|
// distance returns the squared distance between two points (chase.c dist).
|
||||||
func distance(y1, x1, y2, x2 int) int {
|
func distance(y1, x1, y2, x2 int) int {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||||
package game
|
package game
|
||||||
|
|
||||||
// wizard.c — special wizard commands, some of which are also non-wizard
|
// wizard.c — special wizard commands, some of which are also non-wizard
|
||||||
@@ -25,6 +26,7 @@ func (g *RogueGame) createObj() {
|
|||||||
obj.Count = 1
|
obj.Count = 1
|
||||||
g.Msgs.Mpos = 0
|
g.Msgs.Mpos = 0
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case KindWeapon, KindArmor:
|
case KindWeapon, KindArmor:
|
||||||
g.createWeaponArmor(obj)
|
g.createWeaponArmor(obj)
|
||||||
@@ -82,6 +84,7 @@ func (g *RogueGame) createWeaponArmor(obj *Object) {
|
|||||||
// createRing sets up a wizard-created ring, prompting for a bonus on
|
// createRing sets up a wizard-created ring, prompting for a bonus on
|
||||||
// the bonus rings (the ring arm of wizard.c create_obj).
|
// the bonus rings (the ring arm of wizard.c create_obj).
|
||||||
func (g *RogueGame) createRing(obj *Object) {
|
func (g *RogueGame) createRing(obj *Object) {
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.RingKind() {
|
switch obj.RingKind() {
|
||||||
case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage:
|
case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage:
|
||||||
g.msg("blessing? (+,-,n)")
|
g.msg("blessing? (+,-,n)")
|
||||||
@@ -136,6 +139,7 @@ func (g *RogueGame) whatis(insist bool, kind ObjectKind) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||||
switch obj.Kind {
|
switch obj.Kind {
|
||||||
case KindScroll:
|
case KindScroll:
|
||||||
setKnow(obj, g.Items.Scrolls[:])
|
setKnow(obj, g.Items.Scrolls[:])
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ func namedKey(k tcell.Key) (byte, bool) {
|
|||||||
// motionKey translates the arrow and paging keys to Rogue's movement
|
// motionKey translates the arrow and paging keys to Rogue's movement
|
||||||
// letters (tcell.go ReadChar).
|
// letters (tcell.go ReadChar).
|
||||||
func motionKey(k tcell.Key) (byte, bool) {
|
func motionKey(k tcell.Key) (byte, bool) {
|
||||||
|
//nolint:exhaustive // translation table: all other keys fall through
|
||||||
switch k {
|
switch k {
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
return 'k', true
|
return 'k', true
|
||||||
@@ -153,19 +154,20 @@ func motionKey(k tcell.Key) (byte, bool) {
|
|||||||
// editingKey translates the editing and control keys to their C0 codes
|
// editingKey translates the editing and control keys to their C0 codes
|
||||||
// (tcell.go ReadChar).
|
// (tcell.go ReadChar).
|
||||||
func editingKey(k tcell.Key) (byte, bool) {
|
func editingKey(k tcell.Key) (byte, bool) {
|
||||||
|
//nolint:exhaustive // translation table: all other keys fall through
|
||||||
switch k {
|
switch k {
|
||||||
case tcell.KeyEnter:
|
case tcell.KeyEnter:
|
||||||
return '\n', true
|
return '\n', true
|
||||||
case tcell.KeyEscape:
|
case tcell.KeyEscape:
|
||||||
return game.Escape, true
|
return game.Escape, true
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||||
return 8, true
|
return '\b', true
|
||||||
case tcell.KeyDelete:
|
case tcell.KeyDelete:
|
||||||
return 0x7f, true
|
return '\x7f', true
|
||||||
case tcell.KeyTab:
|
case tcell.KeyTab:
|
||||||
return '\t', true
|
return '\t', true
|
||||||
case tcell.KeyCtrlC:
|
case tcell.KeyCtrlC:
|
||||||
return 3, true
|
return '\x03', true
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, false
|
return 0, false
|
||||||
@@ -188,8 +190,8 @@ func (t *Tcell) ShellEscape() {
|
|||||||
"[Entering shell; exit to return to the game]")
|
"[Entering shell; exit to return to the game]")
|
||||||
|
|
||||||
// The shell session has no deadline by design; Background context.
|
// The shell session has no deadline by design; Background context.
|
||||||
cmd := exec.CommandContext(context.Background(), //nolint:gosec // G204: the user's own $SHELL
|
//nolint:gosec // G204: the user's own $SHELL
|
||||||
shell)
|
cmd := exec.CommandContext(context.Background(), shell)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|||||||
Reference in New Issue
Block a user