Move all package-level vars into gameData; finish lint adoption

gochecknoglobals: all 37 package-level tables consolidated into the
gameData struct (game/tables.go), built by newGameData() and carried
on RogueGame as g.data (set in NewGame and Restore). ObjectKind
Glyph()/objectKindForGlyph are now switches; the table-reading subtype
Stringer methods are gone; isMagic is a RogueGame method.

goconst: repeated words named (potionName/scrollName/ringName/goldName
in object.go, wandName/staffName in sticks.go, ripWall in tables.go).

exhaustive, testpackage: disabled in .golangci.yml with sneak's
approval (2026-07-07).

Also reverts misspell's silent corruption of the "ther" scroll-name
syllable (it had become "there", changing generated scroll names vs C).

Remaining red: cyclop/gocognit/nestif until refactor step 7; mnd
awaits a ruling. TODO.md rotated; MEMORY.md lint notes updated.
This commit is contained in:
2026-07-07 02:10:58 +02:00
parent 50afbec8e3
commit a8feb6c05d
28 changed files with 796 additions and 711 deletions

View File

@@ -16,6 +16,9 @@ linters:
- 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
linters-settings:
lll:

View File

@@ -20,7 +20,10 @@ The .golangci.yml is the house standard and may only be modified with
sneak's explicit permission. To disable a linter, ask, explaining what
the linter does; he approves specific exceptions, which are recorded
in the config's "Repo-specific exceptions" block with the approval
date. Approved so far: paralleltest (2026-07-06). Line-level //nolint
date. Approved so far: paralleltest (2026-07-06); testpackage and
exhaustive (2026-07-07). Complexity linters (cyclop, gocognit, nestif)
stay enabled and red until refactor step 7 fixes the findings, per
sneak 2026-07-07. mnd findings await a ruling. Line-level //nolint
with a reason is used sparingly for C-faithfulness (e.g. the authentic
"missle" message spellings) and provably-safe gosec conversions; each
needs a justifying comment.

84
TODO.md
View File

@@ -29,22 +29,34 @@ Refactor ground rules:
# Next Step
Finish adopting the house Go linting standards. Done so far (branch
refactor/lint-adoption): .golangci.yml copied verbatim from the
prompts repo (plus the sneak-approved paralleltest exception,
2026-07-06); ~1,500 findings fixed (autofix formatting sweep, errcheck/
err113/noinlineerr error handling, forbidigo, funcorder, recvcheck
pointer receivers, goprintffuncname renames msg helpers to *f, revive
doc comments, gocritic switch rewrites, gosec real fixes plus justified
nolints, unparam signature tightening, C-faithful "missle" spellings
restored after misspell autofix changed game text). Remaining findings
are all in linters awaiting sneak's exception decision: mnd (288),
gochecknoglobals (37), cyclop (36), nestif (30), gocognit (23),
exhaustive (22), goconst (16), testpackage (9). Blocked on that
decision; either disable with approval or scope the fixes.
Refactor step 4: method renames, movement/world subsystem
(doMove→moveHero, beTrapped→springTrap, rndmove→randomStep,
doRooms/doPassages/doMaze→digRooms/digPassages/digMaze,
chgStr→changeStrength, ...); remove the goto/label flows in doMove,
dispatch, and saveGame in favor of loops and helpers.
# Completed Steps
- 2026-07-07 Lint adoption finished (refactor/no-package-globals): all
37 package-level vars moved into `gameData` (built by `newGameData`,
hung on RogueGame as `g.data`, set in NewGame and Restore); ObjectKind
Glyph()/objectKindForGlyph became switches; the table-reading subtype
Stringers were removed; isMagic became a RogueGame method; goconst
fixed with named word constants (potionName, goldName, staffName,
ripWall, ...); testpackage and exhaustive disabled in .golangci.yml
with sneak's approval (2026-07-07); misspell's corruption of the
"ther" scroll syllable reverted. Remaining red: cyclop (36), nestif
(30), gocognit (23) stay until step 7 fixes them per sneak's ruling,
and mnd (289) still awaits a ruling.
- 2026-07-06 Lint adoption bulk (refactor/lint-adoption, 5ba9fe8):
.golangci.yml copied verbatim from the prompts repo (plus the
sneak-approved paralleltest exception, 2026-07-06); ~1,500 findings
fixed (autofix formatting sweep, errcheck/err113/noinlineerr error
handling, forbidigo, funcorder, recvcheck pointer receivers,
goprintffuncname renames msg helpers to *f, revive doc comments,
gocritic switch rewrites, gosec real fixes plus justified nolints,
unparam signature tightening, C-faithful "missle" spellings restored
after misspell autofix changed game text).
- 2026-07-06 Module base path updated to git.eeqj.de/sneak/rgoue
(go.mod, term/ and cmd/ imports, ARCHITECTURE.md, version string).
- 2026-07-06 Refactor step 3 (refactor/object-fields): Object.Arm split
@@ -86,45 +98,41 @@ decision; either disable with approval or scope the fixes.
# Future Steps
1. Refactor step 4: method renames, movement/world subsystem
(doMove→moveHero, beTrapped→springTrap, rndmove→randomStep,
doRooms/doPassages/doMaze→digRooms/digPassages/digMaze,
chgStr→changeStrength, ...); remove the goto/label flows in doMove,
dispatch, and saveGame in favor of loops and helpers.
2. Refactor step 5: method renames, items/combat/UI subsystems
1. Refactor step 5: method renames, items/combat/UI subsystems
(invName→inventoryName, rollEm→rollAttacks, doPot→applyPotionFuse,
getItem→promptPackItem returning (obj, ok), getDir→promptDirection);
int status codes (attack returning -1) become named results. Two or
three commits, one subsystem each.
3. Refactor step 6: extract types from the god object — MessageLine
2. Refactor step 6: extract types from the god object — MessageLine
owns the msg/addmsg/endmsg machinery; pack/inventory operations move
onto *Player; monster/object list management and map queries
consolidate onto *Level; RogueGame keeps turn orchestration and
cross-system effects only.
4. Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap
3. Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap
switches become per-kind handler tables of small named methods,
keeping effect order and RNG call sequence identical.
5. Refactor step 8: constructor and style pass per the house
keeping effect order and RNG call sequence identical. This step also
clears the outstanding cyclop/gocognit/nestif lint findings.
4. Refactor step 8: constructor and style pass per the house
styleguide — game.New(game.Params{...}) replacing NewGame(Config);
replace the gameEnd panic unwind with error-based turn results where
feasible; 77-column wrap sweep.
6. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the
5. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the
post-refactor names; add the C name → Go name rename table.
7. Playtest hardening pass: play several full games with the tcell
binary and extend run_test.go to script a deeper multi-level
playthrough (descend past level 5, use potions, scrolls, zapping,
save/restore). Fix any panics, message mismatches, or divergences
from the C behavior that this uncovers, with regression tests.
8. Verify the seed-compatibility claim against the C reference on
c-master: same seed, same dungeon, same item tables, for several
seeds.
9. Broaden unit test coverage where playtesting finds thin spots
(rings, sticks, wizard commands).
10. Tag a release once a full game (Amulet retrieval and score entry)
completes without defects.
11. Full-terminal-size support (deferred by explicit decision
6. Playtest hardening pass: play several full games with the tcell
binary and extend run_test.go to script a deeper multi-level
playthrough (descend past level 5, use potions, scrolls, zapping,
save/restore). Fix any panics, message mismatches, or divergences
from the C behavior that this uncovers, with regression tests.
7. Verify the seed-compatibility claim against the C reference on
c-master: same seed, same dungeon, same item tables, for several
seeds.
8. Broaden unit test coverage where playtesting finds thin spots
(rings, sticks, wizard commands).
9. Tag a release once a full game (Amulet retrieval and score entry)
completes without defects.
10. Full-terminal-size support (deferred by explicit decision
2026-07-06): per-game dungeon dimensions instead of the 80x24
constants; open design questions are resize policy, gameplay
tuning at larger sizes, and a --classic 80x24 mode.
12. Note: this repo is exempt from the standard policy scaffold. Do not
11. Note: this repo is exempt from the standard policy scaffold. Do not
add Makefile, Dockerfile, or REPO_POLICIES.md.

View File

@@ -373,9 +373,9 @@ over:
case g.Level.Char(g.Delta.Y, g.Delta.X) != Trap:
g.msg("no trap there")
case p.On(Hallucinating):
g.msg("%s", trName[g.rnd(NumTrapTypes)])
g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)])
default:
g.msg("%s", trName[*fp&FTrapMask])
g.msg("%s", g.data.trName[*fp&FTrapMask])
fp.Set(FSeen)
}
}
@@ -540,9 +540,9 @@ func (g *RogueGame) search() {
}
if p.On(Hallucinating) {
g.msg("%s", trName[g.rnd(NumTrapTypes)])
g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)])
} else {
g.msg("%s", trName[*fp&FTrapMask])
g.msg("%s", g.data.trName[*fp&FTrapMask])
fp.Set(FSeen)
}
@@ -584,7 +584,7 @@ func (g *RogueGame) help() {
if helpch != '*' {
g.move(0, 0)
for _, strp := range helpStr {
for _, strp := range g.data.helpStr {
if strp.Ch == helpch {
g.Msgs.LowerMsg = true
g.msg("%s%s", unctrl(strp.Ch), strp.Desc)
@@ -602,7 +602,7 @@ func (g *RogueGame) help() {
// command mode.
numprint := 0
for _, strp := range helpStr {
for _, strp := range g.data.helpStr {
if strp.Print {
numprint++
}
@@ -622,7 +622,7 @@ func (g *RogueGame) help() {
cnt := 0
for _, strp := range helpStr {
for _, strp := range g.data.helpStr {
if !strp.Print {
continue
}
@@ -652,28 +652,6 @@ func (g *RogueGame) help() {
g.refresh()
}
// identList is command.c's static ident_list.
var identList = []helpEntry{
{'|', "wall of a room", false},
{'-', "wall of a room", false},
{Gold, "gold", false},
{Stairs, "a staircase", false},
{Door, "door", false},
{Floor, "room floor", false},
{PlayerCh, "you", false},
{Passage, "passage", false},
{Trap, "trap", false},
{Potion, "potion", false},
{Scroll, "scroll", false},
{Food, "food", false},
{Weapon, "weapon", false},
{' ', "solid rock", false},
{Armor, "armor", false},
{Amulet, "the Amulet of Yendor", false},
{Ring, "ring", false},
{Stick, "wand or staff", false},
}
// identify tells the player what a certain thing is (command.c identify).
func (g *RogueGame) identify() {
g.msg("what do you want identified? ")
@@ -692,7 +670,7 @@ func (g *RogueGame) identify() {
} else {
str = "unknown character"
for _, hp := range identList {
for _, hp := range g.data.identList {
if hp.Ch == ch {
str = hp.Desc

View File

@@ -32,15 +32,16 @@ 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) {
for i, m := range monsterTable {
data := newGameData()
for i, m := range data.monsterTable {
if len(m.Stats.Dmg) == 0 {
t.Errorf("monster %c (%s) has no attacks", 'A'+i, m.Name)
}
}
for w, iw := range initWeaps {
for w, iw := range data.initWeaps {
if len(iw.dam) == 0 || len(iw.hrl) == 0 {
t.Errorf("weapon %v has empty dice", WeaponKind(w))
t.Errorf("weapon %d has empty dice", w)
}
}
}

View File

@@ -4,43 +4,6 @@ import "strconv"
// fight.c — all the fighting gets done here.
// hNames are the strings for hitting; the first four are used when the
// player strikes, the second four for monsters (fight.c h_names).
var hNames = [8]string{
" scored an excellent hit on ",
" hit ",
" have injured ",
" swing and hit ",
" scored an excellent hit on ",
" hit ",
" has injured ",
" swings and hits ",
}
// mNames are the strings for missing (fight.c m_names).
var mNames = [8]string{
" miss",
" swing and miss",
" barely miss",
" don't hit",
" misses",
" swings and misses",
" barely misses",
" doesn't hit",
}
// strPlus adjusts hit probabilities due to strength (fight.c str_plus).
var strPlus = [32]int{
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
}
// addDam adjusts damage done due to strength (fight.c add_dam).
var addDam = [32]int{
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3,
3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
}
// setMname returns the monster name for the given monster (fight.c
// set_mname).
func (g *RogueGame) setMname(tp *Monster) string {
@@ -254,7 +217,7 @@ func (g *RogueGame) attack(mp *Monster) int {
p.Stats.Exp = 0
p.Stats.Lvl = 1
} else {
p.Stats.Exp = eLevels[p.Stats.Lvl-1] + 1
p.Stats.Exp = g.data.eLevels[p.Stats.Lvl-1] + 1
}
fewer = g.roll(1, 10)
@@ -314,7 +277,7 @@ func (g *RogueGame) attack(mp *Monster) int {
for _, obj := range p.Pack {
if obj != p.CurArmor && obj != p.CurWeapon &&
obj != p.CurRing[Left] && obj != p.CurRing[Right] &&
obj.isMagic() {
g.isMagic(obj) {
if nobj++; g.rnd(nobj) == 0 {
steal = obj
}
@@ -436,10 +399,10 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool
didHit := false
for _, atk := range attacks {
if g.swing(att.Lvl, defArm, hplus+strPlus[att.Str]) {
if g.swing(att.Lvl, defArm, hplus+g.data.strPlus[att.Str]) {
proll := g.roll(atk.Count, atk.Sides)
damage := dplus + proll + addDam[att.Str]
damage := dplus + proll + g.data.addDam[att.Str]
if damage > 0 {
def.HP -= damage
}
@@ -515,7 +478,7 @@ func (g *RogueGame) hit(er, ee string, noend bool) {
i += 4
}
s = hNames[i]
s = g.data.hNames[i]
}
g.addmsgf("%s", s)
@@ -546,7 +509,7 @@ func (g *RogueGame) miss(er, ee string, noend bool) {
i += 4
}
g.addmsgf("%s", mNames[i])
g.addmsgf("%s", g.data.mNames[i])
if !g.Options.Terse {
g.addmsgf(" %s", prname(ee, false))

View File

@@ -138,6 +138,9 @@ type RogueGame struct {
rogueOpts string // the ROGUEOPTS string, re-parsed by playit as in C
restored bool // game came from a save file; Run skips setup
// data is the game's copy of the static tables (extern.c and friends).
data *gameData
}
// NewGame builds a game from cfg, seeds the RNG, and randomizes the item
@@ -145,6 +148,7 @@ type RogueGame struct {
// and first level arrive with later porting phases).
func NewGame(cfg Config) *RogueGame {
g := &RogueGame{
data: newGameData(),
Rng: &Rng{Seed: cfg.Seed},
Dnum: int(cfg.Seed),
Whoami: cfg.Name,
@@ -176,7 +180,7 @@ func NewGame(cfg Config) *RogueGame {
g.ParseOpts(cfg.RogueOpts)
}
g.Monsters = monsterTable
g.Monsters = g.data.monsterTable
g.Items.Group = 2 // weapons.c: int group = 2
for i := range g.Level.Passages {

View File

@@ -8,7 +8,7 @@ import "strings"
// initPlayer rolls her up (init.c init_player).
func (g *RogueGame) initPlayer() {
p := &g.Player
p.MaxStats = initStats
p.MaxStats = g.data.initStats
p.Stats = p.MaxStats
p.FoodLeft = HungerTime
// Give him some food
@@ -20,7 +20,7 @@ func (g *RogueGame) initPlayer() {
obj = newObject()
obj.Kind = KindArmor
obj.Which = int(ArmorRingMail)
obj.ArmorClass = aClass[ArmorRingMail] - 1
obj.ArmorClass = g.data.aClass[ArmorRingMail] - 1
obj.Flags.Set(Known)
obj.Count = 1
p.CurArmor = obj
@@ -50,19 +50,19 @@ func (g *RogueGame) initPlayer() {
// initColors initializes the potion color scheme for this game
// (init.c init_colors).
func (g *RogueGame) initColors() {
used := make([]bool, len(rainbow))
used := make([]bool, len(g.data.rainbow))
for i := range NumPotionTypes {
var j int
for {
j = g.rnd(len(rainbow))
j = g.rnd(len(g.data.rainbow))
if !used[j] {
break
}
}
used[j] = true
g.Items.PotColors[i] = rainbow[j]
g.Items.PotColors[i] = g.data.rainbow[j]
}
}
@@ -75,7 +75,7 @@ func (g *RogueGame) initNames() {
for ; nwords > 0; nwords-- {
nsyl := g.rnd(3) + 1
for ; nsyl > 0; nsyl-- {
sp := sylls[g.rnd(len(sylls))]
sp := g.data.sylls[g.rnd(len(g.data.sylls))]
if cp.Len()+len(sp) > MaxNameLen {
break
}
@@ -93,47 +93,47 @@ func (g *RogueGame) initNames() {
// initStones initializes the ring stone setting scheme for this game
// (init.c init_stones).
func (g *RogueGame) initStones() {
used := make([]bool, len(stoneTable))
used := make([]bool, len(g.data.stoneTable))
for i := range NumRingTypes {
var j int
for {
j = g.rnd(len(stoneTable))
j = g.rnd(len(g.data.stoneTable))
if !used[j] {
break
}
}
used[j] = true
g.Items.RingStones[i] = stoneTable[j].Name
g.Items.Rings[i].Worth += stoneTable[j].Value
g.Items.RingStones[i] = g.data.stoneTable[j].Name
g.Items.Rings[i].Worth += g.data.stoneTable[j].Value
}
}
// initMaterials initializes the construction materials for wands and staffs
// (init.c init_materials).
func (g *RogueGame) initMaterials() {
used := make([]bool, len(woods))
metused := make([]bool, len(metals))
used := make([]bool, len(g.data.woods))
metused := make([]bool, len(g.data.metals))
for i := range NumWandTypes {
var str string
for {
if g.rnd(2) == 0 {
j := g.rnd(len(metals))
j := g.rnd(len(g.data.metals))
if !metused[j] {
g.Items.WandType[i] = "wand"
str = metals[j]
g.Items.WandType[i] = wandName
str = g.data.metals[j]
metused[j] = true
break
}
} else {
j := g.rnd(len(woods))
j := g.rnd(len(g.data.woods))
if !used[j] {
g.Items.WandType[i] = "staff"
str = woods[j]
g.Items.WandType[i] = staffName
str = g.data.woods[j]
used[j] = true
break
@@ -156,13 +156,13 @@ func sumProbs(info []ObjInfo) {
// initProbs copies the base tables into the game and initializes the
// probabilities for the various items (init.c init_probs).
func (g *RogueGame) initProbs() {
g.Items.Things = baseThings
g.Items.Potions = basePotInfo
g.Items.Scrolls = baseScrInfo
g.Items.Rings = baseRingInfo
g.Items.Sticks = baseWsInfo
g.Items.Weapons = baseWeapInfo
g.Items.Armors = baseArmInfo
g.Items.Things = g.data.baseThings
g.Items.Potions = g.data.basePotInfo
g.Items.Scrolls = g.data.baseScrInfo
g.Items.Rings = g.data.baseRingInfo
g.Items.Sticks = g.data.baseWsInfo
g.Items.Weapons = g.data.baseWeapInfo
g.Items.Armors = g.data.baseArmInfo
sumProbs(g.Items.Things[:])
sumProbs(g.Items.Potions[:])
@@ -177,7 +177,7 @@ func (g *RogueGame) initProbs() {
// hallucinating (init.c pick_color).
func (g *RogueGame) pickColor(col string) string {
if g.Player.On(Hallucinating) {
return rainbow[g.rnd(len(rainbow))]
return g.data.rainbow[g.rnd(len(g.data.rainbow))]
}
return col

View File

@@ -146,8 +146,6 @@ type statusCache struct {
init bool
}
var hungerStateName = [...]string{"", "Hungry", "Weak", "Faint"}
// status displays the important stats line, keeping the cursor where it was
// (io.c status).
func (g *RogueGame) status() {
@@ -192,7 +190,7 @@ func (g *RogueGame) status() {
"Level: %d Gold: %-5d Hp: %*d(%*d) Str: %2d(%d) Arm: %-2d Exp: %d/%d %s",
g.Depth, p.Purse, s.hpwidth, p.Stats.HP, s.hpwidth, p.Stats.MaxHP,
p.Stats.Str, p.MaxStats.Str, 10-s.arm, p.Stats.Lvl, p.Stats.Exp,
hungerStateName[p.HungryState])
g.data.hungerStateName[p.HungryState])
if g.StatMsg {
g.move(0, 0)
g.msg("%s", line)

View File

@@ -286,8 +286,8 @@ func (g *RogueGame) checkLevel() {
p := &g.Player
var i int
for i = 0; eLevels[i] != 0; i++ {
if eLevels[i] > p.Stats.Exp {
for i = 0; g.data.eLevels[i] != 0; i++ {
if g.data.eLevels[i] > p.Stats.Exp {
break
}
}
@@ -493,22 +493,17 @@ func (g *RogueGame) callIt(info *ObjInfo) {
}
}
// thingList is misc.c rnd_thing()'s static table.
var thingList = []byte{
Potion, Scroll, Ring, Stick, Food, Weapon, Armor, Stairs, Gold, Amulet,
}
// rndThing picks a random thing appropriate for this level (misc.c
// rnd_thing).
func (g *RogueGame) rndThing() byte {
var i int
if g.Depth >= AmuletLevel {
i = g.rnd(len(thingList))
i = g.rnd(len(g.data.thingList))
} else {
i = g.rnd(len(thingList) - 1)
i = g.rnd(len(g.data.thingList) - 1)
}
return thingList[i]
return g.data.thingList[i]
}
// chooseStr picks the first or second string depending on whether the

View File

@@ -2,24 +2,12 @@ package game
// monsters.c — monster creation and saving throws.
// lvlMons and wandMons list monsters in rough order of vorpalness; zero
// entries in wandMons never wander (monsters.c).
var lvlMons = [26]byte{
'K', 'E', 'B', 'S', 'H', 'I', 'R', 'O', 'Z', 'L', 'C', 'Q', 'A',
'N', 'Y', 'F', 'T', 'W', 'P', 'X', 'U', 'M', 'V', 'G', 'J', 'D',
}
var wandMons = [26]byte{
'K', 'E', 'B', 'S', 'H', 0, 'R', 'O', 'Z', 0, 'C', 'Q', 'A',
0, 'Y', 0, 'T', 'W', 'P', 0, 'U', 'M', 'V', 'G', 'J', 0,
}
// randMonster picks a monster to show up; the lower the level, the meaner
// the monster (monsters.c randmonster).
func (g *RogueGame) randMonster(wander bool) byte {
mons := &lvlMons
mons := &g.data.lvlMons
if wander {
mons = &wandMons
mons = &g.data.wandMons
}
for {

View File

@@ -255,17 +255,17 @@ func (g *RogueGame) beTrapped(tc Coord) TrapKind {
case 0:
g.msg("you are suddenly in a parallel dimension")
case 1:
g.msg("the light in here suddenly seems %s", rainbow[g.rnd(len(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:
g.msg("multi-colored lines swirl around you, then fade")
case 4:
g.msg("a %s light flashes in your eyes", rainbow[g.rnd(len(rainbow))])
g.msg("a %s light flashes in your eyes", g.data.rainbow[g.rnd(len(g.data.rainbow))])
case 5:
g.msg("a spike shoots past your ear!")
case 6:
g.msg("%s sparks dance across your armor", rainbow[g.rnd(len(rainbow))])
g.msg("%s sparks dance across your armor", g.data.rainbow[g.rnd(len(g.data.rainbow))])
case 7:
g.msg("you suddenly feel very thirsty")
case 8:
@@ -273,7 +273,7 @@ func (g *RogueGame) beTrapped(tc Coord) TrapKind {
case 9:
g.msg("time now seems to be going slower")
case 10:
g.msg("you pack turns %s!", rainbow[g.rnd(len(rainbow))])
g.msg("you pack turns %s!", g.data.rainbow[g.rnd(len(g.data.rainbow))])
}
case TrapSleep:
g.NoCommand += g.spread(5) // SLEEPTIME

View File

@@ -26,36 +26,49 @@ const (
KindRingOrStick ObjectKind = -2
)
// kindGlyphs maps each kind to the character Rogue draws for it.
var kindGlyphs = [...]byte{
KindNone: ' ',
KindPotion: Potion,
KindScroll: Scroll,
KindFood: Food,
KindWeapon: Weapon,
KindArmor: Armor,
KindRing: Ring,
KindWand: Stick,
KindAmulet: Amulet,
KindGold: Gold,
}
// Category words shared by ObjectKind.String, the discovery list, and the
// ident table. (The bare identifiers Potion, Scroll, Ring, Gold are the
// glyph byte constants.)
const (
potionName = "potion"
scrollName = "scroll"
ringName = "ring"
goldName = "gold"
)
// Glyph returns the map/display character for this kind of object.
func (k ObjectKind) Glyph() byte {
if k < 0 || int(k) >= len(kindGlyphs) {
return ' '
switch k {
case KindPotion:
return Potion
case KindScroll:
return Scroll
case KindFood:
return Food
case KindWeapon:
return Weapon
case KindArmor:
return Armor
case KindRing:
return Ring
case KindWand:
return Stick
case KindAmulet:
return Amulet
case KindGold:
return Gold
}
return kindGlyphs[k]
return ' '
}
// String names the category the way the C type_name() did.
func (k ObjectKind) String() string {
switch k {
case KindPotion:
return "potion"
return potionName
case KindScroll:
return "scroll"
return scrollName
case KindFood:
return "food"
case KindWeapon:
@@ -63,13 +76,13 @@ func (k ObjectKind) String() string {
case KindArmor:
return "suit of armor"
case KindRing:
return "ring"
return ringName
case KindWand:
return "wand or staff"
case KindAmulet:
return "amulet"
case KindGold:
return "gold"
return goldName
case KindRingOrStick:
return "ring, wand or staff"
}
@@ -80,10 +93,25 @@ func (k ObjectKind) String() string {
// objectKindForGlyph is the reverse of Glyph: what category of item does a
// map character denote. Returns KindNone for non-item characters.
func objectKindForGlyph(ch byte) ObjectKind {
for k, g := range kindGlyphs {
if g == ch && ObjectKind(k) != KindNone {
return ObjectKind(k)
}
switch ch {
case Potion:
return KindPotion
case Scroll:
return KindScroll
case Food:
return KindFood
case Weapon:
return KindWeapon
case Armor:
return KindArmor
case Ring:
return KindRing
case Stick:
return KindWand
case Amulet:
return KindAmulet
case Gold:
return KindGold
}
return KindNone

View File

@@ -101,7 +101,7 @@ func (g *RogueGame) putOpt(op *optDesc) {
case optBool, optSeeFloor:
hw.AddStr(boolStr(*op.boolP))
case optInvT:
hw.AddStr(invTName[*op.intP])
hw.AddStr(g.data.invTName[*op.intP])
case optStr:
hw.AddStr(*op.strP)
}
@@ -291,7 +291,7 @@ func displayStr(buf []byte) string {
func (g *RogueGame) getInvT(ip *int) int {
win := g.scr.Hw
oy, ox := win.GetYX()
win.AddStr(invTName[*ip])
win.AddStr(g.data.invTName[*ip])
for {
win.Move(oy, ox)
@@ -319,7 +319,7 @@ func (g *RogueGame) getInvT(ip *int) int {
break
}
win.MvPrintwf(oy, ox, "%s\n", invTName[*ip])
win.MvPrintwf(oy, ox, "%s\n", g.data.invTName[*ip])
return Norm
}
@@ -383,7 +383,7 @@ func (g *RogueGame) ParseOpts(str string) {
w = string(toUpper(w[0])) + w[1:]
}
for ti, tn := range invTName {
for ti, tn := range g.data.invTName {
if strings.HasPrefix(tn, w) {
*op.intP = ti

View File

@@ -2,19 +2,6 @@ package game
// passages.c — draw the connecting passages.
// rdesConn is the hardcoded 3x3 room adjacency matrix from do_passages.
var rdesConn = [MaxRooms][MaxRooms]bool{
{false, true, false, true, false, false, false, false, false},
{true, false, true, false, true, false, false, false, false},
{false, true, false, false, false, true, false, false, false},
{true, false, false, false, true, false, true, false, false},
{false, true, false, true, false, true, false, true, false},
{false, false, true, false, true, false, false, false, true},
{false, false, false, true, false, false, false, true, false},
{false, false, false, false, true, false, true, false, true},
{false, false, false, false, false, true, false, true, false},
}
// doPassages draws all the passages on a level (passages.c do_passages).
func (g *RogueGame) doPassages() {
var (
@@ -34,7 +21,7 @@ func (g *RogueGame) doPassages() {
r2 := -1
for i := range MaxRooms {
if rdesConn[r1][i] && !ingraph[i] {
if g.data.rdesConn[r1][i] && !ingraph[i] {
if j++; g.rnd(j) == 0 {
r2 = i
}
@@ -74,7 +61,7 @@ func (g *RogueGame) doPassages() {
r2 := -1
for i := range MaxRooms {
if rdesConn[r1][i] && !isconn[r1][i] {
if g.data.rdesConn[r1][i] && !isconn[r1][i] {
if j++; g.rnd(j) == 0 {
r2 = i
}

View File

@@ -13,24 +13,6 @@ type pact struct {
straight string
}
// pActions is potions.c p_actions[]. The P_SEEINVIS message is dynamic
// (it names the fruit) and is computed in doPot.
var pActions = [NumPotionTypes]pact{
PotionConfusion: {Confused, DUnconfuse, HuhDuration,
"what a tripy feeling!",
"wait, what's going on here. Huh? What? Who?"},
PotionLSD: {Hallucinating, DComeDown, SeeDuration,
"Oh, wow! Everything seems so cosmic!",
"Oh, wow! Everything seems so cosmic!"},
PotionSeeInvisible: {CanSeeInvisible, DUnsee, SeeDuration, "", ""},
PotionBlindness: {Blind, DSight, SeeDuration,
"oh, bummer! Everything is dark! Help!",
"a cloak of darkness falls around you"},
PotionLevitation: {Levitating, DLand, HealTime,
"oh, wow! You're floating in the air!",
"you start to float in the air"},
}
// quaff drinks a potion from the pack (potions.c quaff).
func (g *RogueGame) quaff() {
p := &g.Player
@@ -100,7 +82,7 @@ func (g *RogueGame) quaff() {
g.scr.Hw.Clear()
for _, tp := range g.Level.Objects {
if tp.isMagic() {
if g.isMagic(tp) {
show = true
g.scr.Hw.MvAddCh(tp.Pos.Y, tp.Pos.X, Magic)
@@ -110,7 +92,7 @@ func (g *RogueGame) quaff() {
for _, mp := range g.Level.Monsters {
for _, tp := range mp.Pack {
if tp.isMagic() {
if g.isMagic(tp) {
show = true
g.scr.Hw.MvAddCh(mp.Pos.Y, mp.Pos.X, Magic)
@@ -208,14 +190,14 @@ func (g *RogueGame) quaff() {
// raiseLevel: the guy just magically went up a level (potions.c
// raise_level).
func (g *RogueGame) raiseLevel() {
g.Player.Stats.Exp = eLevels[g.Player.Stats.Lvl-1] + 1
g.Player.Stats.Exp = g.data.eLevels[g.Player.Stats.Lvl-1] + 1
g.checkLevel()
}
// doPot does a potion with standard setup: it uses a fuse and turns on a
// flag (potions.c do_pot).
func (g *RogueGame) doPot(kind PotionKind, knowit bool) {
pp := &pActions[kind]
pp := &g.data.pActions[kind]
if !g.Items.Potions[kind].Know {
g.Items.Potions[kind].Know = knowit
}
@@ -240,10 +222,10 @@ func (g *RogueGame) doPot(kind PotionKind, knowit bool) {
}
// isMagic reports whether an object radiates magic (potions.c is_magic).
func (o *Object) isMagic() bool {
func (g *RogueGame) isMagic(o *Object) bool {
switch o.Kind {
case KindArmor:
return o.Flags.Has(Protected) || o.ArmorClass != aClass[o.Which]
return o.Flags.Has(Protected) || o.ArmorClass != g.data.aClass[o.Which]
case KindWeapon:
return o.HPlus != 0 || o.DPlus != 0
case KindPotion, KindScroll, KindWand, KindRing, KindAmulet:

View File

@@ -139,25 +139,6 @@ func (g *RogueGame) gethand() int {
}
}
// ringUses is the rings.c ring_eat static uses[] table: how much food each
// ring type uses up per turn (negative = a 1-in-n chance of 1).
var ringUses = [NumRingTypes]int{
1, // R_PROTECT
1, // R_ADDSTR
1, // R_SUSTSTR
-3, // R_SEARCH
-5, // R_SEEINVIS
0, // R_NOP
0, // R_AGGR
-3, // R_ADDHIT
-3, // R_ADDDAM
2, // R_REGEN
-2, // R_DIGEST
0, // R_TELEPORT
1, // R_STEALTH
1, // R_SUSTARM
}
// ringEat reports how much food the ring on the given hand uses up
// (rings.c ring_eat).
func (g *RogueGame) ringEat(hand int) int {
@@ -166,7 +147,7 @@ func (g *RogueGame) ringEat(hand int) int {
return 0
}
eat := ringUses[ring.RingKind()]
eat := g.data.ringUses[ring.RingKind()]
if eat < 0 {
if g.rnd(-eat) == 0 {
eat = 1

View File

@@ -21,22 +21,6 @@ func (g *RogueGame) myExit() {
panic(gameEnd{})
}
var ripArt = []string{
" __________",
" / \\",
" / REST \\",
" / IN \\",
" / PEACE \\",
" / \\",
" | |",
" | |",
" | killed by a |",
" | |",
" | 1980 |",
" *| * * * | *",
" ________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______",
}
// death does something really fun when he dies (rip.c death).
func (g *RogueGame) death(monst byte) {
p := &g.Player
@@ -56,7 +40,7 @@ func (g *RogueGame) death(monst byte) {
} else {
year := time.Now().Year()
for i, line := range ripArt {
for i, line := range g.data.ripArt {
g.scr.Std.MvAddStr(8+i, 0, line)
}
@@ -144,7 +128,7 @@ func (g *RogueGame) totalWinner() {
case KindArmor:
worth = it.Armors[obj.Which].Worth
worth += (9 - obj.ArmorClass) * 100
worth += 10 * (aClass[obj.Which] - obj.ArmorClass)
worth += 10 * (g.data.aClass[obj.Which] - obj.ArmorClass)
obj.Flags.Set(Known)
case KindScroll:
op := &it.Scrolls[obj.Which]
@@ -216,15 +200,6 @@ func (g *RogueGame) totalWinner() {
g.myExit()
}
// killnameTable is the rip.c nlist[]: special death causes.
var killnameTable = []helpEntry{
{'a', "arrow", true},
{'b', "bolt", true},
{'d', "dart", true},
{'h', "hypothermia", false},
{'s', "starvation", false},
}
// killname converts a code to a monster name (rip.c killname).
func (g *RogueGame) killname(monst byte, doart bool) string {
var (
@@ -239,7 +214,7 @@ func (g *RogueGame) killname(monst byte, doart bool) string {
sp = "Wally the Wonder Badger"
article = false
for _, hp := range killnameTable {
for _, hp := range g.data.killnameTable {
if hp.Ch == monst {
sp = hp.Desc
article = hp.Print

View File

@@ -606,6 +606,7 @@ func Restore(path string, cfg Config) (*RogueGame, error) {
}
g := &RogueGame{
data: newGameData(),
Rng: &Rng{},
Playing: true,
ScorePath: cfg.ScorePath,

View File

@@ -25,13 +25,6 @@ type ScoreEnt struct {
Time int64
}
var scoreReasons = [4]string{
"killed",
"quit",
"A total winner",
"killed with Amulet",
}
// rdScore reads the scoreboard file (save.c rd_score).
func (g *RogueGame) rdScore() []ScoreEnt {
topTen := make([]ScoreEnt, numScores)
@@ -187,7 +180,7 @@ func (g *RogueGame) score(amount, flags int, monst byte) {
}
line := fmt.Sprintf("%2d %5d %s: %s on level %d", i+1,
scp.Score, scp.Name, scoreReasons[scp.Flags], scp.Level)
scp.Score, scp.Name, g.data.scoreReasons[scp.Flags], scp.Level)
if scp.Flags == 0 || scp.Flags == 3 {
line += " by " + g.killname(scp.Monster, true)
}

View File

@@ -2,16 +2,6 @@ package game
// scrolls.c — read a scroll and let it happen.
// idType maps identify scrolls to the kind of item they identify
// (scrolls.c static id_type).
var idType = [ScrollIdentifyRingOrStick + 1]ObjectKind{
ScrollIdentifyPotion: KindPotion,
ScrollIdentifyScroll: KindScroll,
ScrollIdentifyWeapon: KindWeapon,
ScrollIdentifyArmor: KindArmor,
ScrollIdentifyRingOrStick: KindRingOrStick,
}
// readScroll reads a scroll from the pack and does the appropriate thing
// (scrolls.c read_scroll).
func (g *RogueGame) readScroll() {
@@ -136,7 +126,7 @@ func (g *RogueGame) readScroll() {
// Identify, let him figure something out
g.Items.Scrolls[obj.Which].Know = true
g.msg("this scroll is an %s scroll", g.Items.Scrolls[obj.Which].Name)
g.whatis(true, idType[obj.ScrollKind()])
g.whatis(true, g.data.idType[obj.ScrollKind()])
case ScrollMagicMapping:
// Scroll of magic mapping.
g.Items.Scrolls[ScrollMagicMapping].Know = true

View File

@@ -4,6 +4,12 @@ import "fmt"
// sticks.c — zap wands and staffs.
// The two ws_type strings a stick can be made as.
const (
wandName = "wand"
staffName = "staff"
)
// doZap performs a zap with a wand (sticks.c do_zap).
func (g *RogueGame) doZap() {
p := &g.Player
@@ -378,7 +384,7 @@ func (g *RogueGame) fireBolt(start Coord, dir *Coord, name string) {
// fixStick sets up a new wand or staff (sticks.c fix_stick).
func (g *RogueGame) fixStick(cur *Object) {
if g.Items.WandType[cur.Which] == "staff" {
if g.Items.WandType[cur.Which] == staffName {
cur.Damage = dice("2x3")
} else {
cur.Damage = dice("1x1")

View File

@@ -1,247 +1,11 @@
package game
// This file is the immutable data from extern.c and init.c: tables that are
// never written after program start. Per-game mutable copies (the ObjInfo
// tables, whose probabilities are re-summed and whose Know/Guess fields
// change during play) are cloned into RogueGame.Items by NewGame.
// initStats is the C INIT_STATS: the player's starting statistics.
var initStats = Stats{Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12, Dmg: dice("1x4"), MaxHP: 12}
// aClass is extern.c a_class[]: armor class for each armor type.
var aClass = [NumArmorTypes]int{
8, // LEATHER
7, // RING_MAIL
7, // STUDDED_LEATHER
6, // SCALE_MAIL
5, // CHAIN_MAIL
4, // SPLINT_MAIL
4, // BANDED_MAIL
3, // PLATE_MAIL
}
// eLevels is extern.c e_levels[]: experience thresholds per level; the
// zero terminates the table as in C.
var eLevels = []int{
10, 20, 40, 80, 160, 320, 640, 1300, 2600, 5200, 13000, 26000,
50000, 100000, 200000, 400000, 800000, 2000000, 4000000, 8000000, 0,
}
// trName is extern.c tr_name[]: names of the traps.
var trName = [NumTrapTypes]string{
"a trapdoor",
"an arrow trap",
"a sleeping gas trap",
"a beartrap",
"a teleport trap",
"a poison dart trap",
"a rust trap",
"a mysterious trap",
}
// invTName is extern.c inv_t_name[]: the inventory style names.
var invTName = []string{"Overwrite", "Slow", "Clear"}
// monsterTable is extern.c monsters[26]: all monster kinds, indexed by
// letter - 'A'. Monster strength (XX in C) is always 10; HP (___ in C) is
// rolled from the level at creation time.
var monsterTable = [26]MonsterKind{
/* Name CARRY FLAGS str exp lvl arm hp dmg */
{"aquator", 0, Mean, Stats{10, 20, 5, 2, 1, dice("0x0/0x0"), 0}},
{"bat", 0, Flying, Stats{10, 1, 1, 3, 1, dice("1x2"), 0}},
{"centaur", 15, 0, Stats{10, 17, 4, 4, 1, dice("1x2/1x5/1x5"), 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}},
{"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}},
{"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}},
{"kestrel", 0, Mean | Flying, Stats{10, 1, 1, 7, 1, dice("1x4"), 0}},
{"leprechaun", 0, 0, Stats{10, 10, 3, 8, 1, dice("1x1"), 0}},
{"medusa", 40, Mean, Stats{10, 200, 8, 2, 1, dice("3x4/3x4/2x5"), 0}},
{"nymph", 100, 0, Stats{10, 37, 3, 9, 1, dice("0x0"), 0}},
{"orc", 15, Greedy, Stats{10, 5, 1, 6, 1, dice("1x8"), 0}},
{"phantom", 0, Invisible, Stats{10, 120, 8, 3, 1, dice("4x4"), 0}},
{"quagga", 0, Mean, Stats{10, 15, 3, 3, 1, dice("1x5/1x5"), 0}},
{"rattlesnake", 0, Mean, Stats{10, 9, 2, 3, 1, dice("1x6"), 0}},
{"snake", 0, Mean, Stats{10, 2, 1, 5, 1, dice("1x3"), 0}},
{"troll", 50, Regenerates | Mean, Stats{10, 120, 6, 4, 1, dice("1x8/1x8/2x6"), 0}},
{"black unicorn", 0, Mean, Stats{10, 190, 7, -2, 1, dice("1x9/1x9/2x9"), 0}},
{"vampire", 20, Regenerates | Mean, Stats{10, 350, 8, 1, 1, dice("1x10"), 0}},
{"wraith", 0, 0, Stats{10, 55, 5, 4, 1, dice("1x6"), 0}},
{"xeroc", 30, 0, Stats{10, 100, 7, 7, 1, dice("4x4"), 0}},
{"yeti", 30, 0, Stats{10, 50, 4, 6, 1, dice("1x6/1x6"), 0}},
{"zombie", 0, Mean, Stats{10, 6, 2, 8, 1, dice("1x8"), 0}},
}
// Base ObjInfo tables (extern.c). These are templates: NewGame copies them
// into ItemLore before initProbs converts Prob to cumulative form.
var baseThings = [NumThings]ObjInfo{
{Prob: 26}, // potion
{Prob: 36}, // scroll
{Prob: 16}, // food
{Prob: 7}, // weapon
{Prob: 7}, // armor
{Prob: 4}, // ring
{Prob: 4}, // stick
}
var baseArmInfo = [NumArmorTypes]ObjInfo{
{Name: "leather armor", Prob: 20, Worth: 20},
{Name: "ring mail", Prob: 15, Worth: 25},
{Name: "studded leather armor", Prob: 15, Worth: 20},
{Name: "scale mail", Prob: 13, Worth: 30},
{Name: "chain mail", Prob: 12, Worth: 75},
{Name: "splint mail", Prob: 10, Worth: 80},
{Name: "banded mail", Prob: 10, Worth: 90},
{Name: "plate mail", Prob: 5, Worth: 150},
}
var basePotInfo = [NumPotionTypes]ObjInfo{
{Name: "confusion", Prob: 7, Worth: 5},
{Name: "hallucination", Prob: 8, Worth: 5},
{Name: "poison", Prob: 8, Worth: 5},
{Name: "gain strength", Prob: 13, Worth: 150},
{Name: "see invisible", Prob: 3, Worth: 100},
{Name: "healing", Prob: 13, Worth: 130},
{Name: "monster detection", Prob: 6, Worth: 130},
{Name: "magic detection", Prob: 6, Worth: 105},
{Name: "raise level", Prob: 2, Worth: 250},
{Name: "extra healing", Prob: 5, Worth: 200},
{Name: "haste self", Prob: 5, Worth: 190},
{Name: "restore strength", Prob: 13, Worth: 130},
{Name: "blindness", Prob: 5, Worth: 5},
{Name: "levitation", Prob: 6, Worth: 75},
}
var baseRingInfo = [NumRingTypes]ObjInfo{
{Name: "protection", Prob: 9, Worth: 400},
{Name: "add strength", Prob: 9, Worth: 400},
{Name: "sustain strength", Prob: 5, Worth: 280},
{Name: "searching", Prob: 10, Worth: 420},
{Name: "see invisible", Prob: 10, Worth: 310},
{Name: "adornment", Prob: 1, Worth: 10},
{Name: "aggravate monster", Prob: 10, Worth: 10},
{Name: "dexterity", Prob: 8, Worth: 440},
{Name: "increase damage", Prob: 8, Worth: 400},
{Name: "regeneration", Prob: 4, Worth: 460},
{Name: "slow digestion", Prob: 9, Worth: 240},
{Name: "teleportation", Prob: 5, Worth: 30},
{Name: "stealth", Prob: 7, Worth: 470},
{Name: "maintain armor", Prob: 5, Worth: 380},
}
var baseScrInfo = [NumScrollTypes]ObjInfo{
{Name: "monster confusion", Prob: 7, Worth: 140},
{Name: "magic mapping", Prob: 4, Worth: 150},
{Name: "hold monster", Prob: 2, Worth: 180},
{Name: "sleep", Prob: 3, Worth: 5},
{Name: "enchant armor", Prob: 7, Worth: 160},
{Name: "identify potion", Prob: 10, Worth: 80},
{Name: "identify scroll", Prob: 10, Worth: 80},
{Name: "identify weapon", Prob: 6, Worth: 80},
{Name: "identify armor", Prob: 7, Worth: 100},
{Name: "identify ring, wand or staff", Prob: 10, Worth: 115},
{Name: "scare monster", Prob: 3, Worth: 200},
{Name: "food detection", Prob: 2, Worth: 60},
{Name: "teleportation", Prob: 5, Worth: 165},
{Name: "enchant weapon", Prob: 8, Worth: 150},
{Name: "create monster", Prob: 4, Worth: 75},
{Name: "remove curse", Prob: 7, Worth: 105},
{Name: "aggravate monsters", Prob: 3, Worth: 20},
{Name: "protect armor", Prob: 2, Worth: 250},
}
var baseWeapInfo = [NumWeaponTypes + 1]ObjInfo{
{Name: "mace", Prob: 11, Worth: 8},
{Name: "long sword", Prob: 11, Worth: 15},
{Name: "short bow", Prob: 12, Worth: 15},
{Name: "arrow", Prob: 12, Worth: 1},
{Name: "dagger", Prob: 8, Worth: 3},
{Name: "two handed sword", Prob: 10, Worth: 75},
{Name: "dart", Prob: 12, Worth: 2},
{Name: "shuriken", Prob: 12, Worth: 5},
{Name: "spear", Prob: 12, Worth: 5},
{}, // DO NOT REMOVE: fake entry for dragon's breath
}
var baseWsInfo = [NumWandTypes]ObjInfo{
{Name: "light", Prob: 12, Worth: 250},
{Name: "invisibility", Prob: 6, Worth: 5},
{Name: "lightning", Prob: 3, Worth: 330},
{Name: "fire", Prob: 3, Worth: 330},
{Name: "cold", Prob: 3, Worth: 330},
{Name: "polymorph", Prob: 15, Worth: 310},
{Name: "magic missile", Prob: 10, Worth: 170},
{Name: "haste monster", Prob: 10, Worth: 5},
{Name: "slow monster", Prob: 11, Worth: 350},
{Name: "drain life", Prob: 9, Worth: 300},
{Name: "nothing", Prob: 1, Worth: 5},
{Name: "teleport away", Prob: 6, Worth: 340},
{Name: "teleport to", Prob: 6, Worth: 50},
{Name: "cancellation", Prob: 5, Worth: 280},
}
// rainbow is init.c rainbow[]: the possible potion colors.
var rainbow = []string{
"amber", "aquamarine", "black", "blue", "brown", "clear", "crimson",
"cyan", "ecru", "gold", "green", "grey", "magenta", "orange", "pink",
"plaid", "purple", "red", "silver", "tan", "tangerine", "topaz",
"turquoise", "vermilion", "violet", "white", "yellow",
}
// sylls is init.c sylls[]: syllables for generated scroll names.
var sylls = []string{
"a", "ab", "ag", "aks", "ala", "an", "app", "arg", "arze", "ash",
"bek", "bie", "bit", "bjor", "blu", "bot", "bu", "byt", "comp",
"con", "cos", "cre", "dalf", "dan", "den", "do", "e", "eep", "el",
"eng", "er", "ere", "erk", "esh", "evs", "fa", "fid", "fri", "fu",
"gan", "gar", "glen", "gop", "gre", "ha", "hyd", "i", "ing", "ip",
"ish", "it", "ite", "iv", "jo", "kho", "kli", "klis", "la", "lech",
"mar", "me", "mi", "mic", "mik", "mon", "mung", "mur", "nej",
"nelg", "nep", "ner", "nes", "nes", "nih", "nin", "o", "od", "ood",
"org", "orn", "ox", "oxy", "pay", "ple", "plu", "po", "pot",
"prok", "re", "rea", "rhov", "ri", "ro", "rog", "rok", "rol", "sa",
"san", "sat", "sef", "seh", "shu", "ski", "sna", "sne", "snik",
"sno", "so", "sol", "sri", "sta", "sun", "ta", "tab", "tem",
"there", "ti", "tox", "trol", "tue", "turs", "u", "ulk", "um", "un",
"uni", "ur", "val", "viv", "vly", "vom", "wah", "wed", "werg",
"wex", "whon", "wun", "xo", "y", "yot", "yu", "zant", "zeb", "zim",
"zok", "zon", "zum",
}
// stoneTable is init.c stones[]: ring stones and their worth.
var stoneTable = []Stone{
{"agate", 25}, {"alexandrite", 40}, {"amethyst", 50},
{"carnelian", 40}, {"diamond", 300}, {"emerald", 300},
{"germanium", 225}, {"granite", 5}, {"garnet", 50},
{"jade", 150}, {"kryptonite", 300}, {"lapis lazuli", 50},
{"moonstone", 50}, {"obsidian", 15}, {"onyx", 60},
{"opal", 200}, {"pearl", 220}, {"peridot", 63},
{"ruby", 350}, {"sapphire", 285}, {"stibotantalite", 200},
{"tiger eye", 50}, {"topaz", 60}, {"turquoise", 70},
{"taaffeite", 300}, {"zircon", 80},
}
// woods is init.c wood[]: what staffs are made of.
var woods = []string{
"avocado wood", "balsa", "bamboo", "banyan", "birch", "cedar",
"cherry", "cinnibar", "cypress", "dogwood", "driftwood", "ebony",
"elm", "eucalyptus", "fall", "hemlock", "holly", "ironwood",
"kukui wood", "mahogany", "manzanita", "maple", "oaken",
"persimmon wood", "pecan", "pine", "poplar", "redwood", "rosewood",
"spruce", "teak", "walnut", "zebrawood",
}
// metals is init.c metal[]: what wands are made of.
var metals = []string{
"aluminum", "beryllium", "bone", "brass", "bronze", "copper",
"electrum", "gold", "iron", "lead", "magnesium", "mercury",
"nickel", "pewter", "platinum", "steel", "silver", "silicon",
"tin", "titanium", "tungsten", "zinc",
}
// tables.go — the static data tables the C game kept as file-scope globals
// (extern.c, init.c, and assorted per-file statics). The port gathers them
// into gameData: every RogueGame carries its own copy, so the package holds
// no package-level state. Per-game mutable copies (the ObjInfo tables,
// whose probabilities are re-summed and whose Know/Guess fields change
// during play) are cloned into RogueGame.Items by NewGame.
// helpEntry is rogue.h struct h_list.
type helpEntry struct {
@@ -250,73 +14,580 @@ type helpEntry struct {
Print bool
}
// helpStr is extern.c helpstr[]: the '?' command help text.
var helpStr = []helpEntry{
{'?', " prints help", true},
{'/', " identify object", true},
{'h', " left", true},
{'j', " down", true},
{'k', " up", true},
{'l', " right", true},
{'y', " up & left", true},
{'u', " up & right", true},
{'b', " down & left", true},
{'n', " down & right", true},
{'H', " run left", false},
{'J', " run down", false},
{'K', " run up", false},
{'L', " run right", false},
{'Y', " run up & left", false},
{'U', " run up & right", false},
{'B', " run down & left", false},
{'N', " run down & right", false},
{CTRL('H'), " run left until adjacent", false},
{CTRL('J'), " run down until adjacent", false},
{CTRL('K'), " run up until adjacent", false},
{CTRL('L'), " run right until adjacent", false},
{CTRL('Y'), " run up & left until adjacent", false},
{CTRL('U'), " run up & right until adjacent", false},
{CTRL('B'), " run down & left until adjacent", false},
{CTRL('N'), " run down & right until adjacent", false},
{0, " <SHIFT><dir>: run that way", true},
{0, " <CTRL><dir>: run till adjacent", true},
{'f', "<dir> fight till death or near death", true},
{'t', "<dir> throw something", true},
{'m', "<dir> move onto without picking up", true},
{'z', "<dir> zap a wand in a direction", true},
{'^', "<dir> identify trap type", true},
{'s', " search for trap/secret door", true},
{'>', " go down a staircase", true},
{'<', " go up a staircase", true},
{'.', " rest for a turn", true},
{',', " pick something up", true},
{'i', " inventory", true},
{'I', " inventory single item", true},
{'q', " quaff potion", true},
{'r', " read scroll", true},
{'e', " eat food", true},
{'w', " wield a weapon", true},
{'W', " wear armor", true},
{'T', " take armor off", true},
{'P', " put on ring", true},
{'R', " remove ring", true},
{'d', " drop object", true},
{'c', " call object", true},
{'a', " repeat last command", true},
{')', " print current weapon", true},
{']', " print current armor", true},
{'=', " print current rings", true},
{'@', " print current stats", true},
{'D', " recall what's been discovered", true},
{'o', " examine/set options", true},
{CTRL('R'), " redraw screen", true},
{CTRL('P'), " repeat last message", true},
{Escape, " cancel command", true},
{'S', " save game", true},
{'Q', " quit", true},
{'!', " shell escape", true},
{'F', "<dir> fight till either of you dies", true},
{'v', " print version number", true},
// gameData is the bundle of static tables, built by newGameData and hung on
// RogueGame as g.data. Nothing in it mutates during play: the one table the
// C code writes to (the venus flytrap damage hack) operates on the game's
// Monsters copy, not on this template.
type gameData struct {
// initStats is the C INIT_STATS: the player's starting statistics.
initStats Stats
// aClass is extern.c a_class[]: armor class for each armor type.
aClass [NumArmorTypes]int
// eLevels is extern.c e_levels[]: experience thresholds per level; the
// zero terminates the table as in C.
eLevels []int
// trName is extern.c tr_name[]: names of the traps.
trName [NumTrapTypes]string
// invTName is extern.c inv_t_name[]: the inventory style names.
invTName []string
// monsterTable is extern.c monsters[26]: all monster kinds, indexed by
// letter - 'A'. Monster strength (XX in C) is always 10; HP (___ in C)
// is rolled from the level at creation time.
monsterTable [26]MonsterKind
// Base ObjInfo tables (extern.c). These are templates: NewGame copies
// them into ItemLore before initProbs converts Prob to cumulative form.
baseThings [NumThings]ObjInfo
baseArmInfo [NumArmorTypes]ObjInfo
basePotInfo [NumPotionTypes]ObjInfo
baseRingInfo [NumRingTypes]ObjInfo
baseScrInfo [NumScrollTypes]ObjInfo
baseWeapInfo [NumWeaponTypes + 1]ObjInfo
baseWsInfo [NumWandTypes]ObjInfo
// rainbow is init.c rainbow[]: the possible potion colors.
rainbow []string
// sylls is init.c sylls[]: syllables for generated scroll names.
sylls []string
// stoneTable is init.c stones[]: ring stones and their worth.
stoneTable []Stone
// woods is init.c wood[]: what staffs are made of.
woods []string
// metals is init.c metal[]: what wands are made of.
metals []string
// helpStr is extern.c helpstr[]: the '?' command help text.
helpStr []helpEntry
// hNames are the strings for hitting; the first four are used when the
// player strikes, the second four for monsters (fight.c h_names).
hNames [8]string
// mNames are the strings for missing (fight.c m_names).
mNames [8]string
// strPlus adjusts hit probabilities due to strength (fight.c str_plus).
strPlus [32]int
// addDam adjusts damage done due to strength (fight.c add_dam).
addDam [32]int
// lvlMons and wandMons list monsters in rough order of vorpalness;
// zero entries in wandMons never wander (monsters.c).
lvlMons [26]byte
wandMons [26]byte
// ringUses is the rings.c ring_eat static uses[] table: how much food
// each ring type uses up per turn (negative = a 1-in-n chance of 1).
ringUses [NumRingTypes]int
// initWeaps is the weapons.c init_dam[] table.
initWeaps [NumWeaponTypes]weaponSetup
// pActions is potions.c p_actions[]. The P_SEEINVIS message is dynamic
// (it names the fruit) and is computed in doPot.
pActions [NumPotionTypes]pact
// idType maps identify scrolls to the kind of item they identify
// (scrolls.c static id_type).
idType [ScrollIdentifyRingOrStick + 1]ObjectKind
// rdesConn is the hardcoded 3x3 room adjacency matrix from
// passages.c do_passages.
rdesConn [MaxRooms][MaxRooms]bool
// thingList is misc.c rnd_thing()'s static table.
thingList []byte
// identList is command.c's static ident_list.
identList []helpEntry
// hungerStateName is io.c state_name[].
hungerStateName [4]string
// ripArt is the rip.c rip[] tombstone art.
ripArt []string
// killnameTable is the rip.c nlist[]: special death causes.
killnameTable []helpEntry
// scoreReasons is the rip.c reason[] scoreboard strings.
scoreReasons [4]string
}
// ripWall is the repeated blank wall line of the tombstone art.
const ripWall = " | |"
// newGameData builds the static tables. Each game gets a fresh copy, which
// keeps the package free of globals.
//
//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},
aClass: [NumArmorTypes]int{
8, // LEATHER
7, // RING_MAIL
7, // STUDDED_LEATHER
6, // SCALE_MAIL
5, // CHAIN_MAIL
4, // SPLINT_MAIL
4, // BANDED_MAIL
3, // PLATE_MAIL
},
eLevels: []int{
10, 20, 40, 80, 160, 320, 640, 1300, 2600, 5200, 13000, 26000,
50000, 100000, 200000, 400000, 800000, 2000000, 4000000, 8000000, 0,
},
trName: [NumTrapTypes]string{
"a trapdoor",
"an arrow trap",
"a sleeping gas trap",
"a beartrap",
"a teleport trap",
"a poison dart trap",
"a rust trap",
"a mysterious trap",
},
invTName: []string{"Overwrite", "Slow", "Clear"},
monsterTable: [26]MonsterKind{
/* Name CARRY FLAGS str exp lvl arm hp dmg */
{"aquator", 0, Mean, Stats{10, 20, 5, 2, 1, dice("0x0/0x0"), 0}},
{"bat", 0, Flying, Stats{10, 1, 1, 3, 1, dice("1x2"), 0}},
{"centaur", 15, 0, Stats{10, 17, 4, 4, 1, dice("1x2/1x5/1x5"), 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}},
{"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}},
{"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}},
{"kestrel", 0, Mean | Flying, Stats{10, 1, 1, 7, 1, dice("1x4"), 0}},
{"leprechaun", 0, 0, Stats{10, 10, 3, 8, 1, dice("1x1"), 0}},
{"medusa", 40, Mean, Stats{10, 200, 8, 2, 1, dice("3x4/3x4/2x5"), 0}},
{"nymph", 100, 0, Stats{10, 37, 3, 9, 1, dice("0x0"), 0}},
{"orc", 15, Greedy, Stats{10, 5, 1, 6, 1, dice("1x8"), 0}},
{"phantom", 0, Invisible, Stats{10, 120, 8, 3, 1, dice("4x4"), 0}},
{"quagga", 0, Mean, Stats{10, 15, 3, 3, 1, dice("1x5/1x5"), 0}},
{"rattlesnake", 0, Mean, Stats{10, 9, 2, 3, 1, dice("1x6"), 0}},
{"snake", 0, Mean, Stats{10, 2, 1, 5, 1, dice("1x3"), 0}},
{"troll", 50, Regenerates | Mean, Stats{10, 120, 6, 4, 1, dice("1x8/1x8/2x6"), 0}},
{"black unicorn", 0, Mean, Stats{10, 190, 7, -2, 1, dice("1x9/1x9/2x9"), 0}},
{"vampire", 20, Regenerates | Mean, Stats{10, 350, 8, 1, 1, dice("1x10"), 0}},
{"wraith", 0, 0, Stats{10, 55, 5, 4, 1, dice("1x6"), 0}},
{"xeroc", 30, 0, Stats{10, 100, 7, 7, 1, dice("4x4"), 0}},
{"yeti", 30, 0, Stats{10, 50, 4, 6, 1, dice("1x6/1x6"), 0}},
{"zombie", 0, Mean, Stats{10, 6, 2, 8, 1, dice("1x8"), 0}},
},
baseThings: [NumThings]ObjInfo{
{Prob: 26}, // potion
{Prob: 36}, // scroll
{Prob: 16}, // food
{Prob: 7}, // weapon
{Prob: 7}, // armor
{Prob: 4}, // ring
{Prob: 4}, // stick
},
baseArmInfo: [NumArmorTypes]ObjInfo{
{Name: "leather armor", Prob: 20, Worth: 20},
{Name: "ring mail", Prob: 15, Worth: 25},
{Name: "studded leather armor", Prob: 15, Worth: 20},
{Name: "scale mail", Prob: 13, Worth: 30},
{Name: "chain mail", Prob: 12, Worth: 75},
{Name: "splint mail", Prob: 10, Worth: 80},
{Name: "banded mail", Prob: 10, Worth: 90},
{Name: "plate mail", Prob: 5, Worth: 150},
},
basePotInfo: [NumPotionTypes]ObjInfo{
{Name: "confusion", Prob: 7, Worth: 5},
{Name: "hallucination", Prob: 8, Worth: 5},
{Name: "poison", Prob: 8, Worth: 5},
{Name: "gain strength", Prob: 13, Worth: 150},
{Name: "see invisible", Prob: 3, Worth: 100},
{Name: "healing", Prob: 13, Worth: 130},
{Name: "monster detection", Prob: 6, Worth: 130},
{Name: "magic detection", Prob: 6, Worth: 105},
{Name: "raise level", Prob: 2, Worth: 250},
{Name: "extra healing", Prob: 5, Worth: 200},
{Name: "haste self", Prob: 5, Worth: 190},
{Name: "restore strength", Prob: 13, Worth: 130},
{Name: "blindness", Prob: 5, Worth: 5},
{Name: "levitation", Prob: 6, Worth: 75},
},
baseRingInfo: [NumRingTypes]ObjInfo{
{Name: "protection", Prob: 9, Worth: 400},
{Name: "add strength", Prob: 9, Worth: 400},
{Name: "sustain strength", Prob: 5, Worth: 280},
{Name: "searching", Prob: 10, Worth: 420},
{Name: "see invisible", Prob: 10, Worth: 310},
{Name: "adornment", Prob: 1, Worth: 10},
{Name: "aggravate monster", Prob: 10, Worth: 10},
{Name: "dexterity", Prob: 8, Worth: 440},
{Name: "increase damage", Prob: 8, Worth: 400},
{Name: "regeneration", Prob: 4, Worth: 460},
{Name: "slow digestion", Prob: 9, Worth: 240},
{Name: "teleportation", Prob: 5, Worth: 30},
{Name: "stealth", Prob: 7, Worth: 470},
{Name: "maintain armor", Prob: 5, Worth: 380},
},
baseScrInfo: [NumScrollTypes]ObjInfo{
{Name: "monster confusion", Prob: 7, Worth: 140},
{Name: "magic mapping", Prob: 4, Worth: 150},
{Name: "hold monster", Prob: 2, Worth: 180},
{Name: "sleep", Prob: 3, Worth: 5},
{Name: "enchant armor", Prob: 7, Worth: 160},
{Name: "identify potion", Prob: 10, Worth: 80},
{Name: "identify scroll", Prob: 10, Worth: 80},
{Name: "identify weapon", Prob: 6, Worth: 80},
{Name: "identify armor", Prob: 7, Worth: 100},
{Name: "identify ring, wand or staff", Prob: 10, Worth: 115},
{Name: "scare monster", Prob: 3, Worth: 200},
{Name: "food detection", Prob: 2, Worth: 60},
{Name: "teleportation", Prob: 5, Worth: 165},
{Name: "enchant weapon", Prob: 8, Worth: 150},
{Name: "create monster", Prob: 4, Worth: 75},
{Name: "remove curse", Prob: 7, Worth: 105},
{Name: "aggravate monsters", Prob: 3, Worth: 20},
{Name: "protect armor", Prob: 2, Worth: 250},
},
baseWeapInfo: [NumWeaponTypes + 1]ObjInfo{
{Name: "mace", Prob: 11, Worth: 8},
{Name: "long sword", Prob: 11, Worth: 15},
{Name: "short bow", Prob: 12, Worth: 15},
{Name: "arrow", Prob: 12, Worth: 1},
{Name: "dagger", Prob: 8, Worth: 3},
{Name: "two handed sword", Prob: 10, Worth: 75},
{Name: "dart", Prob: 12, Worth: 2},
{Name: "shuriken", Prob: 12, Worth: 5},
{Name: "spear", Prob: 12, Worth: 5},
{}, // DO NOT REMOVE: fake entry for dragon's breath
},
baseWsInfo: [NumWandTypes]ObjInfo{
{Name: "light", Prob: 12, Worth: 250},
{Name: "invisibility", Prob: 6, Worth: 5},
{Name: "lightning", Prob: 3, Worth: 330},
{Name: "fire", Prob: 3, Worth: 330},
{Name: "cold", Prob: 3, Worth: 330},
{Name: "polymorph", Prob: 15, Worth: 310},
{Name: "magic missile", Prob: 10, Worth: 170},
{Name: "haste monster", Prob: 10, Worth: 5},
{Name: "slow monster", Prob: 11, Worth: 350},
{Name: "drain life", Prob: 9, Worth: 300},
{Name: "nothing", Prob: 1, Worth: 5},
{Name: "teleport away", Prob: 6, Worth: 340},
{Name: "teleport to", Prob: 6, Worth: 50},
{Name: "cancellation", Prob: 5, Worth: 280},
},
rainbow: []string{
"amber", "aquamarine", "black", "blue", "brown", "clear", "crimson",
"cyan", "ecru", "gold", "green", "grey", "magenta", "orange", "pink",
"plaid", "purple", "red", "silver", "tan", "tangerine", "topaz",
"turquoise", "vermilion", "violet", "white", "yellow",
},
//nolint:misspell // "ther" is a C scroll syllable, kept faithfully
sylls: []string{
"a", "ab", "ag", "aks", "ala", "an", "app", "arg", "arze", "ash",
"bek", "bie", "bit", "bjor", "blu", "bot", "bu", "byt", "comp",
"con", "cos", "cre", "dalf", "dan", "den", "do", "e", "eep", "el",
"eng", "er", "ere", "erk", "esh", "evs", "fa", "fid", "fri", "fu",
"gan", "gar", "glen", "gop", "gre", "ha", "hyd", "i", "ing", "ip",
"ish", "it", "ite", "iv", "jo", "kho", "kli", "klis", "la", "lech",
"mar", "me", "mi", "mic", "mik", "mon", "mung", "mur", "nej",
"nelg", "nep", "ner", "nes", "nes", "nih", "nin", "o", "od", "ood",
"org", "orn", "ox", "oxy", "pay", "ple", "plu", "po", "pot",
"prok", "re", "rea", "rhov", "ri", "ro", "rog", "rok", "rol", "sa",
"san", "sat", "sef", "seh", "shu", "ski", "sna", "sne", "snik",
"sno", "so", "sol", "sri", "sta", "sun", "ta", "tab", "tem",
"ther", "ti", "tox", "trol", "tue", "turs", "u", "ulk", "um", "un",
"uni", "ur", "val", "viv", "vly", "vom", "wah", "wed", "werg",
"wex", "whon", "wun", "xo", "y", "yot", "yu", "zant", "zeb", "zim",
"zok", "zon", "zum",
},
stoneTable: []Stone{
{"agate", 25}, {"alexandrite", 40}, {"amethyst", 50},
{"carnelian", 40}, {"diamond", 300}, {"emerald", 300},
{"germanium", 225}, {"granite", 5}, {"garnet", 50},
{"jade", 150}, {"kryptonite", 300}, {"lapis lazuli", 50},
{"moonstone", 50}, {"obsidian", 15}, {"onyx", 60},
{"opal", 200}, {"pearl", 220}, {"peridot", 63},
{"ruby", 350}, {"sapphire", 285}, {"stibotantalite", 200},
{"tiger eye", 50}, {"topaz", 60}, {"turquoise", 70},
{"taaffeite", 300}, {"zircon", 80},
},
woods: []string{
"avocado wood", "balsa", "bamboo", "banyan", "birch", "cedar",
"cherry", "cinnibar", "cypress", "dogwood", "driftwood", "ebony",
"elm", "eucalyptus", "fall", "hemlock", "holly", "ironwood",
"kukui wood", "mahogany", "manzanita", "maple", "oaken",
"persimmon wood", "pecan", "pine", "poplar", "redwood", "rosewood",
"spruce", "teak", "walnut", "zebrawood",
},
metals: []string{
"aluminum", "beryllium", "bone", "brass", "bronze", "copper",
"electrum", "gold", "iron", "lead", "magnesium", "mercury",
"nickel", "pewter", "platinum", "steel", "silver", "silicon",
"tin", "titanium", "tungsten", "zinc",
},
helpStr: []helpEntry{
{'?', " prints help", true},
{'/', " identify object", true},
{'h', " left", true},
{'j', " down", true},
{'k', " up", true},
{'l', " right", true},
{'y', " up & left", true},
{'u', " up & right", true},
{'b', " down & left", true},
{'n', " down & right", true},
{'H', " run left", false},
{'J', " run down", false},
{'K', " run up", false},
{'L', " run right", false},
{'Y', " run up & left", false},
{'U', " run up & right", false},
{'B', " run down & left", false},
{'N', " run down & right", false},
{CTRL('H'), " run left until adjacent", false},
{CTRL('J'), " run down until adjacent", false},
{CTRL('K'), " run up until adjacent", false},
{CTRL('L'), " run right until adjacent", false},
{CTRL('Y'), " run up & left until adjacent", false},
{CTRL('U'), " run up & right until adjacent", false},
{CTRL('B'), " run down & left until adjacent", false},
{CTRL('N'), " run down & right until adjacent", false},
{0, " <SHIFT><dir>: run that way", true},
{0, " <CTRL><dir>: run till adjacent", true},
{'f', "<dir> fight till death or near death", true},
{'t', "<dir> throw something", true},
{'m', "<dir> move onto without picking up", true},
{'z', "<dir> zap a wand in a direction", true},
{'^', "<dir> identify trap type", true},
{'s', " search for trap/secret door", true},
{'>', " go down a staircase", true},
{'<', " go up a staircase", true},
{'.', " rest for a turn", true},
{',', " pick something up", true},
{'i', " inventory", true},
{'I', " inventory single item", true},
{'q', " quaff potion", true},
{'r', " read scroll", true},
{'e', " eat food", true},
{'w', " wield a weapon", true},
{'W', " wear armor", true},
{'T', " take armor off", true},
{'P', " put on ring", true},
{'R', " remove ring", true},
{'d', " drop object", true},
{'c', " call object", true},
{'a', " repeat last command", true},
{')', " print current weapon", true},
{']', " print current armor", true},
{'=', " print current rings", true},
{'@', " print current stats", true},
{'D', " recall what's been discovered", true},
{'o', " examine/set options", true},
{CTRL('R'), " redraw screen", true},
{CTRL('P'), " repeat last message", true},
{Escape, " cancel command", true},
{'S', " save game", true},
{'Q', " quit", true},
{'!', " shell escape", true},
{'F', "<dir> fight till either of you dies", true},
{'v', " print version number", true},
},
hNames: [8]string{
" scored an excellent hit on ",
" hit ",
" have injured ",
" swing and hit ",
" scored an excellent hit on ",
" hit ",
" has injured ",
" swings and hits ",
},
mNames: [8]string{
" miss",
" swing and miss",
" barely miss",
" don't hit",
" misses",
" swings and misses",
" barely misses",
" doesn't hit",
},
strPlus: [32]int{
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
},
addDam: [32]int{
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3,
3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
},
lvlMons: [26]byte{
'K', 'E', 'B', 'S', 'H', 'I', 'R', 'O', 'Z', 'L', 'C', 'Q', 'A',
'N', 'Y', 'F', 'T', 'W', 'P', 'X', 'U', 'M', 'V', 'G', 'J', 'D',
},
wandMons: [26]byte{
'K', 'E', 'B', 'S', 'H', 0, 'R', 'O', 'Z', 0, 'C', 'Q', 'A',
0, 'Y', 0, 'T', 'W', 'P', 0, 'U', 'M', 'V', 'G', 'J', 0,
},
ringUses: [NumRingTypes]int{
1, // R_PROTECT
1, // R_ADDSTR
1, // R_SUSTSTR
-3, // R_SEARCH
-5, // R_SEEINVIS
0, // R_NOP
0, // R_AGGR
-3, // R_ADDHIT
-3, // R_ADDDAM
2, // R_REGEN
-2, // R_DIGEST
0, // R_TELEPORT
1, // R_STEALTH
1, // R_SUSTARM
},
initWeaps: [NumWeaponTypes]weaponSetup{
{dice("2x4"), dice("1x3"), noWeapon, 0}, // WeaponMace
{dice("3x4"), dice("1x2"), noWeapon, 0}, // Long sword
{dice("1x1"), dice("1x1"), noWeapon, 0}, // WeaponBow
{dice("1x1"), dice("2x3"), WeaponBow, Stackable | Missile}, // WeaponArrow
{dice("1x6"), dice("1x4"), noWeapon, Missile}, // WeaponDagger
{dice("4x4"), dice("1x2"), noWeapon, 0}, // 2h sword
{dice("1x1"), dice("1x3"), noWeapon, Stackable | Missile}, // WeaponDart
{dice("1x2"), dice("2x4"), noWeapon, Stackable | Missile}, // Shuriken
{dice("2x3"), dice("1x6"), noWeapon, Missile}, // WeaponSpear
},
pActions: [NumPotionTypes]pact{
PotionConfusion: {Confused, DUnconfuse, HuhDuration,
"what a tripy feeling!",
"wait, what's going on here. Huh? What? Who?"},
PotionLSD: {Hallucinating, DComeDown, SeeDuration,
"Oh, wow! Everything seems so cosmic!",
"Oh, wow! Everything seems so cosmic!"},
PotionSeeInvisible: {CanSeeInvisible, DUnsee, SeeDuration, "", ""},
PotionBlindness: {Blind, DSight, SeeDuration,
"oh, bummer! Everything is dark! Help!",
"a cloak of darkness falls around you"},
PotionLevitation: {Levitating, DLand, HealTime,
"oh, wow! You're floating in the air!",
"you start to float in the air"},
},
idType: [ScrollIdentifyRingOrStick + 1]ObjectKind{
ScrollIdentifyPotion: KindPotion,
ScrollIdentifyScroll: KindScroll,
ScrollIdentifyWeapon: KindWeapon,
ScrollIdentifyArmor: KindArmor,
ScrollIdentifyRingOrStick: KindRingOrStick,
},
rdesConn: [MaxRooms][MaxRooms]bool{
{false, true, false, true, false, false, false, false, false},
{true, false, true, false, true, false, false, false, false},
{false, true, false, false, false, true, false, false, false},
{true, false, false, false, true, false, true, false, false},
{false, true, false, true, false, true, false, true, false},
{false, false, true, false, true, false, false, false, true},
{false, false, false, true, false, false, false, true, false},
{false, false, false, false, true, false, true, false, true},
{false, false, false, false, false, true, false, true, false},
},
thingList: []byte{
Potion, Scroll, Ring, Stick, Food, Weapon, Armor, Stairs, Gold, Amulet,
},
identList: []helpEntry{
{'|', "wall of a room", false},
{'-', "wall of a room", false},
{Gold, goldName, false},
{Stairs, "a staircase", false},
{Door, "door", false},
{Floor, "room floor", false},
{PlayerCh, "you", false},
{Passage, "passage", false},
{Trap, "trap", false},
{Potion, potionName, false},
{Scroll, scrollName, false},
{Food, "food", false},
{Weapon, "weapon", false},
{' ', "solid rock", false},
{Armor, "armor", false},
{Amulet, "the Amulet of Yendor", false},
{Ring, ringName, false},
{Stick, "wand or staff", false},
},
hungerStateName: [4]string{"", "Hungry", "Weak", "Faint"},
ripArt: []string{
" __________",
" / \\",
" / REST \\",
" / IN \\",
" / PEACE \\",
" / \\",
ripWall,
ripWall,
" | killed by a |",
ripWall,
" | 1980 |",
" *| * * * | *",
" ________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______",
},
killnameTable: []helpEntry{
{'a', "arrow", true},
{'b', "bolt", true},
{'d', "dart", true},
{'h', "hypothermia", false},
{'s', "starvation", false},
},
scoreReasons: [4]string{
"killed",
"quit",
"A total winner",
"killed with Amulet",
},
}
}
// Version strings (vers.c). The encstr/statlist XOR keys are not ported:

View File

@@ -13,14 +13,16 @@ func TestProbabilitiesSumTo100(t *testing.T) {
return s
}
data := newGameData()
tables := map[string][]ObjInfo{
"things": baseThings[:],
"potions": basePotInfo[:],
"scrolls": baseScrInfo[:],
"rings": baseRingInfo[:],
"sticks": baseWsInfo[:],
"weapons": baseWeapInfo[:NumWeaponTypes], // excludes the flame entry
"armor": baseArmInfo[:],
"things": data.baseThings[:],
"potions": data.basePotInfo[:],
"scrolls": data.baseScrInfo[:],
"rings": data.baseRingInfo[:],
"sticks": data.baseWsInfo[:],
"weapons": data.baseWeapInfo[:NumWeaponTypes], // excludes the flame entry
"armor": data.baseArmInfo[:],
}
for name, tab := range tables {
if s := sum(tab); s != 100 {
@@ -88,11 +90,12 @@ func TestNewGameRandomizesAppearances(t *testing.T) {
}
func TestMonsterTable(t *testing.T) {
if monsterTable[0].Name != "aquator" || monsterTable[25].Name != "zombie" {
data := newGameData()
if data.monsterTable[0].Name != "aquator" || data.monsterTable[25].Name != "zombie" {
t.Error("monster table order broken")
}
if monsterTable['D'-'A'].Name != "dragon" {
if data.monsterTable['D'-'A'].Name != "dragon" {
t.Error("letter indexing broken")
}
}

View File

@@ -17,9 +17,9 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
switch obj.Kind {
case KindPotion:
g.nameit(&pb, obj, "potion", it.PotColors[which], &it.Potions[which], nullstr)
g.nameit(&pb, obj, potionName, it.PotColors[which], &it.Potions[which], nullstr)
case KindRing:
g.nameit(&pb, obj, "ring", it.RingStones[which], &it.Rings[which], ringNum)
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)
case KindScroll:
@@ -78,7 +78,7 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
case KindArmor:
sp := it.Armors[which].Name
if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s [", num(aClass[which]-obj.ArmorClass, 0, Armor), sp)
fmt.Fprintf(&pb, "%s %s [", num(g.data.aClass[which]-obj.ArmorClass, 0, Armor), sp)
if !g.Options.Terse {
pb.WriteString("protection ")
@@ -257,7 +257,7 @@ func (g *RogueGame) newThing() *Object {
cur.Kind = KindArmor
cur.Which = pickOne(g, g.Items.Armors[:])
cur.ArmorClass = aClass[cur.Which]
cur.ArmorClass = g.data.aClass[cur.Which]
if r := g.rnd(100); r < 20 {
cur.Flags.Set(Cursed)
cur.ArmorClass += g.rnd(3) + 1
@@ -536,11 +536,11 @@ func (g *RogueGame) nothing(typ byte) string {
switch typ {
case Potion:
tystr = "potion"
tystr = potionName
case Scroll:
tystr = "scroll"
tystr = scrollName
case Ring:
tystr = "ring"
tystr = ringName
case Stick:
tystr = "stick"
}

View File

@@ -210,16 +210,6 @@ const (
NumTrapTypes = 8
)
// String returns the trap's display name, article included, as the C
// tr_name table had it.
func (t TrapKind) String() string {
if t < 0 || t >= NumTrapTypes {
return "a bizarre trap"
}
return trName[t]
}
// PotionKind identifies a potion (rogue.h potion types).
type PotionKind int
@@ -242,15 +232,6 @@ const (
NumPotionTypes
)
// String returns the potion's true name ("healing", "haste self", ...).
func (p PotionKind) String() string {
if p < 0 || p >= NumPotionTypes {
return "strange potion"
}
return basePotInfo[p].Name
}
// ScrollKind identifies a scroll (rogue.h scroll types).
type ScrollKind int
@@ -277,15 +258,6 @@ const (
NumScrollTypes
)
// String returns the scroll's true name ("magic mapping", ...).
func (s ScrollKind) String() string {
if s < 0 || s >= NumScrollTypes {
return "strange scroll"
}
return baseScrInfo[s].Name
}
// WeaponKind identifies a weapon (rogue.h weapon types).
type WeaponKind int
@@ -307,15 +279,6 @@ const (
// just past them in the tables (C's MAXWEAPONS == FLAME).
const NumWeaponTypes = WeaponFlame
// String returns the weapon's name ("mace", "two handed sword", ...).
func (w WeaponKind) String() string {
if w < 0 || w > WeaponFlame {
return "strange weapon"
}
return baseWeapInfo[w].Name
}
// ArmorKind identifies a suit of armor (rogue.h armor types).
type ArmorKind int
@@ -332,15 +295,6 @@ const (
NumArmorTypes
)
// String returns the armor's name ("ring mail", "plate mail", ...).
func (a ArmorKind) String() string {
if a < 0 || a >= NumArmorTypes {
return "strange armor"
}
return baseArmInfo[a].Name
}
// RingKind identifies a ring (rogue.h ring types).
type RingKind int
@@ -363,15 +317,6 @@ const (
NumRingTypes
)
// String returns the ring's true name ("add strength", "stealth", ...).
func (r RingKind) String() string {
if r < 0 || r >= NumRingTypes {
return "strange ring"
}
return baseRingInfo[r].Name
}
// WandKind identifies a wand or staff (rogue.h rod/wand/staff types).
type WandKind int
@@ -394,15 +339,6 @@ const (
NumWandTypes
)
// String returns the wand/staff's true name ("lightning", ...).
func (w WandKind) String() string {
if w < 0 || w >= NumWandTypes {
return "strange stick"
}
return baseWsInfo[w].Name
}
// Coord is a position on the level (rogue.h coord). A value type: the C
// ce(a,b) macro is plain == here.
type Coord struct {

View File

@@ -143,27 +143,18 @@ func (g *RogueGame) wield() {
g.msg("wielding %s (%c)", sp, obj.PackCh)
}
// initWeaps is the weapons.c init_dam[] table.
var initWeaps = [NumWeaponTypes]struct {
// weaponSetup is one row of the weapons.c init_dam[] table (see
// gameData.initWeaps).
type weaponSetup struct {
dam DiceSpec // damage when wielded
hrl DiceSpec // damage when thrown
launch WeaponKind // launching weapon
flags ObjFlags
}{
{dice("2x4"), dice("1x3"), noWeapon, 0}, // WeaponMace
{dice("3x4"), dice("1x2"), noWeapon, 0}, // Long sword
{dice("1x1"), dice("1x1"), noWeapon, 0}, // WeaponBow
{dice("1x1"), dice("2x3"), WeaponBow, Stackable | Missile}, // WeaponArrow
{dice("1x6"), dice("1x4"), noWeapon, Missile}, // WeaponDagger
{dice("4x4"), dice("1x2"), noWeapon, 0}, // 2h sword
{dice("1x1"), dice("1x3"), noWeapon, Stackable | Missile}, // WeaponDart
{dice("1x2"), dice("2x4"), noWeapon, Stackable | Missile}, // Shuriken
{dice("2x3"), dice("1x6"), noWeapon, Missile}, // WeaponSpear
}
// initWeapon sets up a new weapon (weapons.c init_weapon).
func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) {
iwp := &initWeaps[which]
iwp := &g.data.initWeaps[which]
weap.Kind = KindWeapon
weap.Which = int(which)
weap.Damage = iwp.dam

View File

@@ -46,7 +46,7 @@ func (g *RogueGame) createObj() {
obj.HPlus += g.rnd(3) + 1
}
} else {
obj.ArmorClass = aClass[obj.Which]
obj.ArmorClass = g.data.aClass[obj.Which]
if bless == '-' {
obj.ArmorClass += g.rnd(3) + 1
}