Instrumented the C game on modern-rogue with a DUMP mode (patch in
testdata/c_seedcompat.patch) that forces the RNG seed and prints the
per-seed item appearance tables — potion colors, scroll names, ring
stones, wand/staff materials — in the normal init order, before initscr
so no terminal is needed. Captured its output for four seeds as
testdata/item_tables.golden.
TestSeedCompatItemTables regenerates the same tables from the Go port
via New(Params{Seed, Wizard: true}) and checks they match the golden
byte for byte. They do, for all four seeds — proving the LCG and its
consumption order through the whole init sequence (init_probs →
init_player → init_names → init_colors → init_stones → init_materials,
including init_player's arrow rnd(8)+rnd(15)) agree with C exactly.
testdata/README.md documents how to regenerate the golden.
99 lines
2.4 KiB
Go
99 lines
2.4 KiB
Go
package game
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// dumpItemTables formats a game's per-seed item appearance tables in the
|
|
// same layout the instrumented C reference prints: the potion colors,
|
|
// scroll names, ring stones, and wand/staff materials, each generated by
|
|
// consuming the RNG in a fixed order during New().
|
|
func dumpItemTables(seed int32, g *RogueGame) string {
|
|
var b strings.Builder
|
|
|
|
fmt.Fprintf(&b, "SEED %d\n", seed)
|
|
|
|
fmt.Fprintln(&b, "POTIONS")
|
|
|
|
for _, c := range g.Items.PotColors {
|
|
fmt.Fprintln(&b, c)
|
|
}
|
|
|
|
fmt.Fprintln(&b, "SCROLLS")
|
|
|
|
for _, s := range g.Items.ScrNames {
|
|
fmt.Fprintln(&b, s)
|
|
}
|
|
|
|
fmt.Fprintln(&b, "RINGS")
|
|
|
|
for _, s := range g.Items.RingStones {
|
|
fmt.Fprintln(&b, s)
|
|
}
|
|
|
|
fmt.Fprintln(&b, "STICKS")
|
|
|
|
for i := range g.Items.WandType {
|
|
fmt.Fprintf(&b, "%s %s\n", g.Items.WandType[i], g.Items.WandMade[i])
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// TestSeedCompatItemTables proves the port's seed-compatibility claim: for
|
|
// the same seed, the Go game generates the exact per-seed item appearance
|
|
// tables as the C reference on modern-rogue. That requires the LCG and its
|
|
// consumption order through the whole init sequence (init_probs →
|
|
// init_player → init_names → init_colors → init_stones → init_materials) to
|
|
// match C byte for byte. The golden is captured from an instrumented build
|
|
// of the C game (testdata/README.md).
|
|
func TestSeedCompatItemTables(t *testing.T) {
|
|
golden, err := os.ReadFile("testdata/item_tables.golden")
|
|
if err != nil {
|
|
t.Fatalf("read golden: %v", err)
|
|
}
|
|
|
|
// These must match the seeds the golden was generated from
|
|
// (testdata/README.md).
|
|
seeds := []int32{1, 42, 12345, 99999}
|
|
|
|
var got strings.Builder
|
|
|
|
for _, seed := range seeds {
|
|
g := New(Params{Seed: seed, Wizard: true})
|
|
got.WriteString(dumpItemTables(seed, g))
|
|
}
|
|
|
|
if got.String() != string(golden) {
|
|
t.Errorf("Go item tables diverge from the C reference at %s",
|
|
firstDiff(string(golden), got.String()))
|
|
}
|
|
}
|
|
|
|
// firstDiff returns a description of the first line where want and got
|
|
// differ, for a readable failure.
|
|
func firstDiff(want, got string) string {
|
|
wl := strings.Split(want, "\n")
|
|
gl := strings.Split(got, "\n")
|
|
|
|
for i := 0; i < len(wl) || i < len(gl); i++ {
|
|
w, g := "", ""
|
|
if i < len(wl) {
|
|
w = wl[i]
|
|
}
|
|
|
|
if i < len(gl) {
|
|
g = gl[i]
|
|
}
|
|
|
|
if w != g {
|
|
return fmt.Sprintf("line %d: C=%q Go=%q", i+1, w, g)
|
|
}
|
|
}
|
|
|
|
return "no line difference (trailing content?)"
|
|
}
|