- command.c in full: the command dispatcher with repeat counts, run prefixes, ctrl-run door-stop mode, fight-to-death targeting, and all wizard debug commands; search, help, identify, d_level/u_level, call, current - main.c completed: Run()/playit() with the C double ROGUEOPTS parse; exit()-anywhere becomes a gameEnd panic recovered in Run - save.c + state.c: gob SaveState snapshot with explicit pointer-to- index fixups for equipment, monster rooms, and chase targets (the rs_fix_thing role); save file deleted on restore as in C; SIGHUP/ SIGTERM autosave hook - wizard.c completed (create_obj, show_map), passages.c add_pass - term/: tcell/v2 Terminal — the one third-party dependency — replacing curses and mdport's 900-line key decoder; shell escape via suspend - cmd/rogue: flags -s/-d, SEED (wizard), ROGUEOPTS, ROGUE_WIZARD Tests: full scripted sessions through Run (quit, descend stairs, 3200-move crash sweep, save-command round trip). Notable fixes found by tests: gob silently drops embedded fields of unexported types, and --More-- prompts swallow space-free input scripts.
275 lines
8.3 KiB
Go
275 lines
8.3 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
|
|
// command.c statics
|
|
countCh byte
|
|
direction byte
|
|
newCount bool
|
|
|
|
// 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
|
|
|
|
rogueOpts string // the ROGUEOPTS string, re-parsed by playit as in C
|
|
restored bool // game came from a save file; Run skips setup
|
|
}
|
|
|
|
// 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"
|
|
g.rogueOpts = cfg.RogueOpts
|
|
if cfg.Wizard {
|
|
g.Player.Flags.Set(SeeMonst)
|
|
}
|
|
if cfg.RogueOpts != "" {
|
|
g.ParseOpts(cfg.RogueOpts)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Run plays the game to its end: the back half of main.c main() plus
|
|
// playit(). It returns after death, victory, quitting, or saving.
|
|
func (g *RogueGame) Run() (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if _, ok := r.(gameEnd); ok {
|
|
return // normal game over / save exit
|
|
}
|
|
panic(r)
|
|
}
|
|
}()
|
|
if !g.restored {
|
|
g.NewLevel() // draw current level
|
|
// Start up daemons and fuses
|
|
g.StartDaemon(DRunners, 0, After)
|
|
g.StartDaemon(DDoctor, 0, After)
|
|
g.Fuse(DSwander, 0, wanderTime(g), After)
|
|
g.StartDaemon(DStomach, 0, After)
|
|
}
|
|
g.playit()
|
|
return nil
|
|
}
|
|
|
|
// playit is the main loop of the program (main.c playit).
|
|
func (g *RogueGame) playit() {
|
|
// set up defaults for modern terminals: curses' md_hasclreol() is
|
|
// always true, so the C default inventory style applies
|
|
if !g.restored {
|
|
g.Options.InvType = InvClear
|
|
}
|
|
// parse environment declaration of options (C parses ROGUEOPTS again
|
|
// here, letting it override the terminal defaults)
|
|
if g.rogueOpts != "" {
|
|
g.ParseOpts(g.rogueOpts)
|
|
}
|
|
|
|
g.Oldpos = g.Player.Pos
|
|
g.Oldrp = g.roomin(g.Player.Pos)
|
|
for g.Playing {
|
|
g.command() // command execution
|
|
}
|
|
g.endit()
|
|
}
|
|
|
|
// endit exits the game (main.c endit).
|
|
func (g *RogueGame) endit() {
|
|
g.fatal("Okay, bye bye!\n")
|
|
}
|
|
|
|
// fatal prints a message and leaves (main.c fatal).
|
|
func (g *RogueGame) fatal(s string) {
|
|
g.mvaddstr(NumLines-2, 0, s)
|
|
g.refresh()
|
|
g.myExit(0)
|
|
}
|
|
|
|
// 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
|
|
}
|