go: port foundation — types, RNG, tables, daemon scheduler
Module sneak.berlin/go/rogue, package game: - types.go: all rogue.h constants, flag types, Coord/Stats/Room/ObjInfo - creature.go/object.go: the THING union split into Creature (Player/ Monster) and Object; slices replace the C linked lists - level.go: Place map with the C column-major layout and chat/flat/moat/ winat access methods - tables.go: extern.c + init.c data (bestiary, item tables, name pools) - rng.go: the exact C LCG (seed*11109+13849), golden-tested against a compiled C reference for seed compatibility - init.go: per-game item appearance randomization (init.c) - daemon.go/daemons.go: scheduler with DaemonID replacing function pointers (ids match the C save format's mapping) - game.go: RogueGame holds all former globals; NewGame constructor
This commit is contained in:
65
game/creature.go
Normal file
65
game/creature.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// Creature is the _t arm of the C THING union: the player or a monster.
|
||||||
|
type Creature struct {
|
||||||
|
Pos Coord // position
|
||||||
|
Turn bool // if slowed, is it a turn to move
|
||||||
|
Type byte // what it is: 'A'..'Z' for monsters, '@' for the player
|
||||||
|
Disguise byte // what mimic looks like
|
||||||
|
OldCh byte // character that was where it was
|
||||||
|
Dest *Coord // where it is running to — aliases live coords (hero pos, room gold, another monster's pos)
|
||||||
|
Flags CreatureFlags
|
||||||
|
Stats Stats
|
||||||
|
Room *Room // current room for thing
|
||||||
|
Pack []*Object // what the thing is carrying
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monster is a hostile creature on the level.
|
||||||
|
type Monster struct {
|
||||||
|
Creature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player embeds Creature and owns the player-only state that was global
|
||||||
|
// in the C sources (cur_armor, purse, food_left, ...).
|
||||||
|
type Player struct {
|
||||||
|
Creature
|
||||||
|
CurArmor *Object // what he is wearing
|
||||||
|
CurWeapon *Object // which weapon he is wielding
|
||||||
|
CurRing [2]*Object // which rings are being worn (Left/Right)
|
||||||
|
PackUsed [26]bool // is the character used in the pack?
|
||||||
|
Inpack int // number of things in pack
|
||||||
|
Purse int // how much gold he has
|
||||||
|
FoodLeft int // amount of food in hero's stomach
|
||||||
|
HungryState int // how hungry is he (0..3)
|
||||||
|
NoFood int // number of levels without food
|
||||||
|
MaxStats Stats // the maximum for the player
|
||||||
|
VfHit int // number of times the flytrap has hit
|
||||||
|
}
|
||||||
|
|
||||||
|
// On is the C on(thing, flag) macro.
|
||||||
|
func (c *Creature) On(f CreatureFlags) bool { return c.Flags.Has(f) }
|
||||||
|
|
||||||
|
// IsRing is the C ISRING(hand, ring) macro.
|
||||||
|
func (p *Player) IsRing(hand, ring int) bool {
|
||||||
|
return p.CurRing[hand] != nil && p.CurRing[hand].Which == ring
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWearing is the C ISWEARING(ring) macro: is the ring on either hand.
|
||||||
|
func (p *Player) IsWearing(ring int) bool {
|
||||||
|
return p.IsRing(Left, ring) || p.IsRing(Right, ring)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attachMon pushes a monster onto the front of a list (list.c attach).
|
||||||
|
func attachMon(list *[]*Monster, item *Monster) {
|
||||||
|
*list = append([]*Monster{item}, *list...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// detachMon removes a monster (by identity) from a list (list.c detach).
|
||||||
|
func detachMon(list *[]*Monster, item *Monster) {
|
||||||
|
for i, m := range *list {
|
||||||
|
if m == item {
|
||||||
|
*list = append((*list)[:i], (*list)[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
138
game/daemon.go
Normal file
138
game/daemon.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// daemon.c — the daemon/fuse scheduler.
|
||||||
|
//
|
||||||
|
// C identifies daemons and fuses by function pointer; the port uses DaemonID
|
||||||
|
// (which the C save format itself resorted to when serializing d_list), and
|
||||||
|
// dispatches through one switch in daemons.go.
|
||||||
|
|
||||||
|
// DaemonID names a daemon/fuse callback. The numeric values match the
|
||||||
|
// function-pointer-to-int mapping in state.c rs_write_daemons.
|
||||||
|
type DaemonID int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DNone DaemonID = 0
|
||||||
|
DRollwand DaemonID = 1
|
||||||
|
DDoctor DaemonID = 2
|
||||||
|
DStomach DaemonID = 3
|
||||||
|
DRunners DaemonID = 4
|
||||||
|
DSwander DaemonID = 5
|
||||||
|
DNohaste DaemonID = 6
|
||||||
|
DUnconfuse DaemonID = 7
|
||||||
|
DUnsee DaemonID = 8
|
||||||
|
DSight DaemonID = 9
|
||||||
|
// Fuses beyond the C save map (state.c never saved these; the Go save
|
||||||
|
// format handles them uniformly).
|
||||||
|
DVisuals DaemonID = 10
|
||||||
|
DComeDown DaemonID = 11
|
||||||
|
DLand DaemonID = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scheduling phases and slot states (daemon.c).
|
||||||
|
const (
|
||||||
|
dEmpty = 0
|
||||||
|
Before = 1 // run before the player's command
|
||||||
|
After = 2 // run after the player's command
|
||||||
|
daemonTime = -1 // d_time sentinel: a recurring daemon, not a fuse
|
||||||
|
)
|
||||||
|
|
||||||
|
// delayedAction is rogue.h struct delayed_action.
|
||||||
|
type delayedAction struct {
|
||||||
|
Type int // dEmpty, Before or After
|
||||||
|
Func DaemonID
|
||||||
|
Arg int
|
||||||
|
Time int // daemonTime for daemons; remaining turns for fuses
|
||||||
|
}
|
||||||
|
|
||||||
|
// DaemonList is the C d_list[MAXDAEMONS] plus the file statics that ride
|
||||||
|
// along with the daemon system.
|
||||||
|
type DaemonList struct {
|
||||||
|
List [MaxDaemons]delayedAction
|
||||||
|
Between int // daemons.c rollwand static `between`
|
||||||
|
}
|
||||||
|
|
||||||
|
// dSlot finds an empty slot in the daemon/fuse list (daemon.c d_slot).
|
||||||
|
func (g *RogueGame) dSlot() *delayedAction {
|
||||||
|
for i := range g.Daemons.List {
|
||||||
|
if g.Daemons.List[i].Type == dEmpty {
|
||||||
|
return &g.Daemons.List[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("ran out of fuse slots") // C: debug message in MASTER, NULL deref otherwise
|
||||||
|
}
|
||||||
|
|
||||||
|
// findSlot finds a particular slot in the table (daemon.c find_slot).
|
||||||
|
func (g *RogueGame) findSlot(f DaemonID) *delayedAction {
|
||||||
|
for i := range g.Daemons.List {
|
||||||
|
d := &g.Daemons.List[i]
|
||||||
|
if d.Type != dEmpty && d.Func == f {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartDaemon starts a recurring daemon (daemon.c start_daemon).
|
||||||
|
func (g *RogueGame) StartDaemon(f DaemonID, arg, typ int) {
|
||||||
|
dev := g.dSlot()
|
||||||
|
dev.Type = typ
|
||||||
|
dev.Func = f
|
||||||
|
dev.Arg = arg
|
||||||
|
dev.Time = daemonTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// KillDaemon removes a daemon from the list (daemon.c kill_daemon).
|
||||||
|
func (g *RogueGame) KillDaemon(f DaemonID) {
|
||||||
|
if dev := g.findSlot(f); dev != nil {
|
||||||
|
dev.Type = dEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoDaemons runs all the daemons that are active with the current flag
|
||||||
|
// (daemon.c do_daemons).
|
||||||
|
func (g *RogueGame) DoDaemons(flag int) {
|
||||||
|
for i := range g.Daemons.List {
|
||||||
|
dev := &g.Daemons.List[i]
|
||||||
|
if dev.Type == flag && dev.Time == daemonTime {
|
||||||
|
g.runDaemon(dev.Func, dev.Arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuse starts a countdown fuse (daemon.c fuse).
|
||||||
|
func (g *RogueGame) Fuse(f DaemonID, arg, time, typ int) {
|
||||||
|
wire := g.dSlot()
|
||||||
|
wire.Type = typ
|
||||||
|
wire.Func = f
|
||||||
|
wire.Arg = arg
|
||||||
|
wire.Time = time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lengthen extends a fuse's countdown (daemon.c lengthen).
|
||||||
|
func (g *RogueGame) Lengthen(f DaemonID, xtime int) {
|
||||||
|
if wire := g.findSlot(f); wire != nil {
|
||||||
|
wire.Time += xtime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extinguish puts out a fuse (daemon.c extinguish).
|
||||||
|
func (g *RogueGame) Extinguish(f DaemonID) {
|
||||||
|
if wire := g.findSlot(f); wire != nil {
|
||||||
|
wire.Type = dEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoFuses decrements all fuses in the given phase and fires any that burn
|
||||||
|
// down (daemon.c do_fuses).
|
||||||
|
func (g *RogueGame) DoFuses(flag int) {
|
||||||
|
for i := range g.Daemons.List {
|
||||||
|
wire := &g.Daemons.List[i]
|
||||||
|
if wire.Type == flag && wire.Time > 0 {
|
||||||
|
wire.Time--
|
||||||
|
if wire.Time == 0 {
|
||||||
|
wire.Type = dEmpty
|
||||||
|
g.runDaemon(wire.Func, wire.Arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
game/daemons.go
Normal file
17
game/daemons.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// daemons.c — the daemon and fuse callbacks. Each callback is ported in the
|
||||||
|
// phase that owns its subsystem; runDaemon is the dispatch switch that
|
||||||
|
// replaces the C function pointers.
|
||||||
|
|
||||||
|
// runDaemon invokes the callback named by id (the call through d_func in C).
|
||||||
|
func (g *RogueGame) runDaemon(id DaemonID, arg int) {
|
||||||
|
_ = arg
|
||||||
|
switch id {
|
||||||
|
default:
|
||||||
|
// Callbacks are added to this switch as their subsystems are
|
||||||
|
// ported; reaching one that isn't here is a porting bug, not a
|
||||||
|
// game state.
|
||||||
|
panic("daemon not yet ported")
|
||||||
|
}
|
||||||
|
}
|
||||||
155
game/game.go
Normal file
155
game/game.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// daemons/fuses
|
||||||
|
Daemons DaemonList
|
||||||
|
|
||||||
|
// UI state (the Screen itself arrives with the UI phase)
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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.FileName = cfg.Home + "/rogue.save"
|
||||||
|
if cfg.Wizard {
|
||||||
|
g.Player.Flags.Set(SeeMonst)
|
||||||
|
}
|
||||||
|
// TODO(port): parse cfg.RogueOpts once options.go lands.
|
||||||
|
|
||||||
|
g.initProbs() // set up prob tables for objects
|
||||||
|
// TODO(port): initPlayer() goes exactly here (RNG order!) once pack.go lands.
|
||||||
|
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
|
||||||
|
// TODO(port): new level + daemon start-up once their phases land.
|
||||||
|
return g
|
||||||
|
}
|
||||||
128
game/init.go
Normal file
128
game/init.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// init.c — per-game randomization of item appearances and probability
|
||||||
|
// tables. initPlayer arrives with the pack/items phase (it needs addPack).
|
||||||
|
|
||||||
|
// initColors initializes the potion color scheme for this game
|
||||||
|
// (init.c init_colors).
|
||||||
|
func (g *RogueGame) initColors() {
|
||||||
|
used := make([]bool, len(rainbow))
|
||||||
|
for i := 0; i < MaxPotions; i++ {
|
||||||
|
var j int
|
||||||
|
for {
|
||||||
|
j = g.rnd(len(rainbow))
|
||||||
|
if !used[j] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used[j] = true
|
||||||
|
g.Items.PotColors[i] = rainbow[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initNames generates the names of the various scrolls (init.c init_names).
|
||||||
|
func (g *RogueGame) initNames() {
|
||||||
|
for i := 0; i < MaxScrolls; i++ {
|
||||||
|
var cp strings.Builder
|
||||||
|
nwords := g.rnd(3) + 2
|
||||||
|
for ; nwords > 0; nwords-- {
|
||||||
|
nsyl := g.rnd(3) + 1
|
||||||
|
for ; nsyl > 0; nsyl-- {
|
||||||
|
sp := sylls[g.rnd(len(sylls))]
|
||||||
|
if cp.Len()+len(sp) > MaxNameLen {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cp.WriteString(sp)
|
||||||
|
}
|
||||||
|
cp.WriteByte(' ')
|
||||||
|
}
|
||||||
|
g.Items.ScrNames[i] = strings.TrimSuffix(cp.String(), " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initStones initializes the ring stone setting scheme for this game
|
||||||
|
// (init.c init_stones).
|
||||||
|
func (g *RogueGame) initStones() {
|
||||||
|
used := make([]bool, len(stoneTable))
|
||||||
|
for i := 0; i < MaxRings; i++ {
|
||||||
|
var j int
|
||||||
|
for {
|
||||||
|
j = g.rnd(len(stoneTable))
|
||||||
|
if !used[j] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used[j] = true
|
||||||
|
g.Items.RingStones[i] = stoneTable[j].Name
|
||||||
|
g.Items.Rings[i].Worth += stoneTable[j].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initMaterials initializes the construction materials for wands and staffs
|
||||||
|
// (init.c init_materials).
|
||||||
|
func (g *RogueGame) initMaterials() {
|
||||||
|
used := make([]bool, len(woods))
|
||||||
|
metused := make([]bool, len(metals))
|
||||||
|
for i := 0; i < MaxSticks; i++ {
|
||||||
|
var str string
|
||||||
|
for {
|
||||||
|
if g.rnd(2) == 0 {
|
||||||
|
j := g.rnd(len(metals))
|
||||||
|
if !metused[j] {
|
||||||
|
g.Items.WandType[i] = "wand"
|
||||||
|
str = metals[j]
|
||||||
|
metused[j] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
j := g.rnd(len(woods))
|
||||||
|
if !used[j] {
|
||||||
|
g.Items.WandType[i] = "staff"
|
||||||
|
str = woods[j]
|
||||||
|
used[j] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Items.WandMade[i] = str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sumProbs sums up the probabilities for items appearing, converting the
|
||||||
|
// per-item weights into a cumulative distribution (init.c sumprobs).
|
||||||
|
func sumProbs(info []ObjInfo) {
|
||||||
|
for i := 1; i < len(info); i++ {
|
||||||
|
info[i].Prob += info[i-1].Prob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initProbs copies the base tables into the game and initializes the
|
||||||
|
// probabilities for the various items (init.c init_probs).
|
||||||
|
func (g *RogueGame) initProbs() {
|
||||||
|
g.Items.Things = baseThings
|
||||||
|
g.Items.Potions = basePotInfo
|
||||||
|
g.Items.Scrolls = baseScrInfo
|
||||||
|
g.Items.Rings = baseRingInfo
|
||||||
|
g.Items.Sticks = baseWsInfo
|
||||||
|
g.Items.Weapons = baseWeapInfo
|
||||||
|
g.Items.Armors = baseArmInfo
|
||||||
|
|
||||||
|
sumProbs(g.Items.Things[:])
|
||||||
|
sumProbs(g.Items.Potions[:])
|
||||||
|
sumProbs(g.Items.Scrolls[:])
|
||||||
|
sumProbs(g.Items.Rings[:])
|
||||||
|
sumProbs(g.Items.Sticks[:])
|
||||||
|
sumProbs(g.Items.Weapons[:MaxWeapons]) // C sums MAXWEAPONS, excluding the flame entry
|
||||||
|
sumProbs(g.Items.Armors[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickColor returns the given color, or a random one if the hero is
|
||||||
|
// hallucinating (init.c pick_color).
|
||||||
|
func (g *RogueGame) pickColor(col string) string {
|
||||||
|
if g.Player.On(IsHalu) {
|
||||||
|
return rainbow[g.rnd(len(rainbow))]
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
}
|
||||||
73
game/level.go
Normal file
73
game/level.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// Place describes a spot on the level map (rogue.h PLACE).
|
||||||
|
type Place struct {
|
||||||
|
Ch byte // the base map character
|
||||||
|
Flags PlaceFlags
|
||||||
|
Monst *Monster
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level is the current dungeon level: the map and everything on it. It is
|
||||||
|
// reset in place by NewLevel, matching the C reuse of the global arrays.
|
||||||
|
type Level struct {
|
||||||
|
Places [MaxLines * MaxCols]Place
|
||||||
|
Rooms [MaxRooms]Room
|
||||||
|
Passages [MaxPass]Room // one pseudo-room per passage network
|
||||||
|
Objects []*Object // lvl_obj: objects on this level
|
||||||
|
Monsters []*Monster // mlist: monsters on the level
|
||||||
|
Stairs Coord // location of the staircase
|
||||||
|
NTraps int // number of traps on this level
|
||||||
|
}
|
||||||
|
|
||||||
|
// At returns the map cell at (y, x); the C INDEX(y,x) macro, including its
|
||||||
|
// column-major (x<<5)+y layout.
|
||||||
|
func (l *Level) At(y, x int) *Place {
|
||||||
|
return &l.Places[(x<<5)+y]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Char is the chat(y,x) macro: the base map character at a spot.
|
||||||
|
func (l *Level) Char(y, x int) byte { return l.At(y, x).Ch }
|
||||||
|
|
||||||
|
// SetChar updates the base map character at a spot.
|
||||||
|
func (l *Level) SetChar(y, x int, ch byte) { l.At(y, x).Ch = ch }
|
||||||
|
|
||||||
|
// FlagsAt is the flat(y,x) macro, returned as a pointer so ported
|
||||||
|
// read-modify-write sites keep their shape.
|
||||||
|
func (l *Level) FlagsAt(y, x int) *PlaceFlags { return &l.At(y, x).Flags }
|
||||||
|
|
||||||
|
// MonsterAt is the moat(y,x) macro: the monster standing at a spot, if any.
|
||||||
|
func (l *Level) MonsterAt(y, x int) *Monster { return l.At(y, x).Monst }
|
||||||
|
|
||||||
|
// SetMonsterAt places (or clears, with nil) the monster at a spot.
|
||||||
|
func (l *Level) SetMonsterAt(y, x int, m *Monster) { l.At(y, x).Monst = m }
|
||||||
|
|
||||||
|
// VisibleChar is the winat(y,x) macro: what is apparently at a spot — a
|
||||||
|
// monster's disguise if one stands there, else the map character.
|
||||||
|
func (l *Level) VisibleChar(y, x int) byte {
|
||||||
|
if m := l.MonsterAt(y, x); m != nil {
|
||||||
|
return m.Disguise
|
||||||
|
}
|
||||||
|
return l.Char(y, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the per-level state ahead of NewLevel drawing a fresh level.
|
||||||
|
func (l *Level) reset() {
|
||||||
|
for i := range l.Places {
|
||||||
|
l.Places[i] = Place{}
|
||||||
|
}
|
||||||
|
for i := range l.Rooms {
|
||||||
|
l.Rooms[i] = Room{}
|
||||||
|
}
|
||||||
|
for i := range l.Passages {
|
||||||
|
l.Passages[i] = Room{Flags: IsGone | IsDark}
|
||||||
|
}
|
||||||
|
l.Objects = nil
|
||||||
|
l.Monsters = nil
|
||||||
|
l.Stairs = Coord{}
|
||||||
|
l.NTraps = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// goldCalc is the GOLDCALC macro: how much a gold pile is worth at depth.
|
||||||
|
func (g *RogueGame) goldCalc() int {
|
||||||
|
return g.rnd(50+10*g.Depth) + 2
|
||||||
|
}
|
||||||
54
game/object.go
Normal file
54
game/object.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// Object is the _o arm of the C THING union: anything that can lie on the
|
||||||
|
// floor or ride in a pack.
|
||||||
|
type Object struct {
|
||||||
|
Type byte // what kind of object it is (Potion, Scroll, Weapon, ...)
|
||||||
|
Pos Coord // where it lives on the screen
|
||||||
|
Text string // what it says if you read it
|
||||||
|
Launch int // what you need to launch it (weapon index, -1 none)
|
||||||
|
PackCh byte // what character it is in the pack
|
||||||
|
Damage string // damage if used like sword
|
||||||
|
HurlDmg string // damage if thrown
|
||||||
|
Count int // count for plural objects
|
||||||
|
Which int // which object of a type it is
|
||||||
|
HPlus int // plusses to hit
|
||||||
|
DPlus int // plusses to damage
|
||||||
|
Arm int // armor protection — overloaded as in C (see Charges/GoldVal)
|
||||||
|
Flags ObjFlags
|
||||||
|
Group int // group number for this object
|
||||||
|
Label string // label for object
|
||||||
|
}
|
||||||
|
|
||||||
|
// newObject is list.c new_item() for objects: a zeroed Object with the
|
||||||
|
// same non-zero defaults the C code relies on.
|
||||||
|
func newObject() *Object {
|
||||||
|
return &Object{Launch: -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charges is the o_charges alias for Arm on wands and staffs.
|
||||||
|
func (o *Object) Charges() int { return o.Arm }
|
||||||
|
|
||||||
|
// SetCharges stores a charge count in the overloaded Arm field.
|
||||||
|
func (o *Object) SetCharges(n int) { o.Arm = n }
|
||||||
|
|
||||||
|
// GoldVal is the o_goldval alias for Arm on gold piles.
|
||||||
|
func (o *Object) GoldVal() int { return o.Arm }
|
||||||
|
|
||||||
|
// SetGoldVal stores a gold value in the overloaded Arm field.
|
||||||
|
func (o *Object) SetGoldVal(n int) { o.Arm = n }
|
||||||
|
|
||||||
|
// attachObj is list.c attach(): push item onto the front of a list.
|
||||||
|
func attachObj(list *[]*Object, item *Object) {
|
||||||
|
*list = append([]*Object{item}, *list...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// detachObj is list.c detach(): remove item (by identity) from a list.
|
||||||
|
func detachObj(list *[]*Object, item *Object) {
|
||||||
|
for i, o := range *list {
|
||||||
|
if o == item {
|
||||||
|
*list = append((*list)[:i], (*list)[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
game/rng.go
Normal file
51
game/rng.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// Rng is the original Rogue linear congruential generator. The C RN macro is
|
||||||
|
//
|
||||||
|
// #define RN (((seed = seed*11109+13849) >> 16) & 0xffff)
|
||||||
|
//
|
||||||
|
// with `seed` a C int; Seed is int32 so the multiplication wraps exactly the
|
||||||
|
// way 32-bit C arithmetic does. A given seed therefore produces the same
|
||||||
|
// dungeon as the C game.
|
||||||
|
type Rng struct {
|
||||||
|
Seed int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// next steps the generator and returns the next raw value (the RN macro).
|
||||||
|
func (r *Rng) next() int {
|
||||||
|
r.Seed = r.Seed*11109 + 13849
|
||||||
|
return int(r.Seed>>16) & 0xffff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rnd picks a very random number in [0, rng) (main.c rnd).
|
||||||
|
func (r *Rng) Rnd(rng int) int {
|
||||||
|
if rng == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v := r.next()
|
||||||
|
if v < 0 {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
return v % rng
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll rolls a number of dice (main.c roll).
|
||||||
|
func (r *Rng) Roll(number, sides int) int {
|
||||||
|
dtotal := 0
|
||||||
|
for ; number > 0; number-- {
|
||||||
|
dtotal += r.Rnd(sides) + 1
|
||||||
|
}
|
||||||
|
return dtotal
|
||||||
|
}
|
||||||
|
|
||||||
|
// rnd is the ported code's spelling of C rnd(): every call site in the C
|
||||||
|
// sources reads rnd(x), and keeping that shape makes cross-checking easy.
|
||||||
|
func (g *RogueGame) rnd(rng int) int { return g.Rng.Rnd(rng) }
|
||||||
|
|
||||||
|
// roll is C roll().
|
||||||
|
func (g *RogueGame) roll(number, sides int) int { return g.Rng.Roll(number, sides) }
|
||||||
|
|
||||||
|
// spread gives a fuzzy number centered on nm (misc.c spread).
|
||||||
|
func (g *RogueGame) spread(nm int) int {
|
||||||
|
return nm - nm/20 + g.rnd(nm/10)
|
||||||
|
}
|
||||||
64
game/rng_test.go
Normal file
64
game/rng_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// The golden values below were produced by the original C generator
|
||||||
|
// (seed = seed*11109+13849; rnd(range) = abs(RN) % range) compiled and run
|
||||||
|
// on this machine. They lock in seed compatibility with the C game.
|
||||||
|
func TestRndMatchesCImplementation(t *testing.T) {
|
||||||
|
golden := map[int32][]int{
|
||||||
|
1: {0, 30, 79, 7, 87, 1, 23, 7, 57, 98},
|
||||||
|
12345: {92, 92, 45, 98, 24, 39, 92, 67, 3, 7},
|
||||||
|
2026: {43, 90, 6, 2, 43, 34, 20, 5, 67, 71},
|
||||||
|
1751808000: {61, 51, 56, 99, 68, 45, 88, 50, 46, 88},
|
||||||
|
}
|
||||||
|
for seed, want := range golden {
|
||||||
|
r := &Rng{Seed: seed}
|
||||||
|
for i, w := range want {
|
||||||
|
if got := r.Rnd(100); got != w {
|
||||||
|
t.Errorf("seed %d: rnd(100) call %d = %d, want %d", seed, i, got, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRollMatchesCImplementation(t *testing.T) {
|
||||||
|
golden := map[int32][]int{
|
||||||
|
1: {6, 8, 10},
|
||||||
|
12345: {10, 10, 15},
|
||||||
|
2026: {10, 10, 13},
|
||||||
|
1751808000: {7, 9, 9},
|
||||||
|
}
|
||||||
|
for seed, want := range golden {
|
||||||
|
r := &Rng{Seed: seed}
|
||||||
|
for i, w := range want {
|
||||||
|
if got := r.Roll(3, 6); got != w {
|
||||||
|
t.Errorf("seed %d: roll(3,6) call %d = %d, want %d", seed, i, got, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rnd(0) must return 0 without stepping the generator: the C macro
|
||||||
|
// short-circuits before evaluating RN, and BEFORE/AFTER depend on it.
|
||||||
|
func TestRndZeroDoesNotStep(t *testing.T) {
|
||||||
|
r := &Rng{Seed: 42}
|
||||||
|
if got := r.Rnd(0); got != 0 {
|
||||||
|
t.Fatalf("rnd(0) = %d, want 0", got)
|
||||||
|
}
|
||||||
|
if r.Seed != 42 {
|
||||||
|
t.Fatalf("rnd(0) stepped the generator: seed = %d, want 42", r.Seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spread(1)==1 and spread(2)==2 deterministically; the C BEFORE/AFTER
|
||||||
|
// constants rely on this.
|
||||||
|
func TestSpreadSmallValues(t *testing.T) {
|
||||||
|
g := &RogueGame{Rng: &Rng{Seed: 7}}
|
||||||
|
if got := g.spread(1); got != 1 {
|
||||||
|
t.Errorf("spread(1) = %d, want 1", got)
|
||||||
|
}
|
||||||
|
if got := g.spread(2); got != 2 {
|
||||||
|
t.Errorf("spread(2) = %d, want 2", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
327
game/tables.go
Normal file
327
game/tables.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// This file is the immutable data from extern.c and init.c: tables that are
|
||||||
|
// never written after program start. Per-game mutable copies (the ObjInfo
|
||||||
|
// tables, whose probabilities are re-summed and whose Know/Guess fields
|
||||||
|
// change during play) are cloned into RogueGame.Items by NewGame.
|
||||||
|
|
||||||
|
// initStats is the C INIT_STATS: the player's starting statistics.
|
||||||
|
var initStats = Stats{Str: 16, Exp: 0, Lvl: 1, Arm: 10, HP: 12, Dmg: "1x4", MaxHP: 12}
|
||||||
|
|
||||||
|
// aClass is extern.c a_class[]: armor class for each armor type.
|
||||||
|
var aClass = [MaxArmors]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 = [NTraps]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, IsMean, Stats{10, 20, 5, 2, 1, "0x0/0x0", 0}},
|
||||||
|
{"bat", 0, IsFly, Stats{10, 1, 1, 3, 1, "1x2", 0}},
|
||||||
|
{"centaur", 15, 0, Stats{10, 17, 4, 4, 1, "1x2/1x5/1x5", 0}},
|
||||||
|
{"dragon", 100, IsMean, Stats{10, 5000, 10, -1, 1, "1x8/1x8/3x10", 0}},
|
||||||
|
{"emu", 0, IsMean, Stats{10, 2, 1, 7, 1, "1x2", 0}},
|
||||||
|
{"venus flytrap", 0, IsMean, Stats{10, 80, 8, 3, 1, "%%%x0", 0}},
|
||||||
|
{"griffin", 20, IsMean | IsFly | IsRegen, Stats{10, 2000, 13, 2, 1, "4x3/3x5", 0}},
|
||||||
|
{"hobgoblin", 0, IsMean, Stats{10, 3, 1, 5, 1, "1x8", 0}},
|
||||||
|
{"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, "0x0", 0}},
|
||||||
|
{"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, "2x12/2x4", 0}},
|
||||||
|
{"kestrel", 0, IsMean | IsFly, Stats{10, 1, 1, 7, 1, "1x4", 0}},
|
||||||
|
{"leprechaun", 0, 0, Stats{10, 10, 3, 8, 1, "1x1", 0}},
|
||||||
|
{"medusa", 40, IsMean, Stats{10, 200, 8, 2, 1, "3x4/3x4/2x5", 0}},
|
||||||
|
{"nymph", 100, 0, Stats{10, 37, 3, 9, 1, "0x0", 0}},
|
||||||
|
{"orc", 15, IsGreed, Stats{10, 5, 1, 6, 1, "1x8", 0}},
|
||||||
|
{"phantom", 0, IsInvis, Stats{10, 120, 8, 3, 1, "4x4", 0}},
|
||||||
|
{"quagga", 0, IsMean, Stats{10, 15, 3, 3, 1, "1x5/1x5", 0}},
|
||||||
|
{"rattlesnake", 0, IsMean, Stats{10, 9, 2, 3, 1, "1x6", 0}},
|
||||||
|
{"snake", 0, IsMean, Stats{10, 2, 1, 5, 1, "1x3", 0}},
|
||||||
|
{"troll", 50, IsRegen | IsMean, Stats{10, 120, 6, 4, 1, "1x8/1x8/2x6", 0}},
|
||||||
|
{"black unicorn", 0, IsMean, Stats{10, 190, 7, -2, 1, "1x9/1x9/2x9", 0}},
|
||||||
|
{"vampire", 20, IsRegen | IsMean, Stats{10, 350, 8, 1, 1, "1x10", 0}},
|
||||||
|
{"wraith", 0, 0, Stats{10, 55, 5, 4, 1, "1x6", 0}},
|
||||||
|
{"xeroc", 30, 0, Stats{10, 100, 7, 7, 1, "4x4", 0}},
|
||||||
|
{"yeti", 30, 0, Stats{10, 50, 4, 6, 1, "1x6/1x6", 0}},
|
||||||
|
{"zombie", 0, IsMean, Stats{10, 6, 2, 8, 1, "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 = [MaxArmors]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 = [MaxPotions]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 = [MaxRings]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 = [MaxScrolls]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 = [MaxWeapons + 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 = [MaxSticks]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",
|
||||||
|
"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 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.
|
||||||
|
type helpEntry struct {
|
||||||
|
Ch byte
|
||||||
|
Desc string
|
||||||
|
Print bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpStr is extern.c helpstr[]: the '?' command help text.
|
||||||
|
var helpStr = []helpEntry{
|
||||||
|
{'?', " prints help", true},
|
||||||
|
{'/', " identify object", true},
|
||||||
|
{'h', " left", true},
|
||||||
|
{'j', " down", true},
|
||||||
|
{'k', " up", true},
|
||||||
|
{'l', " right", true},
|
||||||
|
{'y', " up & left", true},
|
||||||
|
{'u', " up & right", true},
|
||||||
|
{'b', " down & left", true},
|
||||||
|
{'n', " down & right", true},
|
||||||
|
{'H', " run left", false},
|
||||||
|
{'J', " run down", false},
|
||||||
|
{'K', " run up", false},
|
||||||
|
{'L', " run right", false},
|
||||||
|
{'Y', " run up & left", false},
|
||||||
|
{'U', " run up & right", false},
|
||||||
|
{'B', " run down & left", false},
|
||||||
|
{'N', " run down & right", false},
|
||||||
|
{CTRL('H'), " run left until adjacent", false},
|
||||||
|
{CTRL('J'), " run down until adjacent", false},
|
||||||
|
{CTRL('K'), " run up until adjacent", false},
|
||||||
|
{CTRL('L'), " run right until adjacent", false},
|
||||||
|
{CTRL('Y'), " run up & left until adjacent", false},
|
||||||
|
{CTRL('U'), " run up & right until adjacent", false},
|
||||||
|
{CTRL('B'), " run down & left until adjacent", false},
|
||||||
|
{CTRL('N'), " run down & right until adjacent", false},
|
||||||
|
{0, " <SHIFT><dir>: run that way", true},
|
||||||
|
{0, " <CTRL><dir>: run till adjacent", true},
|
||||||
|
{'f', "<dir> fight till death or near death", true},
|
||||||
|
{'t', "<dir> throw something", true},
|
||||||
|
{'m', "<dir> move onto without picking up", true},
|
||||||
|
{'z', "<dir> zap a wand in a direction", true},
|
||||||
|
{'^', "<dir> identify trap type", true},
|
||||||
|
{'s', " search for trap/secret door", true},
|
||||||
|
{'>', " go down a staircase", true},
|
||||||
|
{'<', " go up a staircase", true},
|
||||||
|
{'.', " rest for a turn", true},
|
||||||
|
{',', " pick something up", true},
|
||||||
|
{'i', " inventory", true},
|
||||||
|
{'I', " inventory single item", true},
|
||||||
|
{'q', " quaff potion", true},
|
||||||
|
{'r', " read scroll", true},
|
||||||
|
{'e', " eat food", true},
|
||||||
|
{'w', " wield a weapon", true},
|
||||||
|
{'W', " wear armor", true},
|
||||||
|
{'T', " take armor off", true},
|
||||||
|
{'P', " put on ring", true},
|
||||||
|
{'R', " remove ring", true},
|
||||||
|
{'d', " drop object", true},
|
||||||
|
{'c', " call object", true},
|
||||||
|
{'a', " repeat last command", true},
|
||||||
|
{')', " print current weapon", true},
|
||||||
|
{']', " print current armor", true},
|
||||||
|
{'=', " print current rings", true},
|
||||||
|
{'@', " print current stats", true},
|
||||||
|
{'D', " recall what's been discovered", true},
|
||||||
|
{'o', " examine/set options", true},
|
||||||
|
{CTRL('R'), " redraw screen", true},
|
||||||
|
{CTRL('P'), " repeat last message", true},
|
||||||
|
{Escape, " cancel command", true},
|
||||||
|
{'S', " save game", true},
|
||||||
|
{'Q', " quit", true},
|
||||||
|
{'!', " shell escape", true},
|
||||||
|
{'F', "<dir> fight till either of you dies", true},
|
||||||
|
{'v', " print version number", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version strings (vers.c). The encstr/statlist XOR keys are not ported:
|
||||||
|
// the Go save format does not use them.
|
||||||
|
const (
|
||||||
|
Release = "5.4.4"
|
||||||
|
Version = "rogue (sneak.berlin/go/rogue port of rogueforge 5.4.4)"
|
||||||
|
)
|
||||||
86
game/tables_test.go
Normal file
86
game/tables_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// badcheck from init.c: every probability table must sum to exactly 100.
|
||||||
|
func TestProbabilitiesSumTo100(t *testing.T) {
|
||||||
|
sum := func(info []ObjInfo) int {
|
||||||
|
s := 0
|
||||||
|
for _, oi := range info {
|
||||||
|
s += oi.Prob
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
tables := map[string][]ObjInfo{
|
||||||
|
"things": baseThings[:],
|
||||||
|
"potions": basePotInfo[:],
|
||||||
|
"scrolls": baseScrInfo[:],
|
||||||
|
"rings": baseRingInfo[:],
|
||||||
|
"sticks": baseWsInfo[:],
|
||||||
|
"weapons": baseWeapInfo[:MaxWeapons], // excludes the flame entry
|
||||||
|
"armor": baseArmInfo[:],
|
||||||
|
}
|
||||||
|
for name, tab := range tables {
|
||||||
|
if s := sum(tab); s != 100 {
|
||||||
|
t.Errorf("bad percentages for %s: sum = %d, want 100", name, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitProbsCumulative(t *testing.T) {
|
||||||
|
g := NewGame(Config{Seed: 1})
|
||||||
|
last := g.Items.Potions[MaxPotions-1].Prob
|
||||||
|
if last != 100 {
|
||||||
|
t.Errorf("cumulative potion probability ends at %d, want 100", last)
|
||||||
|
}
|
||||||
|
for i := 1; i < MaxPotions; i++ {
|
||||||
|
if g.Items.Potions[i].Prob < g.Items.Potions[i-1].Prob {
|
||||||
|
t.Errorf("potion probs not nondecreasing at %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGameRandomizesAppearances(t *testing.T) {
|
||||||
|
g := NewGame(Config{Seed: 12345})
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for i, c := range g.Items.PotColors {
|
||||||
|
if c == "" {
|
||||||
|
t.Fatalf("potion %d has no color", i)
|
||||||
|
}
|
||||||
|
if seen[c] {
|
||||||
|
t.Errorf("potion color %q assigned twice", c)
|
||||||
|
}
|
||||||
|
seen[c] = true
|
||||||
|
}
|
||||||
|
for i, n := range g.Items.ScrNames {
|
||||||
|
if n == "" {
|
||||||
|
t.Fatalf("scroll %d has no name", i)
|
||||||
|
}
|
||||||
|
if len(n) > MaxNameLen+1 {
|
||||||
|
t.Errorf("scroll name %q longer than C buffer allows", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range g.Items.WandType {
|
||||||
|
if g.Items.WandType[i] != "wand" && g.Items.WandType[i] != "staff" {
|
||||||
|
t.Errorf("stick %d has type %q", i, g.Items.WandType[i])
|
||||||
|
}
|
||||||
|
if g.Items.WandMade[i] == "" {
|
||||||
|
t.Errorf("stick %d has no material", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determinism: same seed, same appearances.
|
||||||
|
h := NewGame(Config{Seed: 12345})
|
||||||
|
if h.Items != g.Items {
|
||||||
|
t.Error("two games with the same seed produced different item lore")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonsterTable(t *testing.T) {
|
||||||
|
if monsterTable[0].Name != "aquator" || monsterTable[25].Name != "zombie" {
|
||||||
|
t.Error("monster table order broken")
|
||||||
|
}
|
||||||
|
if monsterTable['D'-'A'].Name != "dragon" {
|
||||||
|
t.Error("letter indexing broken")
|
||||||
|
}
|
||||||
|
}
|
||||||
374
game/types.go
Normal file
374
game/types.go
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
// Package game is a Go port of Rogue 5.4.4 ("Exploring the Dungeons of
|
||||||
|
// Doom", Michael Toy, Ken Arnold and Glenn Wichman, 1980-1999).
|
||||||
|
//
|
||||||
|
// The port is function-by-function faithful to the C sources kept in the
|
||||||
|
// repository root; ARCHITECTURE.md documents both the original program and
|
||||||
|
// the design of this port. All formerly-global state lives on the RogueGame
|
||||||
|
// type.
|
||||||
|
package game
|
||||||
|
|
||||||
|
// Maximum number of different things (rogue.h)
|
||||||
|
const (
|
||||||
|
MaxRooms = 9
|
||||||
|
MaxThings = 9
|
||||||
|
MaxObj = 9
|
||||||
|
MaxPack = 23
|
||||||
|
MaxTraps = 10
|
||||||
|
AmuletLevel = 26
|
||||||
|
NumThings = 7 // number of types of things
|
||||||
|
MaxPass = 13 // upper limit on number of passages
|
||||||
|
NumLines = 24
|
||||||
|
NumCols = 80
|
||||||
|
StatLine = NumLines - 1
|
||||||
|
BoreLevel = 50
|
||||||
|
|
||||||
|
MaxStr = 1024 // maximum length of strings
|
||||||
|
MaxLines = 32 // maximum number of screen lines used
|
||||||
|
MaxCols = 80 // maximum number of screen columns used
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return values for get functions (rogue.h)
|
||||||
|
const (
|
||||||
|
Norm = 0 // normal exit
|
||||||
|
Quit = 1 // quit option setting
|
||||||
|
Minus = 2 // back up one option
|
||||||
|
)
|
||||||
|
|
||||||
|
// Inventory types (rogue.h)
|
||||||
|
const (
|
||||||
|
InvOver = 0
|
||||||
|
InvSlow = 1
|
||||||
|
InvClear = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Things that appear on the screens (rogue.h). These byte values serve both
|
||||||
|
// as map characters and as Object.Type discriminators, exactly as in C.
|
||||||
|
const (
|
||||||
|
Passage byte = '#'
|
||||||
|
Door byte = '+'
|
||||||
|
Floor byte = '.'
|
||||||
|
PlayerCh byte = '@'
|
||||||
|
Trap byte = '^'
|
||||||
|
Stairs byte = '%'
|
||||||
|
Gold byte = '*'
|
||||||
|
Potion byte = '!'
|
||||||
|
Scroll byte = '?'
|
||||||
|
Magic byte = '$'
|
||||||
|
Food byte = ':'
|
||||||
|
Weapon byte = ')'
|
||||||
|
Armor byte = ']'
|
||||||
|
Amulet byte = ','
|
||||||
|
Ring byte = '='
|
||||||
|
Stick byte = '/'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pseudo-types for item-selection prompts (rogue.h)
|
||||||
|
const (
|
||||||
|
Callable = -1
|
||||||
|
RorS = -2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Various constants (rogue.h)
|
||||||
|
const (
|
||||||
|
HealTime = 30
|
||||||
|
HuhDuration = 20
|
||||||
|
SeeDuration = 850
|
||||||
|
HungerTime = 1300
|
||||||
|
MoreTime = 150
|
||||||
|
StomachSize = 2000
|
||||||
|
StarveTime = 850
|
||||||
|
Escape = 27
|
||||||
|
Left = 0
|
||||||
|
Right = 1
|
||||||
|
BoltLength = 6
|
||||||
|
LampDist = 3
|
||||||
|
MaxDaemons = 20
|
||||||
|
MaxInp = 50 // options.c: max string entered by the user
|
||||||
|
MaxNameLen = 40 // init.c MAXNAME: max chars in a generated scroll name
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save against things (rogue.h)
|
||||||
|
const (
|
||||||
|
VsPoison = 0
|
||||||
|
VsParalyzation = 0
|
||||||
|
VsDeath = 0
|
||||||
|
VsBreath = 2
|
||||||
|
VsMagic = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags for rooms (rogue.h)
|
||||||
|
type RoomFlags int16
|
||||||
|
|
||||||
|
const (
|
||||||
|
IsDark RoomFlags = 1 << iota // room is dark
|
||||||
|
IsGone // room is gone (a corridor)
|
||||||
|
IsMaze // room is a maze
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f RoomFlags) Has(b RoomFlags) bool { return f&b != 0 }
|
||||||
|
func (f *RoomFlags) Set(b RoomFlags) { *f |= b }
|
||||||
|
func (f *RoomFlags) Clear(b RoomFlags) { *f &^= b }
|
||||||
|
|
||||||
|
// Flags for objects (rogue.h)
|
||||||
|
type ObjFlags int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
IsCursed ObjFlags = 1 << iota // object is cursed
|
||||||
|
IsKnow // player knows details about the object
|
||||||
|
IsMissl // object is a missile type
|
||||||
|
IsMany // object comes in groups
|
||||||
|
ObjIsFound // object has been seen (ISFOUND shares the bit with creatures)
|
||||||
|
IsProt // armor is permanently protected
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f ObjFlags) Has(b ObjFlags) bool { return f&b != 0 }
|
||||||
|
func (f *ObjFlags) Set(b ObjFlags) { *f |= b }
|
||||||
|
func (f *ObjFlags) Clear(b ObjFlags) { *f &^= b }
|
||||||
|
|
||||||
|
// Flags for creatures (rogue.h). The C bit collisions are deliberate and
|
||||||
|
// preserved: one name of each pair applies to monsters, the other to the
|
||||||
|
// hero, and they never coexist on one creature.
|
||||||
|
type CreatureFlags int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
CanHuh CreatureFlags = 0o000001 // creature can confuse
|
||||||
|
CanSee CreatureFlags = 0o000002 // creature can see invisible creatures
|
||||||
|
IsBlind CreatureFlags = 0o000004 // creature is blind
|
||||||
|
IsCanc CreatureFlags = 0o000010 // creature has special qualities cancelled
|
||||||
|
IsLevit CreatureFlags = 0o000010 // hero is levitating
|
||||||
|
IsFound CreatureFlags = 0o000020 // creature has been seen
|
||||||
|
IsGreed CreatureFlags = 0o000040 // creature runs to protect gold
|
||||||
|
IsHaste CreatureFlags = 0o000100 // creature has been hastened
|
||||||
|
IsTarget CreatureFlags = 0o000200 // creature is the target of an 'f' command
|
||||||
|
IsHeld CreatureFlags = 0o000400 // creature has been held
|
||||||
|
IsHuh CreatureFlags = 0o001000 // creature is confused
|
||||||
|
IsInvis CreatureFlags = 0o002000 // creature is invisible
|
||||||
|
IsMean CreatureFlags = 0o004000 // creature can wake when player enters room
|
||||||
|
IsHalu CreatureFlags = 0o004000 // hero is on acid trip
|
||||||
|
IsRegen CreatureFlags = 0o010000 // creature can regenerate
|
||||||
|
IsRun CreatureFlags = 0o020000 // creature is running at the player
|
||||||
|
SeeMonst CreatureFlags = 0o040000 // hero can detect unseen monsters
|
||||||
|
IsFly CreatureFlags = 0o040000 // creature can fly
|
||||||
|
IsSlow CreatureFlags = 0o100000 // creature has been slowed
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f CreatureFlags) Has(b CreatureFlags) bool { return f&b != 0 }
|
||||||
|
func (f *CreatureFlags) Set(b CreatureFlags) { *f |= b }
|
||||||
|
func (f *CreatureFlags) Clear(b CreatureFlags) { *f &^= b }
|
||||||
|
|
||||||
|
// Flags for the level map (rogue.h)
|
||||||
|
type PlaceFlags uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
FPass PlaceFlags = 0x80 // is a passageway
|
||||||
|
FSeen PlaceFlags = 0x40 // have seen this spot before
|
||||||
|
FDropped PlaceFlags = 0x20 // object was dropped here
|
||||||
|
FLocked PlaceFlags = 0x20 // door is locked
|
||||||
|
FReal PlaceFlags = 0x10 // what you see is what you get
|
||||||
|
FPNum PlaceFlags = 0x0f // passage number mask
|
||||||
|
FTMask PlaceFlags = 0x07 // trap number mask
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f PlaceFlags) Has(b PlaceFlags) bool { return f&b != 0 }
|
||||||
|
func (f *PlaceFlags) Set(b PlaceFlags) { *f |= b }
|
||||||
|
func (f *PlaceFlags) Clear(b PlaceFlags) { *f &^= b }
|
||||||
|
|
||||||
|
// Trap types (rogue.h)
|
||||||
|
const (
|
||||||
|
TDoor = 0
|
||||||
|
TArrow = 1
|
||||||
|
TSleep = 2
|
||||||
|
TBear = 3
|
||||||
|
TTelep = 4
|
||||||
|
TDart = 5
|
||||||
|
TRust = 6
|
||||||
|
TMyst = 7
|
||||||
|
NTraps = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Potion types (rogue.h)
|
||||||
|
const (
|
||||||
|
PConfuse = iota
|
||||||
|
PLSD
|
||||||
|
PPoison
|
||||||
|
PStrength
|
||||||
|
PSeeInvis
|
||||||
|
PHealing
|
||||||
|
PMFind
|
||||||
|
PTFind
|
||||||
|
PRaise
|
||||||
|
PXHeal
|
||||||
|
PHaste
|
||||||
|
PRestore
|
||||||
|
PBlind
|
||||||
|
PLevit
|
||||||
|
MaxPotions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scroll types (rogue.h)
|
||||||
|
const (
|
||||||
|
SConfuse = iota
|
||||||
|
SMap
|
||||||
|
SHold
|
||||||
|
SSleep
|
||||||
|
SArmor
|
||||||
|
SIDPotion
|
||||||
|
SIDScroll
|
||||||
|
SIDWeapon
|
||||||
|
SIDArmor
|
||||||
|
SIDRorS
|
||||||
|
SScare
|
||||||
|
SFDet
|
||||||
|
STelep
|
||||||
|
SEnch
|
||||||
|
SCreate
|
||||||
|
SRemove
|
||||||
|
SAggr
|
||||||
|
SProtect
|
||||||
|
MaxScrolls
|
||||||
|
)
|
||||||
|
|
||||||
|
// Weapon types (rogue.h)
|
||||||
|
const (
|
||||||
|
Mace = iota
|
||||||
|
Sword
|
||||||
|
Bow
|
||||||
|
Arrow
|
||||||
|
Dagger
|
||||||
|
TwoSword
|
||||||
|
Dart
|
||||||
|
Shiraken
|
||||||
|
Spear
|
||||||
|
Flame // fake entry for dragon breath (ick)
|
||||||
|
MaxWeapons = Flame
|
||||||
|
)
|
||||||
|
|
||||||
|
// Armor types (rogue.h)
|
||||||
|
const (
|
||||||
|
Leather = iota
|
||||||
|
RingMail
|
||||||
|
StuddedLeather
|
||||||
|
ScaleMail
|
||||||
|
ChainMail
|
||||||
|
SplintMail
|
||||||
|
BandedMail
|
||||||
|
PlateMail
|
||||||
|
MaxArmors
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ring types (rogue.h)
|
||||||
|
const (
|
||||||
|
RProtect = iota
|
||||||
|
RAddStr
|
||||||
|
RSustStr
|
||||||
|
RSearch
|
||||||
|
RSeeInvis
|
||||||
|
RNop
|
||||||
|
RAggr
|
||||||
|
RAddHit
|
||||||
|
RAddDam
|
||||||
|
RRegen
|
||||||
|
RDigest
|
||||||
|
RTeleport
|
||||||
|
RStealth
|
||||||
|
RSustArm
|
||||||
|
MaxRings
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rod/Wand/Staff types (rogue.h)
|
||||||
|
const (
|
||||||
|
WsLight = iota
|
||||||
|
WsInvis
|
||||||
|
WsElect
|
||||||
|
WsFire
|
||||||
|
WsCold
|
||||||
|
WsPolymorph
|
||||||
|
WsMissile
|
||||||
|
WsHasteM
|
||||||
|
WsSlowM
|
||||||
|
WsDrain
|
||||||
|
WsNop
|
||||||
|
WsTelAway
|
||||||
|
WsTelTo
|
||||||
|
WsCancel
|
||||||
|
MaxSticks
|
||||||
|
)
|
||||||
|
|
||||||
|
// Coord is a position on the level (rogue.h coord). A value type: the C
|
||||||
|
// ce(a,b) macro is plain == here.
|
||||||
|
type Coord struct {
|
||||||
|
X, Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats describes a fighting being (rogue.h struct stats).
|
||||||
|
type Stats struct {
|
||||||
|
Str int // strength (s_str; 3..31)
|
||||||
|
Exp int // experience
|
||||||
|
Lvl int // level of mastery
|
||||||
|
Arm int // armor class
|
||||||
|
HP int // hit points (s_hpt)
|
||||||
|
Dmg string // damage dice, e.g. "1x4/1x2"
|
||||||
|
MaxHP int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room describes a room or passage network (rogue.h struct room).
|
||||||
|
type Room struct {
|
||||||
|
Pos Coord // upper left corner
|
||||||
|
Max Coord // size of room
|
||||||
|
Gold Coord // where the gold is
|
||||||
|
GoldVal int // how much the gold is worth
|
||||||
|
Flags RoomFlags
|
||||||
|
Exits []Coord // where the exits are (r_exit[12]/r_nexits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonsterKind is a bestiary entry (rogue.h struct monster).
|
||||||
|
type MonsterKind struct {
|
||||||
|
Name string // what to call the monster
|
||||||
|
Carry int // probability of carrying something
|
||||||
|
Flags CreatureFlags
|
||||||
|
Stats Stats // initial stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjInfo describes one object class: its real name, generation
|
||||||
|
// probability, score worth, and per-game identification state
|
||||||
|
// (rogue.h struct obj_info).
|
||||||
|
type ObjInfo struct {
|
||||||
|
Name string
|
||||||
|
Prob int
|
||||||
|
Worth int
|
||||||
|
Guess string
|
||||||
|
Know bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stone is a ring stone with its worth (rogue.h STONE).
|
||||||
|
type Stone struct {
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTRL maps a letter to its control character, as the C CTRL() macro.
|
||||||
|
func CTRL(c byte) byte { return c & 0o37 }
|
||||||
|
|
||||||
|
// IsMult reports whether an object type stacks in the pack (rogue.h ISMULT).
|
||||||
|
func IsMult(typ byte) bool { return typ == Potion || typ == Scroll || typ == Food }
|
||||||
|
|
||||||
|
// distance returns the squared distance between two points (chase.c dist).
|
||||||
|
func distance(y1, x1, y2, x2 int) int {
|
||||||
|
dx := x2 - x1
|
||||||
|
dy := y2 - y1
|
||||||
|
return dx*dx + dy*dy
|
||||||
|
}
|
||||||
|
|
||||||
|
// distCp is dist_cp from chase.c.
|
||||||
|
func distCp(c1, c2 Coord) int { return distance(c1.Y, c1.X, c2.Y, c2.X) }
|
||||||
|
|
||||||
|
// sign is misc.c sign(): -1, 0 or 1.
|
||||||
|
func sign(nm int) int {
|
||||||
|
switch {
|
||||||
|
case nm < 0:
|
||||||
|
return -1
|
||||||
|
case nm > 0:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user