1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
package game
import (
"testing"
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/object"
"thehouseoficarus/internal/player"
"thehouseoficarus/internal/world"
)
func sessWithFlags(flags map[string]any) *net.Session {
return &net.Session{Player: &player.Player{Flags: flags}}
}
func TestIsTruthy(t *testing.T) {
cases := []struct {
v any
want bool
}{
{nil, false},
{true, true},
{false, false},
{0, false},
{1, true},
{int64(0), false},
{int64(2), true},
{0.0, false},
{1.5, true},
{"", false},
{"x", true},
}
for _, c := range cases {
if got := isTruthy(c.v); got != c.want {
t.Errorf("isTruthy(%v) = %v, want %v", c.v, got, c.want)
}
}
}
func TestCheckConditionPlayerFlagTruthy(t *testing.T) {
g := &Game{GlobalFlags: NewGlobalFlagStore()}
set := sessWithFlags(map[string]any{"x": true})
unset := sessWithFlags(map[string]any{})
// {player_flag: x} -> set means present+truthy
posCond := &behavior.Condition{PlayerFlag: "x"}
if !g.checkCondition(set, posCond) {
t.Error("expected positive flag check to pass when flag is true")
}
if g.checkCondition(unset, posCond) {
t.Error("expected positive flag check to fail when flag is unset")
}
// {player_flag: x, not: true} -> negative
negCond := &behavior.Condition{PlayerFlag: "x", Not: true}
if g.checkCondition(set, negCond) {
t.Error("expected negative flag check to fail when flag is true")
}
if !g.checkCondition(unset, negCond) {
t.Error("expected negative flag check to pass when flag is unset")
}
// flag explicitly set to false counts as not-set for truthy checks
falseSet := sessWithFlags(map[string]any{"x": false})
if g.checkCondition(falseSet, posCond) {
t.Error("expected positive check to fail when flag is false")
}
if !g.checkCondition(falseSet, negCond) {
t.Error("expected negative check to pass when flag is false")
}
}
func TestCheckConditionValueComparisonPreserved(t *testing.T) {
g := &Game{GlobalFlags: NewGlobalFlagStore()}
sess := sessWithFlags(map[string]any{"n": 1})
if !g.checkCondition(sess, &behavior.Condition{PlayerFlag: "n", Value: 1}) {
t.Error("value match should pass for equal int")
}
if g.checkCondition(sess, &behavior.Condition{PlayerFlag: "n", Value: 2}) {
t.Error("value match should fail for unequal int")
}
if !g.checkCondition(sess, &behavior.Condition{PlayerFlag: "n", Value: 2, Not: true}) {
t.Error("negated value match should pass for unequal int")
}
}
func TestCheckConditionGlobalFlag(t *testing.T) {
g := &Game{GlobalFlags: NewGlobalFlagStore()}
g.GlobalFlags.Set("gate_open", true)
sess := sessWithFlags(map[string]any{})
if !g.checkCondition(sess, &behavior.Condition{GlobalFlag: "gate_open"}) {
t.Error("global flag truthy check should pass")
}
if g.checkCondition(sess, &behavior.Condition{GlobalFlag: "gate_open", Not: true}) {
t.Error("negated global flag should fail when set")
}
if g.checkCondition(sess, &behavior.Condition{GlobalFlag: "missing"}) {
t.Error("missing global flag should not pass")
}
}
func TestResolveObjDescPresence(t *testing.T) {
g := &Game{GlobalFlags: NewGlobalFlagStore()}
def := &object.ObjectDef{
Description: behavior.DescList{
{Text: "lined", Condition: &behavior.Condition{PlayerFlag: "lined_up"}},
{Text: "milling", Condition: &behavior.Condition{PlayerFlag: "lined_up", Not: true}},
},
}
// not lined up -> milling, present
if text, present := g.resolveObjDesc(sessWithFlags(map[string]any{}), def); !present || text != "milling" {
t.Errorf("expected milling/present, got %q/%v", text, present)
}
// lined up -> lined, present
if text, present := g.resolveObjDesc(sessWithFlags(map[string]any{"lined_up": true}), def); !present || text != "lined" {
t.Errorf("expected lined/present, got %q/%v", text, present)
}
// gated entirely off -> absent
gated := &object.ObjectDef{
Description: behavior.DescList{
{Text: "x", Condition: &behavior.Condition{PlayerFlag: "done", Not: true}},
},
}
if _, present := g.resolveObjDesc(sessWithFlags(map[string]any{"done": true}), gated); present {
t.Error("expected object to be absent when no variant matches")
}
// plain object always present
plain := &object.ObjectDef{Description: behavior.DescList{{Text: "plain"}}}
if text, present := g.resolveObjDesc(sessWithFlags(nil), plain); !present || text != "plain" {
t.Errorf("expected plain/present, got %q/%v", text, present)
}
}
func TestRoomDescriptionSelection(t *testing.T) {
g := &Game{GlobalFlags: NewGlobalFlagStore()}
room := &world.Room{
Description: behavior.DescList{
{Text: "crowd", Condition: &behavior.Condition{PlayerFlag: "done", Not: true}},
{Text: "empty pad"},
},
}
if got := g.roomDescription(sessWithFlags(map[string]any{}), room); got != "crowd" {
t.Errorf("first visit: want crowd, got %q", got)
}
if got := g.roomDescription(sessWithFlags(map[string]any{"done": true}), room); got != "empty pad" {
t.Errorf("revisit: want empty pad, got %q", got)
}
}
|