214 lines
7.2 KiB
Go
214 lines
7.2 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package rogue_test
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/applicationoverrides/rogue"
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/presentation"
|
|
"git.stormux.org/storm/fenrir/internal/reader"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
)
|
|
|
|
const initialStatus = "Level: 1 Gold: 0 Hp: 12(12) Str: 16(16) Arm: 4 Exp: 1/0"
|
|
|
|
func game(rows, cols int, message, status string) screen.Snapshot {
|
|
s := screen.Snapshot{Terminal: 1, Active: true, Rows: rows, Cols: cols, X: 10, Y: 5, Cells: make([]rune, rows*cols)}
|
|
for index := range s.Cells {
|
|
s.Cells[index] = ' '
|
|
}
|
|
copy(s.Line(0), []rune(message))
|
|
copy(s.Line(4)[8:], []rune("-----"))
|
|
copy(s.Line(5)[8:], []rune("|.@.+"))
|
|
copy(s.Line(6)[8:], []rune("----|"))
|
|
copy(s.Line(23), []rune(status))
|
|
return s
|
|
}
|
|
|
|
func texts(messages []presentation.Message) []string {
|
|
var result []string
|
|
for _, message := range messages {
|
|
if message.Text != "" {
|
|
result = append(result, message.Text)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestNamedRegionsAndVisibleMap(t *testing.T) {
|
|
for _, size := range [][2]int{{24, 80}, {32, 100}} {
|
|
a := &rogue.Adapter{}
|
|
s := game(size[0], size[1], "Welcome", initialStatus)
|
|
s.Line(5)[14], s.Line(8)[10], s.Line(9)[10] = 'B', '%', '!'
|
|
frame := a.Observe(s)
|
|
if !frame.Recognized || !frame.OwnsPresentation {
|
|
t.Fatal("game not recognized")
|
|
}
|
|
for _, trial := range []struct {
|
|
id command.ID
|
|
want string
|
|
}{
|
|
{command.ReadLiveRegion1, "Health: 12 of 12"},
|
|
{command.ReadLiveRegion2, "Status: " + initialStatus},
|
|
{command.ReadLiveRegion3, "Latest message: Welcome"},
|
|
{command.ReadLiveRegion4, "Wall, north 1"},
|
|
{command.ReadLiveRegion5, "Visible monsters: bat, east 4"},
|
|
{command.ReadLiveRegion6, "Stairs, south 3"},
|
|
} {
|
|
got, handled := a.Command(trial.id)
|
|
if !handled || !strings.Contains(got.Text, trial.want) {
|
|
t.Fatalf("%s: %+v", trial.id, got)
|
|
}
|
|
}
|
|
if got, _ := a.Command(command.ListReadingRegions); !strings.Contains(got.Text, "Live region 1: Health") || !strings.Contains(got.Text, "Live region 6: Visible objects and landmarks") {
|
|
t.Fatal(got)
|
|
}
|
|
if _, handled := a.Command(command.ReadLiveRegion7); handled {
|
|
t.Fatal("unassigned slot captured")
|
|
}
|
|
if got, _ := a.Command(command.ReadLiveRegion5); strings.Contains(got.Text, "dragon") {
|
|
t.Fatal("invented hidden monster")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMessagesHealthAndHungerWithoutMapChatter(t *testing.T) {
|
|
a := &rogue.Adapter{}
|
|
r := reader.New()
|
|
update := func(s screen.Snapshot) []string { return texts(r.Update_application(s, a.Observe(s))) }
|
|
update(game(24, 80, "", initialStatus))
|
|
if got := update(game(24, 80, "", initialStatus)); len(got) != 0 {
|
|
t.Fatal("idle chatter", got)
|
|
}
|
|
damaged := strings.Replace(initialStatus, "12(12)", "9(12)", 1) + " Hungry"
|
|
got := update(game(24, 80, "The bat hits you", damaged))
|
|
if !slices.Equal(got, []string{"The bat hits you", "Health: 9 of 12", "Hungry"}) {
|
|
t.Fatal(got)
|
|
}
|
|
if got := update(game(24, 80, "The bat hits you", damaged)); len(got) != 0 {
|
|
t.Fatal("repeated snapshot", got)
|
|
}
|
|
update(game(24, 80, "", damaged))
|
|
if got := update(game(24, 80, "The bat hits you", damaged)); !slices.Equal(got, []string{"The bat hits you"}) {
|
|
t.Fatal("repeated combat lost", got)
|
|
}
|
|
if got := update(game(24, 80, "The bat hits you--More--", damaged)); len(got) != 1 || !strings.Contains(got[0], "More") {
|
|
t.Fatal("paging prompt lost", got)
|
|
}
|
|
if got := update(game(24, 80, "", initialStatus)); !slices.Contains(got, "Not hungry") {
|
|
t.Fatal("hunger recovery", got)
|
|
}
|
|
r.Configure(false, true)
|
|
if got := update(game(24, 80, "A new message", damaged)); len(got) != 0 {
|
|
t.Fatal("automatic disabled", got)
|
|
}
|
|
if got, _ := a.Command(command.ReadLiveRegion3); got.Text != "Latest message: A new message" {
|
|
t.Fatal("explicit read disabled", got)
|
|
}
|
|
}
|
|
|
|
func TestNewMonstersAndLandmarks(t *testing.T) {
|
|
a := &rogue.Adapter{}
|
|
a.Observe(game(24, 80, "", initialStatus))
|
|
s := game(24, 80, "", initialStatus)
|
|
s.Line(5)[14], s.Line(8)[10] = 'B', '%'
|
|
if got := texts(a.Observe(s).Updates); !slices.Equal(got, []string{"bat, east 4", "Stairs, south 3"}) {
|
|
t.Fatal(got)
|
|
}
|
|
s = game(24, 80, "", initialStatus)
|
|
s.Line(5)[13], s.Line(8)[10] = 'B', '%'
|
|
if got := texts(a.Observe(s).Updates); len(got) != 0 {
|
|
t.Fatal("moving monster chatter", got)
|
|
}
|
|
s = game(24, 80, "", initialStatus)
|
|
s.Line(5)[13], s.Line(8)[10], s.Line(6)[15] = 'B', '%', 'B'
|
|
if got := texts(a.Observe(s).Updates); !slices.Equal(got, []string{"bat, south 1, east 5"}) {
|
|
t.Fatal("second monster", got)
|
|
}
|
|
}
|
|
|
|
func TestStatusChangesAndNewLevelBaseline(t *testing.T) {
|
|
a := &rogue.Adapter{}
|
|
a.Observe(game(24, 80, "", initialStatus))
|
|
s := game(24, 80, "", "Level: 2 Gold: 45 Hp: 9(15) Str: 15(16) Arm: 3 Exp: 2/10 Weak")
|
|
s.Line(10)[10] = 'D'
|
|
want := []string{"Health: 9 of 15", "Dungeon level: 2", "Strength: 15", "Armor: 3", "Experience level: 2", "Weak"}
|
|
if got := texts(a.Observe(s).Updates); !slices.Equal(got, want) {
|
|
t.Fatal("status or level baseline", got)
|
|
}
|
|
}
|
|
|
|
func TestDialogsAndContextBoundaries(t *testing.T) {
|
|
a := &rogue.Adapter{}
|
|
a.Observe(game(24, 80, "Welcome", initialStatus))
|
|
s := game(24, 80, "", initialStatus)
|
|
for index := range s.Cells {
|
|
s.Cells[index] = ' '
|
|
}
|
|
copy(s.Line(0), []rune("a) A potion of healing"))
|
|
copy(s.Line(23), []rune("--Press space to continue--"))
|
|
frame := a.Observe(s)
|
|
if !frame.Recognized || frame.OwnsPresentation {
|
|
t.Fatal("inventory did not use ordinary reading")
|
|
}
|
|
if got, _ := a.Command(command.ReadLiveRegion4); got.Text != "Surroundings: Information unavailable" {
|
|
t.Fatal("stale surroundings", got)
|
|
}
|
|
if got := texts(a.Observe(game(24, 80, "", initialStatus)).Updates); len(got) != 0 {
|
|
t.Fatal("return replayed game", got)
|
|
}
|
|
for _, boundary := range []string{"resize", "terminal", "buffer", "inactive", "shell", "malformed"} {
|
|
t.Run(boundary, func(t *testing.T) {
|
|
a := &rogue.Adapter{}
|
|
a.Observe(game(24, 80, "Old message", initialStatus))
|
|
s := game(24, 80, "", initialStatus)
|
|
switch boundary {
|
|
case "resize":
|
|
s = game(32, 100, "", initialStatus)
|
|
case "terminal":
|
|
s.Terminal++
|
|
case "buffer":
|
|
s.Generation++
|
|
case "inactive":
|
|
s.Active = false
|
|
case "shell":
|
|
for index := range s.Cells {
|
|
s.Cells[index] = ' '
|
|
}
|
|
copy(s.Line(23), []rune("user@host $ "))
|
|
case "malformed":
|
|
s.Cells = s.Cells[:10]
|
|
}
|
|
frame := a.Observe(s)
|
|
if boundary == "inactive" || boundary == "shell" || boundary == "malformed" {
|
|
if frame.Recognized {
|
|
t.Fatal("unrelated screen recognized")
|
|
}
|
|
}
|
|
if got, _ := a.Command(command.ReadLiveRegion3); strings.Contains(got.Text, "Old message") {
|
|
t.Fatal("stale context", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRawReviewStillReadsActualMap(t *testing.T) {
|
|
a, r := &rogue.Adapter{}, reader.New()
|
|
s := game(24, 80, "", initialStatus)
|
|
r.Update_application(s, a.Observe(s))
|
|
if got := r.Command(command.CurrentLine); !strings.Contains(got.Text, "@") {
|
|
t.Fatal(got)
|
|
}
|
|
r.Note_input(true, false, false)
|
|
s = game(24, 80, "", initialStatus)
|
|
s.Line(5)[10], s.Line(5)[11], s.X = '.', '@', 11
|
|
if got := texts(r.Update_application(s, a.Observe(s))); len(got) != 0 {
|
|
t.Fatal("movement spoke map", got)
|
|
}
|
|
}
|