Files
rgoue/game/game.go
sneak 3c5add87cd go: port combat, chase driver, traps, zapping, death and scores
- fight.c in full: fight/attack with all eight special monster attacks
  (aquator rust, ice freeze, rattlesnake poison, wraith/vampire drains,
  flytrap hold, leprechaun/nymph theft), swing, roll_em dice parsing
  (with a C-semantics atoi — Go's strconv rejects '1x4/...'), hit/miss
  message variants, killed, remove_mon
- chase.c completed: runners, move_monst, relocate, do_chase with
  dragon breath, chase target selection (C's dead oroom comparison in
  relocate is preserved as-is)
- move.c in full: do_move with passgo turning, all eight traps,
  rndmove, rust_armor
- sticks.c completed: do_zap (all 14 sticks), drain, fire_bolt with
  wall bounces; weapons.c completed: missile/do_motion/fall/wield
- rip.c: death tombstone, total_winner, killname; C exit() becomes a
  gameEnd panic that Run will recover
- score.go: top-ten scoreboard as a gob file with lock-file protocol
  replacing the XOR-encrypted C format
- wizard.c: whatis/set_know/teleport; daemons.c stomach
- monster bestiary moved to per-game state (C mutates the flytrap
  damage string during play)

Tests: combat math, kill/removal bookkeeping, monster chase pursuit,
death unwinding, scripted-input headless terminal.
2026-07-06 19:23:45 +02:00

209 lines
6.6 KiB
Go

package game
// ItemLore is the per-game item identity state: the randomized appearance
// names and the seven mutable ObjInfo tables (extern.c/init.c).
type ItemLore struct {
PotColors [MaxPotions]string // p_colors: colors of the potions
ScrNames [MaxScrolls]string // s_names: names of the scrolls
RingStones [MaxRings]string // r_stones: stone settings of the rings
WandMade [MaxSticks]string // ws_made: what sticks are made of
WandType [MaxSticks]string // ws_type: "wand" or "staff"
Things [NumThings]ObjInfo
Potions [MaxPotions]ObjInfo
Scrolls [MaxScrolls]ObjInfo
Rings [MaxRings]ObjInfo
Sticks [MaxSticks]ObjInfo
Weapons [MaxWeapons + 1]ObjInfo
Armors [MaxArmors]ObjInfo
Group int // group number for the next stack of missiles (weapons.c `group`)
}
// Options are the user-settable game options (options.c optlist).
type Options struct {
Terse bool // terse: shorter messages
FightFlush bool // flush: flush typeahead during battle
Jump bool // jump: show position only at end of run
SeeFloor bool // seefloor: show the lamp-illuminated floor
PassGo bool // passgo: follow turnings in passageways
Tombstone bool // tombstone: print tombstone when killed
InvType int // inven: inventory style (InvOver/InvSlow/InvClear)
}
// Config carries everything needed to construct a game.
type Config struct {
Seed int32 // dungeon number; the caller derives it (time+pid or SEED env)
Name string // player name (overridden by ROGUEOPTS name=)
RogueOpts string // the ROGUEOPTS environment string
Home string // home directory (save file default location)
ScorePath string // scoreboard file; empty disables scoring
Wizard bool // enable debug commands (implies NoScore)
Term Terminal // the display/input device; nil runs headless
}
// RogueGame is one complete game of Rogue: every piece of state that was a
// global (or file-scope static) in the C sources, plus the terminal it is
// played on. Construct with NewGame, then call Run.
//
// The struct grows with the port; fields appear in the phase that ports the
// code owning them.
type RogueGame struct {
// identity / RNG
Rng *Rng
Dnum int // dungeon number
Whoami string // name of player
Fruit string // favorite fruit
Home string // user's home directory
FileName string // save file name
Wizard bool // true if allows wizard commands
NoScore bool // was a wizard sometime
// the world
Player Player
Level Level
Depth int // C `level`: what level she is on
MaxDepth int // C `max_level`: deepest player has gone
HasAmulet bool // C `amulet`: he found the amulet
SeenStairs bool // have seen the stairs (for lsd)
// turn/command engine
Playing bool // true until he quits
After bool // true if we want after daemons
Again bool // repeating the last command
Count int // number of times to repeat command
NoCommand int // number of turns asleep
NoMove int // number of turns held in place
Quiet int // number of quiet turns
Running bool // true if player is running
RunCh byte // direction player is running
DoorStop bool // stop running when we pass a door
Firstmove bool // first move after setting door_stop
MoveOn bool // next move shouldn't pick up items
ToDeath bool // fighting is to the death!
Kamikaze bool // to_death really to DEATH
HasHit bool // has a "hit" message pending in msg
MaxHit int // max damage done to her in to_death
Take byte // thing she is taking
Delta Coord
DirCh byte
LastComm byte
LastDir byte
LLastComm byte
LLastDir byte
LastPick *Object
LLastPick *Object
lastDelt Coord // misc.c get_dir static last_delt
// dungeon generation working state
maze mazeState // rooms.c maze statics
pnum int // passages.c passnum statics
newpnum bool
chRet Coord // chase.c static ch_ret: where chasing takes you
// daemons/fuses
Daemons DaemonList
// screen / messages
scr *Screen
Msgs MsgLine
statusCache statusCache
invPage invPage // things.c discovery-list pagination statics
// UI state
StatMsg bool // should status() print as a msg()
InvDescribe bool // say which way items are being used
QComm bool // are we executing a 'Q' command?
InShell bool // true if executing a shell
Oldpos Coord // position before last look() call
Oldrp *Room
NObjs int // # items listed in inventory() call
// options and item identity
Options Options
Items ItemLore
// Monsters is the per-game copy of the bestiary: the C code mutates
// the venus flytrap's damage string during play, and the table is
// part of the save state.
Monsters [26]MonsterKind
// scores
LastScore int
AllScore bool
ScorePath string
}
// NewGame builds a game from cfg, seeds the RNG, and randomizes the item
// appearance tables (the front half of main.c main(); the player roll-up
// and first level arrive with later porting phases).
func NewGame(cfg Config) *RogueGame {
g := &RogueGame{
Rng: &Rng{Seed: cfg.Seed},
Dnum: int(cfg.Seed),
Whoami: cfg.Name,
Fruit: "slime-mold",
Home: cfg.Home,
Wizard: cfg.Wizard,
NoScore: cfg.Wizard,
Playing: true,
Depth: 1,
ScorePath: cfg.ScorePath,
LastScore: -1,
}
g.Options = Options{
SeeFloor: true,
Tombstone: true,
InvType: InvOver,
}
g.InvDescribe = true
g.Msgs.SaveMsg = true
g.scr = NewScreen(cfg.Term)
g.FileName = cfg.Home + "/rogue.save"
if cfg.Wizard {
g.Player.Flags.Set(SeeMonst)
}
// TODO(port): parse cfg.RogueOpts once options.go lands.
g.Monsters = monsterTable
g.Items.Group = 2 // weapons.c: int group = 2
for i := range g.Level.Passages {
g.Level.Passages[i].Flags = IsGone | IsDark
}
g.initProbs() // set up prob tables for objects
g.initPlayer() // set up initial player stats
g.initNames() // set up names of scrolls
g.initColors() // set up colors of potions
g.initStones() // set up stone settings of rings
g.initMaterials() // set up materials of wands
return g
}
// quit has the player make certain, then exits (main.c quit). The final
// scoring display arrives with the endgame phase.
func (g *RogueGame) quit(int) {
// Reset the message position in case we got here via an interrupt
if !g.QComm {
g.Msgs.Mpos = 0
}
oy, ox := g.scr.Std.GetYX()
g.msg("really quit?")
if g.readchar() == 'y' {
g.clear()
g.scr.Std.MvPrintw(NumLines-2, 0, "You quit with %d gold pieces", g.Player.Purse)
g.move(NumLines-1, 0)
g.refresh()
g.score(g.Player.Purse, 1, 0)
g.myExit(0)
return
}
g.move(0, 0)
g.clrtoeol()
g.status()
g.move(oy, ox)
g.refresh()
g.Msgs.Mpos = 0
g.Count = 0
g.ToDeath = false
}