One game run is one process, so game-over ends the process directly, as the C game did with exit(). myExit now restores the terminal (via the new Terminal.Fini) and calls os.Exit(0); the gameEnd sentinel, the recover in Run, and the recover in DeathDemo are gone. Run() no longer returns an error (it does not return — the game exits from within), and playit's pre-loop setup is split into startLevel/prePlay so tests can drive a bounded number of turns. Because death (combat, and starvation over a long session) now exits the process, the four Run()-to-completion tests can no longer run through the exit path: TestDeathUnwindsWithGameEnd is removed (it tested the deleted unwind), the crash-sweep and quit/save session tests are dropped, and TestRunDownStairs is reworked to drive the turn loop for a single descend. Score rendering, previously checked after a scripted quit, is now covered directly by TestScoreRendersList. Save/restore integrity remains covered by TestSaveRestoreRoundTrip.
70 lines
2.0 KiB
Go
70 lines
2.0 KiB
Go
package game
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// driveTurns runs the game's per-turn loop up to n times, doing the same
|
|
// first-level and pre-play setup Run() does. Run() itself no longer
|
|
// returns — game-over exits the process — so tests drive command()
|
|
// directly, with short scripts that avoid quitting, saving, or playing
|
|
// long enough to starve, any of which would exit the test binary.
|
|
func driveTurns(t *testing.T, g *RogueGame, n int) {
|
|
t.Helper()
|
|
|
|
g.startLevel()
|
|
g.prePlay()
|
|
|
|
for range n {
|
|
g.command()
|
|
}
|
|
}
|
|
|
|
// TestRunDownStairs stands the hero on the staircase and descends via the
|
|
// '>' command through the real turn loop, then checks the level changed.
|
|
func TestRunDownStairs(t *testing.T) {
|
|
// '>' is a free action (After=false), so it is followed by a paying
|
|
// rest ('.') to end the command() call; without a paying action the
|
|
// turn loop would spin forever on the auto-fed prompt input.
|
|
tt := &testTerm{input: []byte(">.")}
|
|
g := New(Params{Seed: 7, Term: tt})
|
|
g.NewLevel()
|
|
g.Player.Pos = g.Level.Stairs // stand on the stairs
|
|
g.restored = true // keep startLevel from regenerating
|
|
g.Daemons = DaemonList{} // and give it a fresh daemon table
|
|
g.StartDaemon(DRunners, 0, After)
|
|
g.StartDaemon(DDoctor, 0, After)
|
|
g.Fuse(DSwander, 0, wanderTime(g), After)
|
|
g.StartDaemon(DStomach, 0, After)
|
|
|
|
driveTurns(t, g, 1)
|
|
|
|
if g.Depth != 2 {
|
|
t.Errorf("depth = %d after descending, want 2", g.Depth)
|
|
}
|
|
}
|
|
|
|
// TestScoreRendersList checks that the scoreboard is drawn on the screen
|
|
// (in C it went to stdout after endwin; here it stays on the screen). The
|
|
// quit and death paths that normally show it now exit the process, so the
|
|
// display is exercised through score() directly.
|
|
func TestScoreRendersList(t *testing.T) {
|
|
g := New(Params{Seed: 1, Term: &testTerm{}})
|
|
g.Player.Purse = 100
|
|
|
|
g.score(g.Player.Purse, 1, 0) // flags 1 = quit; posts the top-ten list
|
|
|
|
found := false
|
|
|
|
for y := range NumLines {
|
|
if strings.Contains(g.scr.Std.Line(y), "Top Ten") {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Error("score list not on screen")
|
|
}
|
|
}
|