Rename constructor to game.New(game.Params) per styleguide

NewGame(Config) becomes New(Params), and Restore takes Params too, so
the package's primary type gets the canonical New() constructor with a
named-field Params struct (styleguide points 139, 159). cmd/rogue and
all tests updated; ARCHITECTURE.md constructor references corrected.
Pure rename, suite green.
This commit is contained in:
2026-07-23 05:39:31 +07:00
parent 8241cf4bee
commit cd0ba6c8ee
10 changed files with 57 additions and 57 deletions

View File

@@ -1076,7 +1076,7 @@ that `game` itself has no third-party imports:
go.mod module git.eeqj.de/sneak/rgoue go.mod module git.eeqj.de/sneak/rgoue
cmd/rogue/main.go package main — flag parsing, constructs RogueGame, calls Run cmd/rogue/main.go package main — flag parsing, constructs RogueGame, calls Run
game/ package game — the port, one .go file per .c file game/ package game — the port, one .go file per .c file
game.go RogueGame struct, NewGame, Run (main.c) game.go RogueGame struct, New, Run (main.c)
rng.go seeded LCG (main.c: rnd/roll + RN macro) rng.go seeded LCG (main.c: rnd/roll + RN macro)
types.go Coord, Stats, flags, constants (rogue.h) types.go Coord, Stats, flags, constants (rogue.h)
object.go Object type (rogue.h THING _o arm) object.go Object type (rogue.h THING _o arm)
@@ -1120,7 +1120,7 @@ coherent, but everything is reachable from `g`.
```go ```go
// RogueGame is one complete game of Rogue: all state that was global in the // RogueGame is one complete game of Rogue: all state that was global in the
// C implementation, plus the terminal it plays on. Construct with NewGame, // C implementation, plus the terminal it plays on. Construct with New,
// then call Run. // then call Run.
type RogueGame struct { type RogueGame struct {
// --- identity / RNG --- // --- identity / RNG ---
@@ -1193,15 +1193,15 @@ The command.c/io.c/things.c file-statics become lowercase fields of `RogueGame`,
### Entrypoints ### Entrypoints
```go ```go
func NewGame(opts Config) *RogueGame // Config: seed, name, ROGUEOPTS string, score path, wizard func New(params Params) *RogueGame // Params: seed, name, ROGUEOPTS string, score path, wizard
func (g *RogueGame) Run() error // main.c main()+playit(): init tables, first level, daemons, loop func (g *RogueGame) Run() error // main.c main()+playit(): init tables, first level, daemons, loop
func (g *RogueGame) command() // one turn (command.c) func (g *RogueGame) command() // one turn (command.c)
func Restore(path string, opts Config) (*RogueGame, error) // save.c restore() func Restore(path string, params Params) (*RogueGame, error) // save.c restore()
``` ```
`cmd/rogue/main.go` is a thin shell: flags (`-s` scores, `-d` death demo, `cmd/rogue/main.go` is a thin shell: flags (`-s` scores, `-d` death demo,
save-file arg, `SEED`/`ROGUEOPTS` env), `NewGame(...)` or `Restore(...)`, save-file arg, `SEED`/`ROGUEOPTS` env), `New(...)` or `Restore(...)`, `Run()`,
`Run()`, exit code. exit code.
## 4. Core types ## 4. Core types
@@ -1366,7 +1366,7 @@ type Room struct {
tables that are never written after init** (the only permitted package-level tables that are never written after init** (the only permitted package-level
data — they are ROM, not state): `monsterTable [26]MonsterKind` (name, carry %, data — they are ROM, not state): `monsterTable [26]MonsterKind` (name, carry %,
flags, stats), `initDam` weapon table, base `objInfo` tables (copied into flags, stats), `initDam` weapon table, base `objInfo` tables (copied into
`RogueGame.Items` at NewGame so per-game mutation — probability resumming, `RogueGame.Items` at New so per-game mutation — probability resumming,
`oi_know`, `oi_guess` — stays instance-local), `eLevels`, `rainbow`, `stones`, `oi_know`, `oi_guess` — stays instance-local), `eLevels`, `rainbow`, `stones`,
`woods`, `metals`, `sylls`, trap names, help text, `lvlMons`/`wandMons` spawn `woods`, `metals`, `sylls`, trap names, help text, `lvlMons`/`wandMons` spawn
strings, `hNames`/`mNames` combat messages. strings, `hNames`/`mNames` combat messages.
@@ -1634,6 +1634,6 @@ defers.
- **Determinism test:** scripted input sequence against a fixed seed must - **Determinism test:** scripted input sequence against a fixed seed must
produce identical final state across runs (guards hidden nondeterminism — map produce identical final state across runs (guards hidden nondeterminism — map
iteration, time dependence). iteration, time dependence).
- **Save round-trip:** NewGame → play N scripted turns → save → restore → - **Save round-trip:** New → play N scripted turns → save → restore → compare
compare full state. full state.
- `go vet` + `gofmt` clean at every commit. - `go vet` + `gofmt` clean at every commit.

View File

@@ -29,10 +29,10 @@ func run() int {
flag.Parse() flag.Parse()
cfg := loadConfig() params := loadParams()
if *scores { if *scores {
game.NewGame(cfg).ShowScores() game.New(params).ShowScores()
return 0 return 0
} }
@@ -45,13 +45,13 @@ func run() int {
} }
defer t.Fini() defer t.Fini()
cfg.Term = t params.Term = t
var g *game.RogueGame var g *game.RogueGame
if args := flag.Args(); len(args) == 1 && !*deathDemo { if args := flag.Args(); len(args) == 1 && !*deathDemo {
// restore a saved game // restore a saved game
g, err = game.Restore(args[0], cfg) g, err = game.Restore(args[0], params)
if err != nil { if err != nil {
t.Fini() t.Fini()
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@@ -59,7 +59,7 @@ func run() int {
return 1 return 1
} }
} else { } else {
g = game.NewGame(cfg) g = game.New(params)
} }
if *deathDemo { if *deathDemo {
@@ -81,10 +81,10 @@ func run() int {
return 0 return 0
} }
// loadConfig gathers the game configuration from the environment: home // loadParams gathers the game parameters from the environment: home
// directory, ROGUEOPTS, user name, wizard mode, and the dungeon seed // directory, ROGUEOPTS, user name, wizard mode, and the dungeon seed
// (main.c's startup). // (main.c's startup).
func loadConfig() game.Config { func loadParams() game.Params {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
name := "" name := ""
@@ -96,7 +96,7 @@ func loadConfig() game.Config {
wizard := os.Getenv("ROGUE_WIZARD") != "" wizard := os.Getenv("ROGUE_WIZARD") != ""
return game.Config{ return game.Params{
Seed: chooseSeed(wizard), Seed: chooseSeed(wizard),
Name: name, Name: name,
RogueOpts: os.Getenv("ROGUEOPTS"), RogueOpts: os.Getenv("ROGUEOPTS"),

View File

@@ -7,7 +7,7 @@ import "testing"
func mkGameInput(t *testing.T) *RogueGame { func mkGameInput(t *testing.T) *RogueGame {
t.Helper() t.Helper()
g := NewGame(Config{Seed: 5, Term: &testTerm{}}) g := New(Params{Seed: 5, Term: &testTerm{}})
g.NewLevel() g.NewLevel()
g.Oldpos = g.Player.Pos g.Oldpos = g.Player.Pos
g.Oldrp = g.roomIn(g.Player.Pos) g.Oldrp = g.roomIn(g.Player.Pos)
@@ -183,7 +183,7 @@ func TestZapSlowMonster(t *testing.T) {
} }
func TestParseOpts(t *testing.T) { func TestParseOpts(t *testing.T) {
g := NewGame(Config{Seed: 1}) g := New(Params{Seed: 1})
g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow") g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow")
if !g.Options.Terse { if !g.Options.Terse {

View File

@@ -7,7 +7,7 @@ import "testing"
func mkGame(t *testing.T, seed int32) *RogueGame { func mkGame(t *testing.T, seed int32) *RogueGame {
t.Helper() t.Helper()
g := NewGame(Config{Seed: seed, Term: &testTerm{}}) g := New(Params{Seed: seed, Term: &testTerm{}})
g.NewLevel() g.NewLevel()
g.Oldpos = g.Player.Pos g.Oldpos = g.Player.Pos
g.Oldrp = g.roomIn(g.Player.Pos) g.Oldrp = g.roomIn(g.Player.Pos)

View File

@@ -31,9 +31,9 @@ type Options struct {
InvType int // inven: inventory style (InvOver/InvSlow/InvClear) InvType int // inven: inventory style (InvOver/InvSlow/InvClear)
} }
// Config carries everything needed to construct a game. // Params carries everything needed to construct a game.
type Config struct { type Params struct {
Seed int32 // dungeon number; the caller derives it (time+pid or SEED env) Seed int32 // dungeon number; caller derives it (time+pid or SEED)
Name string // player name (overridden by ROGUEOPTS name=) Name string // player name (overridden by ROGUEOPTS name=)
RogueOpts string // the ROGUEOPTS environment string RogueOpts string // the ROGUEOPTS environment string
Home string // home directory (save file default location) Home string // home directory (save file default location)
@@ -44,7 +44,7 @@ type Config struct {
// RogueGame is one complete game of Rogue: every piece of state that was a // 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 // global (or file-scope static) in the C sources, plus the terminal it is
// played on. Construct with NewGame, then call Run. // played on. Construct with New, then call Run.
// //
// The struct grows with the port; fields appear in the phase that ports the // The struct grows with the port; fields appear in the phase that ports the
// code owning them. // code owning them.
@@ -143,22 +143,22 @@ type RogueGame struct {
data *gameData data *gameData
} }
// NewGame builds a game from cfg, seeds the RNG, and randomizes the item // New builds a game from params, seeds the RNG, and randomizes the item
// appearance tables (the front half of main.c main(); the player roll-up // appearance tables (the front half of main.c main(); the player roll-up
// and first level arrive with later porting phases). // and first level arrive with later porting phases).
func NewGame(cfg Config) *RogueGame { func New(params Params) *RogueGame {
g := &RogueGame{ g := &RogueGame{
data: newGameData(), data: newGameData(),
Rng: &Rng{Seed: cfg.Seed}, Rng: &Rng{Seed: params.Seed},
Dnum: int(cfg.Seed), Dnum: int(params.Seed),
Whoami: cfg.Name, Whoami: params.Name,
Fruit: "slime-mold", Fruit: "slime-mold",
Home: cfg.Home, Home: params.Home,
Wizard: cfg.Wizard, Wizard: params.Wizard,
NoScore: cfg.Wizard, NoScore: params.Wizard,
Playing: true, Playing: true,
Depth: 1, Depth: 1,
ScorePath: cfg.ScorePath, ScorePath: params.ScorePath,
LastScore: -1, LastScore: -1,
} }
g.Options = Options{ g.Options = Options{
@@ -168,17 +168,17 @@ func NewGame(cfg Config) *RogueGame {
} }
g.InvDescribe = true g.InvDescribe = true
g.Msgs.SaveMsg = true g.Msgs.SaveMsg = true
g.scr = NewScreen(cfg.Term) g.scr = NewScreen(params.Term)
g.Msgs.attach(g.scr, g.look, g.readchar) g.Msgs.attach(g.scr, g.look, g.readchar)
g.FileName = cfg.Home + "/rogue.save" g.FileName = params.Home + "/rogue.save"
g.rogueOpts = cfg.RogueOpts g.rogueOpts = params.RogueOpts
if cfg.Wizard { if params.Wizard {
g.Player.Flags.Set(SenseMonsters) g.Player.Flags.Set(SenseMonsters)
} }
if cfg.RogueOpts != "" { if params.RogueOpts != "" {
g.ParseOpts(cfg.RogueOpts) g.ParseOpts(params.RogueOpts)
} }
g.Monsters = g.data.monsterTable g.Monsters = g.data.monsterTable

View File

@@ -8,7 +8,7 @@ import (
func genLevel(t *testing.T, seed int32) *RogueGame { func genLevel(t *testing.T, seed int32) *RogueGame {
t.Helper() t.Helper()
g := NewGame(Config{Seed: seed}) g := New(Params{Seed: seed})
g.NewLevel() g.NewLevel()
return g return g
@@ -159,7 +159,7 @@ func TestNewLevelDeterministic(t *testing.T) {
// mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep. // mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep.
func TestDeeperLevels(t *testing.T) { func TestDeeperLevels(t *testing.T) {
for _, seed := range []int32{7, 42, 1000, 31337} { for _, seed := range []int32{7, 42, 1000, 31337} {
g := NewGame(Config{Seed: seed}) g := New(Params{Seed: seed})
for depth := 1; depth <= 30; depth++ { for depth := 1; depth <= 30; depth++ {
g.Depth = depth g.Depth = depth
g.NewLevel() g.NewLevel()

View File

@@ -10,7 +10,7 @@ import (
func TestRunScriptedSession(t *testing.T) { func TestRunScriptedSession(t *testing.T) {
tt := &testTerm{input: []byte("hjkl.i Qy")} tt := &testTerm{input: []byte("hjkl.i Qy")}
g := NewGame(Config{Seed: 99, Term: tt}) g := New(Params{Seed: 99, Term: tt})
err := g.Run() err := g.Run()
if err != nil { if err != nil {
@@ -52,7 +52,7 @@ func TestRunManyTurns(t *testing.T) {
script = append(script, " Q y Qy"...) script = append(script, " Q y Qy"...)
tt := &testTerm{input: script} tt := &testTerm{input: script}
g := NewGame(Config{Seed: 31337, Term: tt}) g := New(Params{Seed: 31337, Term: tt})
err := g.Run() err := g.Run()
if err != nil { if err != nil {
@@ -68,7 +68,7 @@ func TestRunManyTurns(t *testing.T) {
// wizard style, then descends and keeps playing. // wizard style, then descends and keeps playing.
func TestRunDownStairs(t *testing.T) { func TestRunDownStairs(t *testing.T) {
tt := &testTerm{input: []byte(">..Qy")} tt := &testTerm{input: []byte(">..Qy")}
g := NewGame(Config{Seed: 7, Term: tt}) g := New(Params{Seed: 7, Term: tt})
g.NewLevel() g.NewLevel()
g.Player.Pos = g.Level.Stairs // stand on the stairs g.Player.Pos = g.Level.Stairs // stand on the stairs
g.restored = true // keep Run from regenerating the level g.restored = true // keep Run from regenerating the level
@@ -99,7 +99,7 @@ func TestSaveCommandRoundTrip(t *testing.T) {
// 'S' with no default file name goes straight to the name prompt. // 'S' with no default file name goes straight to the name prompt.
script := "S" + path + "\n" script := "S" + path + "\n"
tt := &testTerm{input: []byte(script)} tt := &testTerm{input: []byte(script)}
g := NewGame(Config{Seed: 55, Term: tt}) g := New(Params{Seed: 55, Term: tt})
g.FileName = "" // force the name prompt g.FileName = "" // force the name prompt
@@ -108,7 +108,7 @@ func TestSaveCommandRoundTrip(t *testing.T) {
t.Fatalf("Run: %v", runErr) t.Fatalf("Run: %v", runErr)
} }
h, err := Restore(path, Config{Term: &testTerm{}}) h, err := Restore(path, Params{Term: &testTerm{}})
if err != nil { if err != nil {
t.Fatalf("Restore: %v", err) t.Fatalf("Restore: %v", err)
} }

View File

@@ -681,7 +681,7 @@ var ErrSaveOutOfDate = errors.New("sorry, saved game is out of date")
// Restore restores a saved game from a file (save.c restore). The file is // Restore restores a saved game from a file (save.c restore). The file is
// deleted, as in C, to defeat restarting from the same save. // deleted, as in C, to defeat restarting from the same save.
func Restore(path string, cfg Config) (*RogueGame, error) { func Restore(path string, params Params) (*RogueGame, error) {
f, err := os.Open(path) //nolint:gosec // G304: the save path is user-chosen by design f, err := os.Open(path) //nolint:gosec // G304: the save path is user-chosen by design
if err != nil { if err != nil {
return nil, err return nil, err
@@ -704,12 +704,12 @@ func Restore(path string, cfg Config) (*RogueGame, error) {
data: newGameData(), data: newGameData(),
Rng: &Rng{}, Rng: &Rng{},
Playing: true, Playing: true,
ScorePath: cfg.ScorePath, ScorePath: params.ScorePath,
FileName: path, FileName: path,
rogueOpts: cfg.RogueOpts, rogueOpts: params.RogueOpts,
restored: true, restored: true,
} }
g.scr = NewScreen(cfg.Term) g.scr = NewScreen(params.Term)
g.Msgs.attach(g.scr, g.look, g.readchar) g.Msgs.attach(g.scr, g.look, g.readchar)
g.applySnapshot(&st) g.applySnapshot(&st)

View File

@@ -29,7 +29,7 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
t.Fatalf("saveFile: %v", saveErr) t.Fatalf("saveFile: %v", saveErr)
} }
h, err := Restore(path, Config{Term: &testTerm{}}) h, err := Restore(path, Params{Term: &testTerm{}})
if err != nil { if err != nil {
t.Fatalf("Restore: %v", err) t.Fatalf("Restore: %v", err)
} }
@@ -154,7 +154,7 @@ func TestRestoreRejectsWrongVersion(t *testing.T) {
t.Fatal(closeErr) t.Fatal(closeErr)
} }
_, restoreErr := Restore(path, Config{}) _, restoreErr := Restore(path, Params{})
if restoreErr == nil { if restoreErr == nil {
t.Error("restore accepted an out-of-date save") t.Error("restore accepted an out-of-date save")
} }

View File

@@ -32,7 +32,7 @@ func TestProbabilitiesSumTo100(t *testing.T) {
} }
func TestInitProbsCumulative(t *testing.T) { func TestInitProbsCumulative(t *testing.T) {
g := NewGame(Config{Seed: 1}) g := New(Params{Seed: 1})
last := g.Items.Potions[NumPotionTypes-1].Prob last := g.Items.Potions[NumPotionTypes-1].Prob
if last != 100 { if last != 100 {
@@ -47,14 +47,14 @@ func TestInitProbsCumulative(t *testing.T) {
} }
func TestNewGameRandomizesAppearances(t *testing.T) { func TestNewGameRandomizesAppearances(t *testing.T) {
g := NewGame(Config{Seed: 12345}) g := New(Params{Seed: 12345})
checkPotionColors(t, g) checkPotionColors(t, g)
checkScrollNames(t, g) checkScrollNames(t, g)
checkWandMaterials(t, g) checkWandMaterials(t, g)
// Determinism: same seed, same appearances. // Determinism: same seed, same appearances.
h := NewGame(Config{Seed: 12345}) h := New(Params{Seed: 12345})
if h.Items != g.Items { if h.Items != g.Items {
t.Error("two games with the same seed produced different item lore") t.Error("two games with the same seed produced different item lore")
} }