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) } }