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 - varnamelen # Short names like db, id are idiomatic Go
# Repo-specific exceptions approved by sneak (2026-07-06) # Repo-specific exceptions approved by sneak (2026-07-06)
- paralleltest # Requires t.Parallel() in every test - 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: linters-settings:
lll: 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 sneak's explicit permission. To disable a linter, ask, explaining what
the linter does; he approves specific exceptions, which are recorded the linter does; he approves specific exceptions, which are recorded
in the config's "Repo-specific exceptions" block with the approval 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 with a reason is used sparingly for C-faithfulness (e.g. the authentic
"missle" message spellings) and provably-safe gosec conversions; each "missle" message spellings) and provably-safe gosec conversions; each
needs a justifying comment. needs a justifying comment.

84
TODO.md
View File

@@ -29,22 +29,34 @@ Refactor ground rules:
# Next Step # Next Step
Finish adopting the house Go linting standards. Done so far (branch Refactor step 4: method renames, movement/world subsystem
refactor/lint-adoption): .golangci.yml copied verbatim from the (doMove→moveHero, beTrapped→springTrap, rndmove→randomStep,
prompts repo (plus the sneak-approved paralleltest exception, doRooms/doPassages/doMaze→digRooms/digPassages/digMaze,
2026-07-06); ~1,500 findings fixed (autofix formatting sweep, errcheck/ chgStr→changeStrength, ...); remove the goto/label flows in doMove,
err113/noinlineerr error handling, forbidigo, funcorder, recvcheck dispatch, and saveGame in favor of loops and helpers.
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.
# Completed Steps # 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 - 2026-07-06 Module base path updated to git.eeqj.de/sneak/rgoue
(go.mod, term/ and cmd/ imports, ARCHITECTURE.md, version string). (go.mod, term/ and cmd/ imports, ARCHITECTURE.md, version string).
- 2026-07-06 Refactor step 3 (refactor/object-fields): Object.Arm split - 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 # Future Steps
1. Refactor step 4: method renames, movement/world subsystem 1. Refactor step 5: method renames, items/combat/UI subsystems
(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
(invName→inventoryName, rollEm→rollAttacks, doPot→applyPotionFuse, (invName→inventoryName, rollEm→rollAttacks, doPot→applyPotionFuse,
getItem→promptPackItem returning (obj, ok), getDir→promptDirection); getItem→promptPackItem returning (obj, ok), getDir→promptDirection);
int status codes (attack returning -1) become named results. Two or int status codes (attack returning -1) become named results. Two or
three commits, one subsystem each. 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 owns the msg/addmsg/endmsg machinery; pack/inventory operations move
onto *Player; monster/object list management and map queries onto *Player; monster/object list management and map queries
consolidate onto *Level; RogueGame keeps turn orchestration and consolidate onto *Level; RogueGame keeps turn orchestration and
cross-system effects only. 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, switches become per-kind handler tables of small named methods,
keeping effect order and RNG call sequence identical. keeping effect order and RNG call sequence identical. This step also
5. Refactor step 8: constructor and style pass per the house 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); styleguide — game.New(game.Params{...}) replacing NewGame(Config);
replace the gameEnd panic unwind with error-based turn results where replace the gameEnd panic unwind with error-based turn results where
feasible; 77-column wrap sweep. 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. post-refactor names; add the C name → Go name rename table.
7. Playtest hardening pass: play several full games with the tcell 6. Playtest hardening pass: play several full games with the tcell
binary and extend run_test.go to script a deeper multi-level binary and extend run_test.go to script a deeper multi-level
playthrough (descend past level 5, use potions, scrolls, zapping, playthrough (descend past level 5, use potions, scrolls, zapping,
save/restore). Fix any panics, message mismatches, or divergences save/restore). Fix any panics, message mismatches, or divergences
from the C behavior that this uncovers, with regression tests. from the C behavior that this uncovers, with regression tests.
8. Verify the seed-compatibility claim against the C reference on 7. Verify the seed-compatibility claim against the C reference on
c-master: same seed, same dungeon, same item tables, for several c-master: same seed, same dungeon, same item tables, for several
seeds. seeds.
9. Broaden unit test coverage where playtesting finds thin spots 8. Broaden unit test coverage where playtesting finds thin spots
(rings, sticks, wizard commands). (rings, sticks, wizard commands).
10. Tag a release once a full game (Amulet retrieval and score entry) 9. Tag a release once a full game (Amulet retrieval and score entry)
completes without defects. completes without defects.
11. Full-terminal-size support (deferred by explicit decision 10. Full-terminal-size support (deferred by explicit decision
2026-07-06): per-game dungeon dimensions instead of the 80x24 2026-07-06): per-game dungeon dimensions instead of the 80x24
constants; open design questions are resize policy, gameplay constants; open design questions are resize policy, gameplay
tuning at larger sizes, and a --classic 80x24 mode. 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. 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: case g.Level.Char(g.Delta.Y, g.Delta.X) != Trap:
g.msg("no trap there") g.msg("no trap there")
case p.On(Hallucinating): case p.On(Hallucinating):
g.msg("%s", trName[g.rnd(NumTrapTypes)]) g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)])
default: default:
g.msg("%s", trName[*fp&FTrapMask]) g.msg("%s", g.data.trName[*fp&FTrapMask])
fp.Set(FSeen) fp.Set(FSeen)
} }
} }
@@ -540,9 +540,9 @@ func (g *RogueGame) search() {
} }
if p.On(Hallucinating) { if p.On(Hallucinating) {
g.msg("%s", trName[g.rnd(NumTrapTypes)]) g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)])
} else { } else {
g.msg("%s", trName[*fp&FTrapMask]) g.msg("%s", g.data.trName[*fp&FTrapMask])
fp.Set(FSeen) fp.Set(FSeen)
} }
@@ -584,7 +584,7 @@ func (g *RogueGame) help() {
if helpch != '*' { if helpch != '*' {
g.move(0, 0) g.move(0, 0)
for _, strp := range helpStr { for _, strp := range g.data.helpStr {
if strp.Ch == helpch { if strp.Ch == helpch {
g.Msgs.LowerMsg = true g.Msgs.LowerMsg = true
g.msg("%s%s", unctrl(strp.Ch), strp.Desc) g.msg("%s%s", unctrl(strp.Ch), strp.Desc)
@@ -602,7 +602,7 @@ func (g *RogueGame) help() {
// command mode. // command mode.
numprint := 0 numprint := 0
for _, strp := range helpStr { for _, strp := range g.data.helpStr {
if strp.Print { if strp.Print {
numprint++ numprint++
} }
@@ -622,7 +622,7 @@ func (g *RogueGame) help() {
cnt := 0 cnt := 0
for _, strp := range helpStr { for _, strp := range g.data.helpStr {
if !strp.Print { if !strp.Print {
continue continue
} }
@@ -652,28 +652,6 @@ func (g *RogueGame) help() {
g.refresh() 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). // identify tells the player what a certain thing is (command.c identify).
func (g *RogueGame) identify() { func (g *RogueGame) identify() {
g.msg("what do you want identified? ") g.msg("what do you want identified? ")
@@ -692,7 +670,7 @@ func (g *RogueGame) identify() {
} else { } else {
str = "unknown character" str = "unknown character"
for _, hp := range identList { for _, hp := range g.data.identList {
if hp.Ch == ch { if hp.Ch == ch {
str = hp.Desc 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 // The bestiary and weapon tables must parse to at least one attack each so
// every creature and weapon actually swings. // every creature and weapon actually swings.
func TestTablesHaveDice(t *testing.T) { func TestTablesHaveDice(t *testing.T) {
for i, m := range monsterTable { data := newGameData()
for i, m := range data.monsterTable {
if len(m.Stats.Dmg) == 0 { if len(m.Stats.Dmg) == 0 {
t.Errorf("monster %c (%s) has no attacks", 'A'+i, m.Name) 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 { 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. // 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 // setMname returns the monster name for the given monster (fight.c
// set_mname). // set_mname).
func (g *RogueGame) setMname(tp *Monster) string { func (g *RogueGame) setMname(tp *Monster) string {
@@ -254,7 +217,7 @@ func (g *RogueGame) attack(mp *Monster) int {
p.Stats.Exp = 0 p.Stats.Exp = 0
p.Stats.Lvl = 1 p.Stats.Lvl = 1
} else { } 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) fewer = g.roll(1, 10)
@@ -314,7 +277,7 @@ func (g *RogueGame) attack(mp *Monster) int {
for _, obj := range p.Pack { for _, obj := range p.Pack {
if obj != p.CurArmor && obj != p.CurWeapon && if obj != p.CurArmor && obj != p.CurWeapon &&
obj != p.CurRing[Left] && obj != p.CurRing[Right] && obj != p.CurRing[Left] && obj != p.CurRing[Right] &&
obj.isMagic() { g.isMagic(obj) {
if nobj++; g.rnd(nobj) == 0 { if nobj++; g.rnd(nobj) == 0 {
steal = obj steal = obj
} }
@@ -436,10 +399,10 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool
didHit := false didHit := false
for _, atk := range attacks { 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) proll := g.roll(atk.Count, atk.Sides)
damage := dplus + proll + addDam[att.Str] damage := dplus + proll + g.data.addDam[att.Str]
if damage > 0 { if damage > 0 {
def.HP -= damage def.HP -= damage
} }
@@ -515,7 +478,7 @@ func (g *RogueGame) hit(er, ee string, noend bool) {
i += 4 i += 4
} }
s = hNames[i] s = g.data.hNames[i]
} }
g.addmsgf("%s", s) g.addmsgf("%s", s)
@@ -546,7 +509,7 @@ func (g *RogueGame) miss(er, ee string, noend bool) {
i += 4 i += 4
} }
g.addmsgf("%s", mNames[i]) g.addmsgf("%s", g.data.mNames[i])
if !g.Options.Terse { if !g.Options.Terse {
g.addmsgf(" %s", prname(ee, false)) 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 rogueOpts string // the ROGUEOPTS string, re-parsed by playit as in C
restored bool // game came from a save file; Run skips setup 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 // 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). // and first level arrive with later porting phases).
func NewGame(cfg Config) *RogueGame { func NewGame(cfg Config) *RogueGame {
g := &RogueGame{ g := &RogueGame{
data: newGameData(),
Rng: &Rng{Seed: cfg.Seed}, Rng: &Rng{Seed: cfg.Seed},
Dnum: int(cfg.Seed), Dnum: int(cfg.Seed),
Whoami: cfg.Name, Whoami: cfg.Name,
@@ -176,7 +180,7 @@ func NewGame(cfg Config) *RogueGame {
g.ParseOpts(cfg.RogueOpts) g.ParseOpts(cfg.RogueOpts)
} }
g.Monsters = monsterTable g.Monsters = g.data.monsterTable
g.Items.Group = 2 // weapons.c: int group = 2 g.Items.Group = 2 // weapons.c: int group = 2
for i := range g.Level.Passages { for i := range g.Level.Passages {

View File

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

View File

@@ -146,8 +146,6 @@ type statusCache struct {
init bool init bool
} }
var hungerStateName = [...]string{"", "Hungry", "Weak", "Faint"}
// status displays the important stats line, keeping the cursor where it was // status displays the important stats line, keeping the cursor where it was
// (io.c status). // (io.c status).
func (g *RogueGame) 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", "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, 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, 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 { if g.StatMsg {
g.move(0, 0) g.move(0, 0)
g.msg("%s", line) g.msg("%s", line)

View File

@@ -286,8 +286,8 @@ func (g *RogueGame) checkLevel() {
p := &g.Player p := &g.Player
var i int var i int
for i = 0; eLevels[i] != 0; i++ { for i = 0; g.data.eLevels[i] != 0; i++ {
if eLevels[i] > p.Stats.Exp { if g.data.eLevels[i] > p.Stats.Exp {
break 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 // rndThing picks a random thing appropriate for this level (misc.c
// rnd_thing). // rnd_thing).
func (g *RogueGame) rndThing() byte { func (g *RogueGame) rndThing() byte {
var i int var i int
if g.Depth >= AmuletLevel { if g.Depth >= AmuletLevel {
i = g.rnd(len(thingList)) i = g.rnd(len(g.data.thingList))
} else { } 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 // 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. // 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 // randMonster picks a monster to show up; the lower the level, the meaner
// the monster (monsters.c randmonster). // the monster (monsters.c randmonster).
func (g *RogueGame) randMonster(wander bool) byte { func (g *RogueGame) randMonster(wander bool) byte {
mons := &lvlMons mons := &g.data.lvlMons
if wander { if wander {
mons = &wandMons mons = &g.data.wandMons
} }
for { for {

View File

@@ -255,17 +255,17 @@ func (g *RogueGame) beTrapped(tc Coord) TrapKind {
case 0: case 0:
g.msg("you are suddenly in a parallel dimension") g.msg("you are suddenly in a parallel dimension")
case 1: case 1:
g.msg("the light in here suddenly seems %s", 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: case 2:
g.msg("you feel a sting in the side of your neck") g.msg("you feel a sting in the side of your neck")
case 3: case 3:
g.msg("multi-colored lines swirl around you, then fade") g.msg("multi-colored lines swirl around you, then fade")
case 4: 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: case 5:
g.msg("a spike shoots past your ear!") g.msg("a spike shoots past your ear!")
case 6: 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: case 7:
g.msg("you suddenly feel very thirsty") g.msg("you suddenly feel very thirsty")
case 8: case 8:
@@ -273,7 +273,7 @@ func (g *RogueGame) beTrapped(tc Coord) TrapKind {
case 9: case 9:
g.msg("time now seems to be going slower") g.msg("time now seems to be going slower")
case 10: 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: case TrapSleep:
g.NoCommand += g.spread(5) // SLEEPTIME g.NoCommand += g.spread(5) // SLEEPTIME

View File

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

View File

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

View File

@@ -2,19 +2,6 @@ package game
// passages.c — draw the connecting passages. // 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). // doPassages draws all the passages on a level (passages.c do_passages).
func (g *RogueGame) doPassages() { func (g *RogueGame) doPassages() {
var ( var (
@@ -34,7 +21,7 @@ func (g *RogueGame) doPassages() {
r2 := -1 r2 := -1
for i := range MaxRooms { for i := range MaxRooms {
if rdesConn[r1][i] && !ingraph[i] { if g.data.rdesConn[r1][i] && !ingraph[i] {
if j++; g.rnd(j) == 0 { if j++; g.rnd(j) == 0 {
r2 = i r2 = i
} }
@@ -74,7 +61,7 @@ func (g *RogueGame) doPassages() {
r2 := -1 r2 := -1
for i := range MaxRooms { 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 { if j++; g.rnd(j) == 0 {
r2 = i r2 = i
} }

View File

@@ -13,24 +13,6 @@ type pact struct {
straight string 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). // quaff drinks a potion from the pack (potions.c quaff).
func (g *RogueGame) quaff() { func (g *RogueGame) quaff() {
p := &g.Player p := &g.Player
@@ -100,7 +82,7 @@ func (g *RogueGame) quaff() {
g.scr.Hw.Clear() g.scr.Hw.Clear()
for _, tp := range g.Level.Objects { for _, tp := range g.Level.Objects {
if tp.isMagic() { if g.isMagic(tp) {
show = true show = true
g.scr.Hw.MvAddCh(tp.Pos.Y, tp.Pos.X, Magic) 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 _, mp := range g.Level.Monsters {
for _, tp := range mp.Pack { for _, tp := range mp.Pack {
if tp.isMagic() { if g.isMagic(tp) {
show = true show = true
g.scr.Hw.MvAddCh(mp.Pos.Y, mp.Pos.X, Magic) 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 // raiseLevel: the guy just magically went up a level (potions.c
// raise_level). // raise_level).
func (g *RogueGame) raiseLevel() { 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() g.checkLevel()
} }
// doPot does a potion with standard setup: it uses a fuse and turns on a // doPot does a potion with standard setup: it uses a fuse and turns on a
// flag (potions.c do_pot). // flag (potions.c do_pot).
func (g *RogueGame) doPot(kind PotionKind, knowit bool) { func (g *RogueGame) doPot(kind PotionKind, knowit bool) {
pp := &pActions[kind] pp := &g.data.pActions[kind]
if !g.Items.Potions[kind].Know { if !g.Items.Potions[kind].Know {
g.Items.Potions[kind].Know = knowit 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). // 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 { switch o.Kind {
case KindArmor: 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: case KindWeapon:
return o.HPlus != 0 || o.DPlus != 0 return o.HPlus != 0 || o.DPlus != 0
case KindPotion, KindScroll, KindWand, KindRing, KindAmulet: 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 // ringEat reports how much food the ring on the given hand uses up
// (rings.c ring_eat). // (rings.c ring_eat).
func (g *RogueGame) ringEat(hand int) int { func (g *RogueGame) ringEat(hand int) int {
@@ -166,7 +147,7 @@ func (g *RogueGame) ringEat(hand int) int {
return 0 return 0
} }
eat := ringUses[ring.RingKind()] eat := g.data.ringUses[ring.RingKind()]
if eat < 0 { if eat < 0 {
if g.rnd(-eat) == 0 { if g.rnd(-eat) == 0 {
eat = 1 eat = 1

View File

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

View File

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

View File

@@ -25,13 +25,6 @@ type ScoreEnt struct {
Time int64 Time int64
} }
var scoreReasons = [4]string{
"killed",
"quit",
"A total winner",
"killed with Amulet",
}
// rdScore reads the scoreboard file (save.c rd_score). // rdScore reads the scoreboard file (save.c rd_score).
func (g *RogueGame) rdScore() []ScoreEnt { func (g *RogueGame) rdScore() []ScoreEnt {
topTen := make([]ScoreEnt, numScores) 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, 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 { if scp.Flags == 0 || scp.Flags == 3 {
line += " by " + g.killname(scp.Monster, true) line += " by " + g.killname(scp.Monster, true)
} }

View File

@@ -2,16 +2,6 @@ package game
// scrolls.c — read a scroll and let it happen. // 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 // readScroll reads a scroll from the pack and does the appropriate thing
// (scrolls.c read_scroll). // (scrolls.c read_scroll).
func (g *RogueGame) readScroll() { func (g *RogueGame) readScroll() {
@@ -136,7 +126,7 @@ func (g *RogueGame) readScroll() {
// Identify, let him figure something out // Identify, let him figure something out
g.Items.Scrolls[obj.Which].Know = true g.Items.Scrolls[obj.Which].Know = true
g.msg("this scroll is an %s scroll", g.Items.Scrolls[obj.Which].Name) 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: case ScrollMagicMapping:
// Scroll of magic mapping. // Scroll of magic mapping.
g.Items.Scrolls[ScrollMagicMapping].Know = true g.Items.Scrolls[ScrollMagicMapping].Know = true

View File

@@ -4,6 +4,12 @@ import "fmt"
// sticks.c — zap wands and staffs. // 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). // doZap performs a zap with a wand (sticks.c do_zap).
func (g *RogueGame) doZap() { func (g *RogueGame) doZap() {
p := &g.Player 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). // fixStick sets up a new wand or staff (sticks.c fix_stick).
func (g *RogueGame) fixStick(cur *Object) { func (g *RogueGame) fixStick(cur *Object) {
if g.Items.WandType[cur.Which] == "staff" { if g.Items.WandType[cur.Which] == staffName {
cur.Damage = dice("2x3") cur.Damage = dice("2x3")
} else { } else {
cur.Damage = dice("1x1") cur.Damage = dice("1x1")

View File

@@ -1,247 +1,11 @@
package game package game
// This file is the immutable data from extern.c and init.c: tables that are // tables.go — the static data tables the C game kept as file-scope globals
// never written after program start. Per-game mutable copies (the ObjInfo // (extern.c, init.c, and assorted per-file statics). The port gathers them
// tables, whose probabilities are re-summed and whose Know/Guess fields // into gameData: every RogueGame carries its own copy, so the package holds
// change during play) are cloned into RogueGame.Items by NewGame. // no package-level state. Per-game mutable copies (the ObjInfo tables,
// whose probabilities are re-summed and whose Know/Guess fields change
// initStats is the C INIT_STATS: the player's starting statistics. // during play) are cloned into RogueGame.Items by NewGame.
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",
}
// helpEntry is rogue.h struct h_list. // helpEntry is rogue.h struct h_list.
type helpEntry struct { type helpEntry struct {
@@ -250,73 +14,580 @@ type helpEntry struct {
Print bool Print bool
} }
// helpStr is extern.c helpstr[]: the '?' command help text. // gameData is the bundle of static tables, built by newGameData and hung on
var helpStr = []helpEntry{ // RogueGame as g.data. Nothing in it mutates during play: the one table the
{'?', " prints help", true}, // C code writes to (the venus flytrap damage hack) operates on the game's
{'/', " identify object", true}, // Monsters copy, not on this template.
{'h', " left", true}, type gameData struct {
{'j', " down", true}, // initStats is the C INIT_STATS: the player's starting statistics.
{'k', " up", true}, initStats Stats
{'l', " right", true},
{'y', " up & left", true}, // aClass is extern.c a_class[]: armor class for each armor type.
{'u', " up & right", true}, aClass [NumArmorTypes]int
{'b', " down & left", true},
{'n', " down & right", true}, // eLevels is extern.c e_levels[]: experience thresholds per level; the
{'H', " run left", false}, // zero terminates the table as in C.
{'J', " run down", false}, eLevels []int
{'K', " run up", false},
{'L', " run right", false}, // trName is extern.c tr_name[]: names of the traps.
{'Y', " run up & left", false}, trName [NumTrapTypes]string
{'U', " run up & right", false},
{'B', " run down & left", false}, // invTName is extern.c inv_t_name[]: the inventory style names.
{'N', " run down & right", false}, invTName []string
{CTRL('H'), " run left until adjacent", false},
{CTRL('J'), " run down until adjacent", false}, // monsterTable is extern.c monsters[26]: all monster kinds, indexed by
{CTRL('K'), " run up until adjacent", false}, // letter - 'A'. Monster strength (XX in C) is always 10; HP (___ in C)
{CTRL('L'), " run right until adjacent", false}, // is rolled from the level at creation time.
{CTRL('Y'), " run up & left until adjacent", false}, monsterTable [26]MonsterKind
{CTRL('U'), " run up & right until adjacent", false},
{CTRL('B'), " run down & left until adjacent", false}, // Base ObjInfo tables (extern.c). These are templates: NewGame copies
{CTRL('N'), " run down & right until adjacent", false}, // them into ItemLore before initProbs converts Prob to cumulative form.
{0, " <SHIFT><dir>: run that way", true}, baseThings [NumThings]ObjInfo
{0, " <CTRL><dir>: run till adjacent", true}, baseArmInfo [NumArmorTypes]ObjInfo
{'f', "<dir> fight till death or near death", true}, basePotInfo [NumPotionTypes]ObjInfo
{'t', "<dir> throw something", true}, baseRingInfo [NumRingTypes]ObjInfo
{'m', "<dir> move onto without picking up", true}, baseScrInfo [NumScrollTypes]ObjInfo
{'z', "<dir> zap a wand in a direction", true}, baseWeapInfo [NumWeaponTypes + 1]ObjInfo
{'^', "<dir> identify trap type", true}, baseWsInfo [NumWandTypes]ObjInfo
{'s', " search for trap/secret door", true},
{'>', " go down a staircase", true}, // rainbow is init.c rainbow[]: the possible potion colors.
{'<', " go up a staircase", true}, rainbow []string
{'.', " rest for a turn", true},
{',', " pick something up", true}, // sylls is init.c sylls[]: syllables for generated scroll names.
{'i', " inventory", true}, sylls []string
{'I', " inventory single item", true},
{'q', " quaff potion", true}, // stoneTable is init.c stones[]: ring stones and their worth.
{'r', " read scroll", true}, stoneTable []Stone
{'e', " eat food", true},
{'w', " wield a weapon", true}, // woods is init.c wood[]: what staffs are made of.
{'W', " wear armor", true}, woods []string
{'T', " take armor off", true},
{'P', " put on ring", true}, // metals is init.c metal[]: what wands are made of.
{'R', " remove ring", true}, metals []string
{'d', " drop object", true},
{'c', " call object", true}, // helpStr is extern.c helpstr[]: the '?' command help text.
{'a', " repeat last command", true}, helpStr []helpEntry
{')', " print current weapon", true},
{']', " print current armor", true}, // hNames are the strings for hitting; the first four are used when the
{'=', " print current rings", true}, // player strikes, the second four for monsters (fight.c h_names).
{'@', " print current stats", true}, hNames [8]string
{'D', " recall what's been discovered", true},
{'o', " examine/set options", true}, // mNames are the strings for missing (fight.c m_names).
{CTRL('R'), " redraw screen", true}, mNames [8]string
{CTRL('P'), " repeat last message", true},
{Escape, " cancel command", true}, // strPlus adjusts hit probabilities due to strength (fight.c str_plus).
{'S', " save game", true}, strPlus [32]int
{'Q', " quit", true},
{'!', " shell escape", true}, // addDam adjusts damage done due to strength (fight.c add_dam).
{'F', "<dir> fight till either of you dies", true}, addDam [32]int
{'v', " print version number", true},
// 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: // 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 return s
} }
data := newGameData()
tables := map[string][]ObjInfo{ tables := map[string][]ObjInfo{
"things": baseThings[:], "things": data.baseThings[:],
"potions": basePotInfo[:], "potions": data.basePotInfo[:],
"scrolls": baseScrInfo[:], "scrolls": data.baseScrInfo[:],
"rings": baseRingInfo[:], "rings": data.baseRingInfo[:],
"sticks": baseWsInfo[:], "sticks": data.baseWsInfo[:],
"weapons": baseWeapInfo[:NumWeaponTypes], // excludes the flame entry "weapons": data.baseWeapInfo[:NumWeaponTypes], // excludes the flame entry
"armor": baseArmInfo[:], "armor": data.baseArmInfo[:],
} }
for name, tab := range tables { for name, tab := range tables {
if s := sum(tab); s != 100 { if s := sum(tab); s != 100 {
@@ -88,11 +90,12 @@ func TestNewGameRandomizesAppearances(t *testing.T) {
} }
func TestMonsterTable(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") t.Error("monster table order broken")
} }
if monsterTable['D'-'A'].Name != "dragon" { if data.monsterTable['D'-'A'].Name != "dragon" {
t.Error("letter indexing broken") t.Error("letter indexing broken")
} }
} }

View File

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

View File

@@ -210,16 +210,6 @@ const (
NumTrapTypes = 8 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). // PotionKind identifies a potion (rogue.h potion types).
type PotionKind int type PotionKind int
@@ -242,15 +232,6 @@ const (
NumPotionTypes 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). // ScrollKind identifies a scroll (rogue.h scroll types).
type ScrollKind int type ScrollKind int
@@ -277,15 +258,6 @@ const (
NumScrollTypes 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). // WeaponKind identifies a weapon (rogue.h weapon types).
type WeaponKind int type WeaponKind int
@@ -307,15 +279,6 @@ const (
// just past them in the tables (C's MAXWEAPONS == FLAME). // just past them in the tables (C's MAXWEAPONS == FLAME).
const NumWeaponTypes = WeaponFlame 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). // ArmorKind identifies a suit of armor (rogue.h armor types).
type ArmorKind int type ArmorKind int
@@ -332,15 +295,6 @@ const (
NumArmorTypes 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). // RingKind identifies a ring (rogue.h ring types).
type RingKind int type RingKind int
@@ -363,15 +317,6 @@ const (
NumRingTypes 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). // WandKind identifies a wand or staff (rogue.h rod/wand/staff types).
type WandKind int type WandKind int
@@ -394,15 +339,6 @@ const (
NumWandTypes 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 // Coord is a position on the level (rogue.h coord). A value type: the C
// ce(a,b) macro is plain == here. // ce(a,b) macro is plain == here.
type Coord struct { type Coord struct {

View File

@@ -143,27 +143,18 @@ func (g *RogueGame) wield() {
g.msg("wielding %s (%c)", sp, obj.PackCh) g.msg("wielding %s (%c)", sp, obj.PackCh)
} }
// initWeaps is the weapons.c init_dam[] table. // weaponSetup is one row of the weapons.c init_dam[] table (see
var initWeaps = [NumWeaponTypes]struct { // gameData.initWeaps).
type weaponSetup struct {
dam DiceSpec // damage when wielded dam DiceSpec // damage when wielded
hrl DiceSpec // damage when thrown hrl DiceSpec // damage when thrown
launch WeaponKind // launching weapon launch WeaponKind // launching weapon
flags ObjFlags 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). // initWeapon sets up a new weapon (weapons.c init_weapon).
func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) { func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) {
iwp := &initWeaps[which] iwp := &g.data.initWeaps[which]
weap.Kind = KindWeapon weap.Kind = KindWeapon
weap.Which = int(which) weap.Which = int(which)
weap.Damage = iwp.dam weap.Damage = iwp.dam

View File

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