Update golangci-lint to v2.12.2 with canonical config #1
@@ -1,5 +1,9 @@
|
||||
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:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
@@ -14,14 +18,7 @@ linters:
|
||||
- wsl # Deprecated, replaced by wsl_v5
|
||||
- wrapcheck # Too verbose for internal packages
|
||||
- varnamelen # Short names like db, id are idiomatic Go
|
||||
# Repo-specific exceptions approved by sneak (2026-07-06)
|
||||
- paralleltest # Requires t.Parallel() in every test
|
||||
# Approved by sneak 2026-07-07
|
||||
- testpackage # Tests use internal package game to reach unexported state
|
||||
- exhaustive # C-faithful switches handle only the cases C handled
|
||||
- mnd # C-faithful gameplay literals; naming them hurts C-greppability
|
||||
|
||||
linters-settings:
|
||||
settings:
|
||||
lll:
|
||||
line-length: 88
|
||||
funlen:
|
||||
@@ -33,6 +30,5 @@ linters-settings:
|
||||
threshold: 100
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
13
TODO.md
13
TODO.md
@@ -34,6 +34,19 @@ wizard commands).
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
@@ -128,6 +128,7 @@ func chooseSeed(wizard bool) int32 {
|
||||
|
||||
// The C game computed `lowtime + getpid()` in int; the truncation to
|
||||
// 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) +
|
||||
int32(os.Getpid()&0x7fffffff)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
// desire itself. shot means a dragon breathed flame instead of moving
|
||||
// (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
|
||||
|
||||
for {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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) {
|
||||
it := &g.Items
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.Kind {
|
||||
case KindRing:
|
||||
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
|
||||
// guess; ok is false when the item is already identified (command.c
|
||||
// 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
|
||||
|
||||
if op != nil {
|
||||
|
||||
@@ -7,7 +7,9 @@ type Creature struct {
|
||||
Type byte // what it is: 'A'..'Z' for monsters, '@' for the player
|
||||
Disguise byte // what mimic looks like
|
||||
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,
|
||||
// room gold, another monster's pos).
|
||||
Dest *Coord
|
||||
Flags CreatureFlags
|
||||
Stats Stats
|
||||
Room *Room // current room for thing
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
import "testing"
|
||||
@@ -6,6 +7,8 @@ import "testing"
|
||||
// including its junk-tolerant edges: the bestiary placeholder "%%%x0" and
|
||||
// the flytrap reset "000x0" both mean a single 0x0 attack.
|
||||
func TestParseDice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in 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
|
||||
// every creature and weapon actually swings.
|
||||
func TestTablesHaveDice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := newGameData()
|
||||
for i, m := range data.monsterTable {
|
||||
if len(m.Stats.Dmg) == 0 {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import "testing"
|
||||
@@ -37,6 +38,8 @@ func setInput(t *testing.T, g *RogueGame, input ...byte) {
|
||||
}
|
||||
|
||||
func TestQuaffHealingPotion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGameInput(t)
|
||||
pot := newObject()
|
||||
pot.Kind = KindPotion
|
||||
@@ -61,6 +64,8 @@ func TestQuaffHealingPotion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGameInput(t)
|
||||
pot := newObject()
|
||||
pot.Kind = KindPotion
|
||||
@@ -88,6 +93,8 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadEnchantArmor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGameInput(t)
|
||||
scr := newObject()
|
||||
scr.Kind = KindScroll
|
||||
@@ -105,6 +112,7 @@ func TestReadEnchantArmor(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Note: this must not use a greedy monster ('O' orc, ISGREED): the C
|
||||
// wake_monster gold-guarding check has no ISHELD guard, so the
|
||||
// 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
|
||||
// performs, ending up both held and running again.
|
||||
func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGameInput(t)
|
||||
tp := spawnAdjacent(g, 'O')
|
||||
tp.Flags.Set(Awake)
|
||||
@@ -158,6 +168,8 @@ func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestZapSlowMonster(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGameInput(t)
|
||||
tp := spawnAdjacent(g, 'Z')
|
||||
stick := newObject()
|
||||
@@ -183,6 +195,8 @@ func TestZapSlowMonster(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := New(Params{Seed: 1})
|
||||
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
|
||||
|
||||
import "strconv"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import "testing"
|
||||
@@ -25,6 +26,8 @@ func spawnAdjacent(g *RogueGame, typ byte) *Monster {
|
||||
}
|
||||
|
||||
func TestRollEmParsesMultiAttackDice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 42)
|
||||
att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: dice("1x4/1x4/1x4")}}
|
||||
def := &Creature{Stats: Stats{ArmorClass: 10, HP: 1000}}
|
||||
@@ -43,6 +46,8 @@ func TestRollEmParsesMultiAttackDice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFightKillsMonster(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 7)
|
||||
tp := spawnAdjacent(g, 'B') // bat: 1 hit die
|
||||
tp.Stats.HP = 1
|
||||
@@ -64,6 +69,8 @@ func TestFightKillsMonster(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttackHurtsPlayer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 9)
|
||||
tp := spawnAdjacent(g, 'T') // troll: 1x8/1x8/2x6
|
||||
tp.Stats.Lvl = 20 // always hits
|
||||
@@ -80,6 +87,8 @@ func TestAttackHurtsPlayer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunnersChaseHero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 3)
|
||||
// Place a hobgoblin a few squares away in the hero's room and set it
|
||||
// running at the hero.
|
||||
@@ -104,6 +113,8 @@ func TestRunnersChaseHero(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKilledLeprechaunDropsGoldViaFall(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 21)
|
||||
tp := spawnAdjacent(g, 'L')
|
||||
tp.Stats.HP = 0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
import "strings"
|
||||
@@ -169,7 +170,8 @@ func (g *RogueGame) initProbs() {
|
||||
sumProbs(g.Items.Scrolls[:])
|
||||
sumProbs(g.Items.Rings[:])
|
||||
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[:])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
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
|
||||
// squares, waking and disguises for monsters. skip means the square is
|
||||
// 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
|
||||
|
||||
switch {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
// passage runner (reported with the new deltas); anything else completes
|
||||
// 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 {
|
||||
case ' ', '|', '-':
|
||||
if turn, ndy, ndx := g.passageTurn(dy, dx); turn {
|
||||
@@ -343,7 +346,8 @@ func (g *RogueGame) trapMystery(Coord) {
|
||||
case 0:
|
||||
g.msg("you are suddenly in a parallel dimension")
|
||||
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:
|
||||
g.msg("you feel a sting in the side of your neck")
|
||||
case 3:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
@@ -35,6 +36,8 @@ func renderMap(g *RogueGame) string {
|
||||
}
|
||||
|
||||
func TestNewLevelInvariants(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, seed := range []int32{1, 12345, 2026, 99999} {
|
||||
g := genLevel(t, seed)
|
||||
|
||||
@@ -142,6 +145,8 @@ func checkStartingKit(t *testing.T, g *RogueGame, seed int32) {
|
||||
}
|
||||
|
||||
func TestNewLevelDeterministic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := 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 —
|
||||
// mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep.
|
||||
func TestDeeperLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, seed := range []int32{7, 42, 1000, 31337} {
|
||||
g := New(Params{Seed: seed})
|
||||
for depth := 1; depth <= 30; depth++ {
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
|
||||
// Glyph returns the map/display character for this kind of object.
|
||||
func (k ObjectKind) Glyph() byte {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch k {
|
||||
case KindPotion:
|
||||
return Potion
|
||||
@@ -64,6 +65,7 @@ func (k ObjectKind) Glyph() byte {
|
||||
|
||||
// String names the category the way the C type_name() did.
|
||||
func (k ObjectKind) String() string {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch k {
|
||||
case KindPotion:
|
||||
return potionName
|
||||
@@ -118,6 +120,7 @@ func (k ObjectKind) MergesInPack() bool {
|
||||
// stringRest names the remaining kinds, including the ring-or-stick
|
||||
// prompt pseudo-kind (the tail of the C type_name switch).
|
||||
func (k ObjectKind) stringRest() string {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch k {
|
||||
case KindWand:
|
||||
return "wand or staff"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
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/
|
||||
// get_inv_t/get_str dispatch).
|
||||
func (g *RogueGame) getOpt(op *optDesc) int {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch op.kind {
|
||||
case optBool:
|
||||
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
|
||||
// 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.
|
||||
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 {
|
||||
case op.Kind.MergesInPack():
|
||||
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
|
||||
// (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
|
||||
|
||||
lp := i
|
||||
@@ -333,6 +337,7 @@ func (g *RogueGame) pickUp(ch byte) {
|
||||
// everything, KindCallable takes anything nameable (not food, not the
|
||||
// amulet), KindRingOrStick takes rings and wands.
|
||||
func matchesFilter(kind ObjectKind, item *Object) bool {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch kind {
|
||||
case KindNone:
|
||||
return true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
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).
|
||||
func (g *RogueGame) isMagic(o *Object) bool {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch o.Kind {
|
||||
case KindArmor:
|
||||
return o.Flags.Has(Protected) || o.ArmorClass != g.data.aClass[o.Which]
|
||||
|
||||
@@ -33,6 +33,7 @@ func (g *RogueGame) ringOn() {
|
||||
p.CurRing[ring] = obj
|
||||
|
||||
// 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() {
|
||||
case RingAddStrength:
|
||||
g.changeStrength(obj.Bonus)
|
||||
@@ -171,6 +172,7 @@ func ringNum(_ *RogueGame, obj *Object) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.RingKind() {
|
||||
case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity:
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -98,8 +99,10 @@ func (g *RogueGame) totalWinner() {
|
||||
}
|
||||
|
||||
g.standend()
|
||||
g.scr.Std.MvAddStr(10, 0, "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(10, 0,
|
||||
"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.mvaddstr(NumLines-1, 0, "--Press space to continue--")
|
||||
g.refresh()
|
||||
@@ -132,6 +135,7 @@ func (g *RogueGame) objectWorth(obj *Object) int {
|
||||
it := &g.Items
|
||||
worth := 0
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.Kind {
|
||||
case KindFood:
|
||||
worth = 2 * obj.Count
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
import "testing"
|
||||
@@ -6,6 +7,8 @@ import "testing"
|
||||
// (seed = seed*11109+13849; rnd(range) = abs(RN) % range) compiled and run
|
||||
// on this machine. They lock in seed compatibility with the C game.
|
||||
func TestRndMatchesCImplementation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
golden := map[int32][]int{
|
||||
1: {0, 30, 79, 7, 87, 1, 23, 7, 57, 98},
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
golden := map[int32][]int{
|
||||
1: {6, 8, 10},
|
||||
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
|
||||
// short-circuits before evaluating RN, and BEFORE/AFTER depend on it.
|
||||
func TestRndZeroDoesNotStep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := &Rng{Seed: 42}
|
||||
if got := r.Rnd(0); got != 0 {
|
||||
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
|
||||
// constants rely on this.
|
||||
func TestSpreadSmallValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := &RogueGame{Rng: &Rng{Seed: 7}}
|
||||
if got := g.spread(1); got != 1 {
|
||||
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
|
||||
|
||||
// rooms.c — create the layout for the new level.
|
||||
@@ -330,7 +331,9 @@ func floorChar(rp *Room) byte {
|
||||
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
|
||||
if !pickroom {
|
||||
compchar = floorChar(rp)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
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
|
||||
// '>' command through the real turn loop, then checks the level changed.
|
||||
func TestRunDownStairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
// '>' is a free action (After=false), so it is followed by a paying
|
||||
// rest ('.') to end the command() call; without a paying action the
|
||||
// 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
|
||||
// display is exercised through score() directly.
|
||||
func TestScoreRendersList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := New(Params{Seed: 1, Term: &testTerm{}})
|
||||
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
|
||||
// seed keeps it deterministic.
|
||||
func TestDeepPlaythrough(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := New(Params{Seed: 4242, Wizard: true, Term: &testTerm{}})
|
||||
g.startLevel()
|
||||
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 the fixed seeds keep it deterministic; the point is to surface panics.
|
||||
func TestTurnLoopCrashSweep(t *testing.T) {
|
||||
t.Parallel()
|
||||
// A generous mix of movement, search, and rest. The spaces between
|
||||
// commands double as answers to any --More-- prompt (wait_for eats
|
||||
// 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
|
||||
|
||||
import (
|
||||
@@ -686,6 +687,7 @@ func Restore(path string, params Params) (*RogueGame, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = f.Close() }() // read-only handle
|
||||
|
||||
var st SaveState
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import (
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSaveRestoreRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 4242)
|
||||
// Dirty up some state so the round trip is meaningful.
|
||||
g.Player.Purse = 123
|
||||
@@ -134,6 +137,8 @@ func checkEquipmentAliasing(t *testing.T, g, h *RogueGame) {
|
||||
}
|
||||
|
||||
func TestRestoreRejectsWrongVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := mkGame(t, 1)
|
||||
path := filepath.Join(t.TempDir(), "rogue.save")
|
||||
st := g.snapshot()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import (
|
||||
@@ -36,6 +37,7 @@ func (g *RogueGame) rdScore() []ScoreEnt {
|
||||
if err != nil {
|
||||
return topTen
|
||||
}
|
||||
|
||||
defer func() { _ = f.Close() }() // read-only handle
|
||||
|
||||
var onDisk []ScoreEnt
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// scrolls.c — read a scroll and let it happen.
|
||||
@@ -148,7 +149,8 @@ func (g *RogueGame) createMonsterSpot() (Coord, bool) {
|
||||
// Or anything else nasty
|
||||
if ch := g.Level.VisibleChar(y, x); stepOk(ch) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
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
|
||||
// of the C game (testdata/README.md).
|
||||
func TestSeedCompatItemTables(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
golden, err := os.ReadFile("testdata/item_tables.golden")
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
import "fmt"
|
||||
@@ -272,6 +273,7 @@ func slowTarget(tp *Monster) {
|
||||
func (g *RogueGame) zapBolt(obj *Object) bool {
|
||||
var name string
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.WandKind() {
|
||||
case WandLightning:
|
||||
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
|
||||
// monster arm of the fire_bolt loop). It reports whether the bolt was
|
||||
// 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)
|
||||
if !g.saveThrow(VsMagic, &tp.Stats) {
|
||||
bolt.Pos = pos
|
||||
@@ -506,6 +510,7 @@ func (g *RogueGame) fixStick(cur *Object) {
|
||||
|
||||
cur.HurlDmg = dice("1x1")
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch cur.WandKind() {
|
||||
case WandLight:
|
||||
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
|
||||
|
||||
// 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
|
||||
func newGameData() *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{
|
||||
8, // LEATHER
|
||||
@@ -205,7 +209,8 @@ func newGameData() *gameData {
|
||||
{"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}},
|
||||
{"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}},
|
||||
{"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}},
|
||||
@@ -248,6 +253,7 @@ func newGameData() *gameData {
|
||||
{Name: "plate mail", Prob: 5, Worth: 150},
|
||||
},
|
||||
|
||||
//nolint:dupl // distinct C data tables that merely share their shape
|
||||
basePotInfo: [NumPotionTypes]ObjInfo{
|
||||
{Name: "confusion", Prob: 7, Worth: 5},
|
||||
{Name: "hallucination", Prob: 8, Worth: 5},
|
||||
@@ -265,6 +271,7 @@ func newGameData() *gameData {
|
||||
{Name: "levitation", Prob: 6, Worth: 75},
|
||||
},
|
||||
|
||||
//nolint:dupl // distinct C data tables that merely share their shape
|
||||
baseRingInfo: [NumRingTypes]ObjInfo{
|
||||
{Name: "protection", 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
|
||||
},
|
||||
|
||||
//nolint:dupl // distinct C data tables that merely share their shape
|
||||
baseWsInfo: [NumWandTypes]ObjInfo{
|
||||
{Name: "light", Prob: 12, Worth: 250},
|
||||
{Name: "invisibility", Prob: 6, Worth: 5},
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
import "testing"
|
||||
|
||||
// badcheck from init.c: every probability table must sum to exactly 100.
|
||||
func TestProbabilitiesSumTo100(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sum := func(info []ObjInfo) int {
|
||||
s := 0
|
||||
for _, oi := range info {
|
||||
@@ -32,6 +35,8 @@ func TestProbabilitiesSumTo100(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitProbsCumulative(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := New(Params{Seed: 1})
|
||||
|
||||
last := g.Items.Potions[NumPotionTypes-1].Prob
|
||||
@@ -47,6 +52,8 @@ func TestInitProbsCumulative(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewGameRandomizesAppearances(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := New(Params{Seed: 12345})
|
||||
|
||||
checkPotionColors(t, g)
|
||||
@@ -112,6 +119,8 @@ func checkWandMaterials(t *testing.T, g *RogueGame) {
|
||||
}
|
||||
|
||||
func TestMonsterTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := newGameData()
|
||||
if data.monsterTable[0].Name != "aquator" || data.monsterTable[25].Name != "zombie" {
|
||||
t.Error("monster table order broken")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
@@ -15,13 +16,15 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string {
|
||||
which := obj.Which
|
||||
it := &g.Items
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.Kind {
|
||||
case KindPotion:
|
||||
g.nameit(&pb, obj, potionName, it.PotColors[which], &it.Potions[which], nullstr)
|
||||
case KindRing:
|
||||
g.nameit(&pb, obj, ringName, it.RingStones[which], &it.Rings[which], ringNum)
|
||||
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:
|
||||
g.nameScroll(&pb, obj)
|
||||
case KindFood:
|
||||
@@ -250,6 +253,7 @@ func (g *RogueGame) dropRing(obj *Object) {
|
||||
|
||||
p.CurRing[hand] = nil
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.RingKind() {
|
||||
case RingAddStrength:
|
||||
g.changeStrength(-obj.Bonus)
|
||||
@@ -346,6 +350,7 @@ func (g *RogueGame) newRingThing(cur *Object) {
|
||||
cur.Kind = KindRing
|
||||
|
||||
cur.Which = pickOne(g, g.Items.Rings[:])
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch cur.RingKind() {
|
||||
case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage:
|
||||
if cur.Bonus = g.rnd(3); cur.Bonus == 0 {
|
||||
|
||||
@@ -118,7 +118,7 @@ const (
|
||||
Known // ISKNOW: player knows details about the object
|
||||
Missile // ISMISL: object is a missile type
|
||||
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
|
||||
)
|
||||
|
||||
@@ -140,18 +140,18 @@ type CreatureFlags int32
|
||||
// Creature state bits (rogue.h).
|
||||
const (
|
||||
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
|
||||
Cancelled CreatureFlags = 0o000010 // ISCANC: creature has special qualities cancelled
|
||||
Cancelled CreatureFlags = 0o000010 // ISCANC: special qualities cancelled
|
||||
Levitating CreatureFlags = 0o000010 // ISLEVIT: hero is levitating
|
||||
Found CreatureFlags = 0o000020 // ISFOUND: creature has been seen
|
||||
Greedy CreatureFlags = 0o000040 // ISGREED: creature runs to protect gold
|
||||
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
|
||||
Confused CreatureFlags = 0o001000 // ISHUH: creature is confused
|
||||
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
|
||||
Regenerates CreatureFlags = 0o010000 // ISREGEN: creature can regenerate
|
||||
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.
|
||||
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).
|
||||
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
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
||||
package game
|
||||
|
||||
// wizard.c — special wizard commands, some of which are also non-wizard
|
||||
@@ -25,6 +26,7 @@ func (g *RogueGame) createObj() {
|
||||
obj.Count = 1
|
||||
g.Msgs.Mpos = 0
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.Kind {
|
||||
case KindWeapon, KindArmor:
|
||||
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
|
||||
// the bonus rings (the ring arm of wizard.c create_obj).
|
||||
func (g *RogueGame) createRing(obj *Object) {
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.RingKind() {
|
||||
case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage:
|
||||
g.msg("blessing? (+,-,n)")
|
||||
@@ -136,6 +139,7 @@ func (g *RogueGame) whatis(insist bool, kind ObjectKind) {
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07)
|
||||
switch obj.Kind {
|
||||
case KindScroll:
|
||||
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
|
||||
// letters (tcell.go ReadChar).
|
||||
func motionKey(k tcell.Key) (byte, bool) {
|
||||
//nolint:exhaustive // translation table: all other keys fall through
|
||||
switch k {
|
||||
case tcell.KeyUp:
|
||||
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
|
||||
// (tcell.go ReadChar).
|
||||
func editingKey(k tcell.Key) (byte, bool) {
|
||||
//nolint:exhaustive // translation table: all other keys fall through
|
||||
switch k {
|
||||
case tcell.KeyEnter:
|
||||
return '\n', true
|
||||
case tcell.KeyEscape:
|
||||
return game.Escape, true
|
||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||
return 8, true
|
||||
return '\b', true
|
||||
case tcell.KeyDelete:
|
||||
return 0x7f, true
|
||||
return '\x7f', true
|
||||
case tcell.KeyTab:
|
||||
return '\t', true
|
||||
case tcell.KeyCtrlC:
|
||||
return 3, true
|
||||
return '\x03', true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
@@ -188,8 +190,8 @@ func (t *Tcell) ShellEscape() {
|
||||
"[Entering shell; exit to return to the game]")
|
||||
|
||||
// The shell session has no deadline by design; Background context.
|
||||
cmd := exec.CommandContext(context.Background(), //nolint:gosec // G204: the user's own $SHELL
|
||||
shell)
|
||||
//nolint:gosec // G204: the user's own $SHELL
|
||||
cmd := exec.CommandContext(context.Background(), shell)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
Reference in New Issue
Block a user