aboutsummaryrefslogtreecommitdiff
path: root/internal/game/condition_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-24 20:58:25 -0400
committerhistoria <[not public]>2026-06-24 20:58:25 -0400
commit9d4b799db4868bcab291b83599ff088763cd4ed6 (patch)
tree252f0fcc779acf35243983d643d6399ee2e0cd0b /internal/game/condition_test.go
parentd25638d98fe63efdeab570ef77e6a6a97f2d7a60 (diff)
downloadthehouseoficarus-9d4b799db4868bcab291b83599ff088763cd4ed6.tar.gz
feat: new type of non-violent 'combat': work
Diffstat (limited to 'internal/game/condition_test.go')
-rw-r--r--internal/game/condition_test.go155
1 files changed, 155 insertions, 0 deletions
diff --git a/internal/game/condition_test.go b/internal/game/condition_test.go
new file mode 100644
index 0000000..5e5967d
--- /dev/null
+++ b/internal/game/condition_test.go
@@ -0,0 +1,155 @@
+package game
+
+import (
+ "testing"
+
+ "thehouseoficarus/internal/action"
+ "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{WorldFlags: map[string]any{}}
+
+ set := sessWithFlags(map[string]any{"x": true})
+ unset := sessWithFlags(map[string]any{})
+
+ // {player_flag: x} -> set means present+truthy
+ posCond := &action.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 := &action.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{WorldFlags: map[string]any{}}
+ sess := sessWithFlags(map[string]any{"n": 1})
+
+ if !g.checkCondition(sess, &action.Condition{PlayerFlag: "n", Value: 1}) {
+ t.Error("value match should pass for equal int")
+ }
+ if g.checkCondition(sess, &action.Condition{PlayerFlag: "n", Value: 2}) {
+ t.Error("value match should fail for unequal int")
+ }
+ if !g.checkCondition(sess, &action.Condition{PlayerFlag: "n", Value: 2, Not: true}) {
+ t.Error("negated value match should pass for unequal int")
+ }
+}
+
+func TestCheckConditionWorldFlag(t *testing.T) {
+ g := &Game{WorldFlags: map[string]any{"gate_open": true}}
+ sess := sessWithFlags(map[string]any{})
+
+ if !g.checkCondition(sess, &action.Condition{Flag: "gate_open"}) {
+ t.Error("world flag truthy check should pass")
+ }
+ if g.checkCondition(sess, &action.Condition{Flag: "gate_open", Not: true}) {
+ t.Error("negated world flag should fail when set")
+ }
+ if g.checkCondition(sess, &action.Condition{Flag: "missing"}) {
+ t.Error("missing world flag should not pass")
+ }
+}
+
+func TestResolveObjDescPresence(t *testing.T) {
+ g := &Game{WorldFlags: map[string]any{}}
+
+ def := &object.ObjectDef{
+ Descriptions: []object.ConditionalDesc{
+ {Text: "lined", Condition: &action.Condition{PlayerFlag: "lined_up"}},
+ {Text: "milling", Condition: &action.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{
+ Descriptions: []object.ConditionalDesc{
+ {Text: "x", Condition: &action.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 (no Descriptions) always present
+ plain := &object.ObjectDef{Description: "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{WorldFlags: map[string]any{}}
+ room := &world.Room{
+ Description: "empty pad",
+ Descriptions: []world.RoomDesc{
+ {Text: "crowd", Condition: &action.Condition{PlayerFlag: "done", Not: true}},
+ },
+ }
+ 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)
+ }
+}