Initial attempt at making rogue work with application overrides.
This commit is contained in:
1 parent
088f706cb4
commit
46dc520854
15 files changed
+1836
-111
No files matched your search
@@ -8,6 +8,7 @@ package applicationoverrides
|
||||
import (
|
||||
"git.stormux.org/storm/fenrir/internal/command"
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
"git.stormux.org/storm/fenrir/internal/i18n"
|
||||
"git.stormux.org/storm/fenrir/internal/presentation"
|
||||
"git.stormux.org/storm/fenrir/internal/screen"
|
||||
)
|
||||
@@ -16,6 +17,19 @@ type Region struct {
|
||||
Name, Text string
|
||||
}
|
||||
|
||||
// Read includes the region's name for an explicit read. Automatic updates can
|
||||
// use Text alone when repeating the name would add unnecessary speech.
|
||||
func (r Region) Read() string {
|
||||
text := r.Text
|
||||
if text == "" {
|
||||
text = i18n.T("Information unavailable")
|
||||
}
|
||||
if r.Name == "" {
|
||||
return text
|
||||
}
|
||||
return i18n.Format("{0}: {1}", r.Name, text)
|
||||
}
|
||||
|
||||
// Frame supplements the ordinary reader. OwnsPresentation replaces generic
|
||||
// automatic diffs, never raw review/copy or typing echo. Updates are independent
|
||||
// of focus: a focus change must not swallow a simultaneous chat message.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package applicationoverrides
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRegionReadIncludesNameAndHandlesMissingInformation(t *testing.T) {
|
||||
for _, trial := range []struct {
|
||||
region Region
|
||||
want string
|
||||
}{
|
||||
{Region{Name: "Health", Text: "12 of 12"}, "Health: 12 of 12"},
|
||||
{Region{Name: "Health"}, "Health: Information unavailable"},
|
||||
{Region{Text: "12 of 12"}, "12 of 12"},
|
||||
} {
|
||||
if got := trial.region.Read(); got != trial.want {
|
||||
t.Fatalf("%q, want %q", got, trial.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Package rogue interprets classic Rogue 5.4's visible terminal display.
|
||||
// Layout and glyphs follow RogueForge's io.c, command.c, and rogue.h:
|
||||
// https://github.com/rogueforge/rogue5.4/tree/d4ff90dc41053f1962efbf32c7b71b12503b9b62
|
||||
package rogue
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
|
||||
"git.stormux.org/storm/fenrir/internal/command"
|
||||
"git.stormux.org/storm/fenrir/internal/config"
|
||||
"git.stormux.org/storm/fenrir/internal/i18n"
|
||||
"git.stormux.org/storm/fenrir/internal/presentation"
|
||||
"git.stormux.org/storm/fenrir/internal/screen"
|
||||
)
|
||||
|
||||
// Classic Rogue uses a 24 by 80 playfield even in a larger terminal.
|
||||
const mapBottom = 22
|
||||
|
||||
var statusPattern = regexp.MustCompile(`^Level:\s+(\d+)\s+Gold:\s+(\d+)\s+Hp:\s+(-?\d+)\(\s*(\d+)\)\s+Str:\s+(\d+)\(\s*(\d+)\)\s+Arm:\s+(-?\d+)\s+Exp:\s+(\d+)/(\d+)\s*(Hungry|Weak|Faint)?\s*$`)
|
||||
|
||||
type entity struct {
|
||||
x, y int
|
||||
glyph rune
|
||||
}
|
||||
|
||||
type Adapter struct {
|
||||
previous screen.Snapshot
|
||||
stats []string
|
||||
message, latest string
|
||||
entities []entity
|
||||
regions []applicationoverrides.Region
|
||||
dialog bool
|
||||
}
|
||||
|
||||
func (*Adapter) Name() string { return "Rogue" }
|
||||
func (*Adapter) Configure(settings *config.Settings) {
|
||||
settings.Reading.Mode = "cursor"
|
||||
settings.Review.LeaveReviewOnCursorChange = true
|
||||
}
|
||||
func (a *Adapter) Reset() { *a = Adapter{} }
|
||||
func (a *Adapter) Close() { a.Reset() }
|
||||
|
||||
func (a *Adapter) Command(id command.ID) (presentation.CommandResult, bool) {
|
||||
ids := []command.ID{command.ReadLiveRegion1, command.ReadLiveRegion2, command.ReadLiveRegion3, command.ReadLiveRegion4, command.ReadLiveRegion5, command.ReadLiveRegion6}
|
||||
if slot := slices.Index(ids, id); slot >= 0 {
|
||||
regions := a.regions
|
||||
if len(regions) == 0 {
|
||||
regions = region_names()
|
||||
}
|
||||
return presentation.CommandResult{Text: regions[slot].Read()}, true
|
||||
}
|
||||
if id == command.ListReadingRegions {
|
||||
var entries []string
|
||||
for slot, region := range region_names() {
|
||||
entries = append(entries, i18n.Format("Live region {0}: {1}", strconv.Itoa(slot+1), region.Name))
|
||||
}
|
||||
return presentation.CommandResult{Text: strings.Join(entries, ". ")}, true
|
||||
}
|
||||
return presentation.CommandResult{}, false
|
||||
}
|
||||
|
||||
func region_names() []applicationoverrides.Region {
|
||||
return []applicationoverrides.Region{
|
||||
{Name: i18n.T("Health")}, {Name: i18n.T("Status")},
|
||||
{Name: i18n.T("Latest message")}, {Name: i18n.T("Surroundings")},
|
||||
{Name: i18n.T("Visible monsters")}, {Name: i18n.T("Visible objects and landmarks")},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adapter) Observe(next screen.Snapshot) applicationoverrides.Frame {
|
||||
if !next.Active || next.Rows < 24 || next.Cols < 80 || len(next.Cells) != next.Rows*next.Cols {
|
||||
a.Reset()
|
||||
return applicationoverrides.Frame{}
|
||||
}
|
||||
stable := a.previous.Active && a.previous.Terminal == next.Terminal && a.previous.Generation == next.Generation && a.previous.Rows == next.Rows && a.previous.Cols == next.Cols
|
||||
if !stable {
|
||||
a.Reset()
|
||||
}
|
||||
stats := statusPattern.FindStringSubmatch(strings.TrimSpace(next.Text(23)))
|
||||
// Options and full-screen help/inventory use a separate curses window.
|
||||
// Let ordinary reading handle their text; never interpret it as map glyphs.
|
||||
options := strings.Contains(next.Text(0), `Terse output ("terse"):`) && strings.Contains(string(next.Cells), `Inventory style ("inven"):`)
|
||||
page := strings.Contains(string(next.Cells), "--Press space to continue--")
|
||||
if options || page && (stable || stats != nil) {
|
||||
a.regions = region_names()
|
||||
a.regions[2].Text = a.latest
|
||||
a.previous, a.dialog = next, true
|
||||
return applicationoverrides.Frame{Recognized: true, Regions: slices.Clone(a.regions)}
|
||||
}
|
||||
x, y, found := player_position(next)
|
||||
if stats == nil || !found {
|
||||
a.Reset()
|
||||
return applicationoverrides.Frame{}
|
||||
}
|
||||
frame := applicationoverrides.Frame{Recognized: true, OwnsPresentation: true}
|
||||
message := strings.TrimSpace(next.Text(0))
|
||||
if strings.HasSuffix(message, "--More--") {
|
||||
message = strings.TrimSpace(strings.TrimSuffix(message, "--More--")) + ". " + i18n.T("More; press Space")
|
||||
}
|
||||
if message != "" && message != a.message {
|
||||
a.latest = message
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: message})
|
||||
}
|
||||
// A visible clear permits the same message to be announced on a later turn.
|
||||
// Messages replaced between captures cannot be recovered from screenshots.
|
||||
a.message = message
|
||||
regions := region_names()
|
||||
regions[0].Text = i18n.Format("{0} of {1}", stats[3], stats[4])
|
||||
regions[1].Text = strings.TrimSpace(next.Text(23))
|
||||
regions[2].Text = a.latest
|
||||
regions[3].Text = surroundings(next, x, y)
|
||||
entities := visible_entities(next)
|
||||
var monsters, objects []string
|
||||
for _, item := range entities {
|
||||
text := describe(item, x, y)
|
||||
if is_monster(item.glyph) {
|
||||
monsters = append(monsters, text)
|
||||
} else {
|
||||
objects = append(objects, text)
|
||||
}
|
||||
}
|
||||
regions[4].Text, regions[5].Text = join_entities(monsters), join_entities(objects)
|
||||
if !stable || len(a.stats) == 0 {
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: regions[0].Read()})
|
||||
if stats[10] != "" {
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: stats[10]})
|
||||
}
|
||||
} else {
|
||||
if stats[3] != a.stats[3] || stats[4] != a.stats[4] {
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: regions[0].Read()})
|
||||
}
|
||||
for _, field := range []struct {
|
||||
index int
|
||||
label string
|
||||
}{
|
||||
{1, i18n.T("Dungeon level")}, {5, i18n.T("Strength")},
|
||||
{7, i18n.T("Armor")}, {8, i18n.T("Experience level")},
|
||||
} {
|
||||
if stats[field.index] != a.stats[field.index] {
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: (applicationoverrides.Region{Name: field.label, Text: stats[field.index]}).Read()})
|
||||
}
|
||||
}
|
||||
if stats[10] != a.stats[10] {
|
||||
hunger := stats[10]
|
||||
if hunger == "" {
|
||||
hunger = i18n.T("Not hungry")
|
||||
}
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: hunger})
|
||||
}
|
||||
}
|
||||
// Compare monsters by glyph count so a moving monster does not repeatedly
|
||||
// sound like a new arrival. Objects and landmarks have fixed map positions.
|
||||
if stable && !a.dialog && len(a.stats) != 0 && stats[1] == a.stats[1] {
|
||||
counts := map[rune]int{}
|
||||
for _, item := range a.entities {
|
||||
if is_monster(item.glyph) {
|
||||
counts[item.glyph]++
|
||||
}
|
||||
}
|
||||
for _, item := range entities {
|
||||
if is_monster(item.glyph) && counts[item.glyph] > 0 {
|
||||
counts[item.glyph]--
|
||||
continue
|
||||
}
|
||||
if !is_monster(item.glyph) && slices.Contains(a.entities, item) {
|
||||
continue
|
||||
}
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: describe(item, x, y)})
|
||||
}
|
||||
}
|
||||
a.regions, a.stats, a.entities = regions, stats, entities
|
||||
a.previous, a.dialog = next, false
|
||||
frame.Regions = slices.Clone(regions)
|
||||
return frame
|
||||
}
|
||||
|
||||
func player_position(s screen.Snapshot) (int, int, bool) {
|
||||
x, y := -1, -1
|
||||
for row := 1; row <= mapBottom; row++ {
|
||||
for col, glyph := range s.Line(row)[:80] {
|
||||
if glyph == '@' {
|
||||
if x >= 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
x, y = col, row
|
||||
}
|
||||
}
|
||||
}
|
||||
return x, y, x >= 0
|
||||
}
|
||||
|
||||
func is_monster(glyph rune) bool { return glyph >= 'A' && glyph <= 'Z' }
|
||||
|
||||
func visible_entities(s screen.Snapshot) []entity {
|
||||
var result []entity
|
||||
for y := 1; y <= mapBottom; y++ {
|
||||
for x, glyph := range s.Line(y)[:80] {
|
||||
if is_monster(glyph) || strings.ContainsRune("+^%*!?$:)],=/", glyph) {
|
||||
result = append(result, entity{x, y, glyph})
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func join_entities(items []string) string {
|
||||
if len(items) == 0 {
|
||||
return i18n.T("None visible")
|
||||
}
|
||||
return strings.Join(items, "; ")
|
||||
}
|
||||
|
||||
func surroundings(s screen.Snapshot, x, y int) string {
|
||||
var items []string
|
||||
for _, delta := range [][2]int{{0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}} {
|
||||
col, row := x+delta[0], y+delta[1]
|
||||
if col < 0 || col >= 80 || row < 1 || row > mapBottom {
|
||||
continue
|
||||
}
|
||||
items = append(items, describe(entity{col, row, s.Line(row)[col]}, x, y))
|
||||
}
|
||||
return strings.Join(items, "; ")
|
||||
}
|
||||
|
||||
func describe(item entity, x, y int) string {
|
||||
var offsets []string
|
||||
if item.y < y {
|
||||
offsets = append(offsets, i18n.Format("north {0}", strconv.Itoa(y-item.y)))
|
||||
}
|
||||
if item.y > y {
|
||||
offsets = append(offsets, i18n.Format("south {0}", strconv.Itoa(item.y-y)))
|
||||
}
|
||||
if item.x < x {
|
||||
offsets = append(offsets, i18n.Format("west {0}", strconv.Itoa(x-item.x)))
|
||||
}
|
||||
if item.x > x {
|
||||
offsets = append(offsets, i18n.Format("east {0}", strconv.Itoa(item.x-x)))
|
||||
}
|
||||
return i18n.Format("{0}, {1}", glyph_name(item.glyph), strings.Join(offsets, ", "))
|
||||
}
|
||||
|
||||
func glyph_name(glyph rune) string {
|
||||
// These are Rogue's own English monster names, rather than Fenrir labels.
|
||||
if is_monster(glyph) {
|
||||
return []string{"aquator", "bat", "centaur", "dragon", "emu", "venus flytrap", "griffin", "hobgoblin", "ice monster", "jabberwock", "kestrel", "leprechaun", "medusa", "nymph", "orc", "phantom", "quagga", "rattlesnake", "snake", "troll", "black unicorn", "vampire", "wraith", "xeroc", "yeti", "zombie"}[glyph-'A']
|
||||
}
|
||||
switch glyph {
|
||||
case ' ', 0:
|
||||
return i18n.T("Unseen")
|
||||
case '|', '-':
|
||||
return i18n.T("Wall")
|
||||
case '.':
|
||||
return i18n.T("Floor")
|
||||
case '#':
|
||||
return i18n.T("Passage")
|
||||
case '+':
|
||||
return i18n.T("Door")
|
||||
case '^':
|
||||
return i18n.T("Trap")
|
||||
case '%':
|
||||
return i18n.T("Stairs")
|
||||
case '*':
|
||||
return i18n.T("Gold")
|
||||
case '!':
|
||||
return i18n.T("Potion")
|
||||
case '?':
|
||||
return i18n.T("Scroll")
|
||||
case '$':
|
||||
return i18n.T("Magic")
|
||||
case ':':
|
||||
return i18n.T("Food")
|
||||
case ')':
|
||||
return i18n.T("Weapon")
|
||||
case ']':
|
||||
return i18n.T("Armor")
|
||||
case ',':
|
||||
return i18n.T("Amulet")
|
||||
case '=':
|
||||
return i18n.T("Ring")
|
||||
case '/':
|
||||
return i18n.T("Wand or staff")
|
||||
}
|
||||
return string(glyph)
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user