aboutsummaryrefslogtreecommitdiff
path: root/internal/game/core_course_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/core_course_test.go')
-rw-r--r--internal/game/core_course_test.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/internal/game/core_course_test.go b/internal/game/core_course_test.go
new file mode 100644
index 0000000..3d9fdc2
--- /dev/null
+++ b/internal/game/core_course_test.go
@@ -0,0 +1,140 @@
+package game
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestResolvePhasesPrefersExplicitPhases(t *testing.T) {
+ obs := ObstacleDef{
+ RoomID: 5,
+ Verb: "climb",
+ Phases: []ObstaclePhase{
+ {Message: "start", Delay: 0},
+ {Message: "middle", Delay: 2, FailCheck: true},
+ {Message: "end", Delay: 1.5},
+ },
+ // Legacy fields set as a decoy; should be ignored when Phases present.
+ TicksPerPhase: 99,
+ Messages: []string{"ignored1", "ignored2", "ignored3"},
+ }
+ got := resolvePhases(obs)
+ if len(got) != 3 {
+ t.Fatalf("expected 3 phases, got %d", len(got))
+ }
+ if got[0].Message != "start" || got[0].Delay != 0 || got[0].FailCheck {
+ t.Errorf("phase 0 wrong: %+v", got[0])
+ }
+ if got[1].Message != "middle" || got[1].Delay != 2 || !got[1].FailCheck {
+ t.Errorf("phase 1 (fail check) wrong: %+v", got[1])
+ }
+ if got[2].Message != "end" || got[2].Delay != 1.5 {
+ t.Errorf("phase 2 wrong: %+v", got[2])
+ }
+}
+
+func TestResolvePhasesLegacyMigration(t *testing.T) {
+ obs := ObstacleDef{
+ RoomID: 7,
+ Verb: "jump",
+ TicksPerPhase: 2,
+ Messages: []string{"a", "b", "c"},
+ }
+ got := resolvePhases(obs)
+ if len(got) != 3 {
+ t.Fatalf("expected 3 legacy phases, got %d", len(got))
+ }
+ if got[0].Message != "a" || got[0].Delay != 0 || got[0].FailCheck {
+ t.Errorf("legacy phase 0 wrong: %+v", got[0])
+ }
+ if got[1].Message != "b" || got[1].Delay != 2 || !got[1].FailCheck {
+ t.Errorf("legacy phase 1 must carry fail_check at index 1: %+v", got[1])
+ }
+ if got[2].Message != "c" || got[2].Delay != 2 || got[2].FailCheck {
+ t.Errorf("legacy phase 2 wrong: %+v", got[2])
+ }
+}
+
+func TestCourseStoreLoadsWithExplicitFailChance(t *testing.T) {
+ dir := t.TempDir()
+ coursesDir := filepath.Join(dir, "courses")
+ if err := os.MkdirAll(coursesDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ yaml := []byte(`
+name: "Test Course"
+required_level: 10
+start_room: 100
+completion_xp: 50
+obstacles:
+ - room_id: 101
+ verb: climb
+ xp: 12
+ fail_damage: [2, 5]
+ fail_chance: 0.20
+ phases:
+ - message: "begin"
+ delay: 0
+ - message: "mid"
+ delay: 2
+ fail_check: true
+ - message: "done"
+ delay: 2
+ - room_id: 102
+ verb: jump
+ xp: 14
+ fail_damage: [1, 3]
+ messages: ["p0", "p1", "p2"]
+ ticks_per_phase: 1
+`)
+ if err := os.WriteFile(filepath.Join(coursesDir, "testcourse.yaml"), yaml, 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ cs := NewCourseStore(dir)
+ cs.LoadAll()
+
+ // Obstacle 1: explicit phases + explicit fail_chance
+ o1 := cs.GetObstacle(101)
+ if o1 == nil {
+ t.Fatal("expected obstacle for room 101")
+ }
+ if len(o1.Phases) != 3 || o1.Phases[1].FailCheck != true {
+ t.Errorf("obstacle 1 phases wrong: %+v", o1.Phases)
+ }
+ if o1.FailChance == nil || *o1.FailChance != 0.20 {
+ t.Errorf("expected explicit fail_chance 0.20, got %v", o1.FailChance)
+ }
+ if o1.ObstacleIndex != 0 || o1.TotalObstacles != 2 {
+ t.Errorf("index/total wrong: %d/%d", o1.ObstacleIndex, o1.TotalObstacles)
+ }
+ if o1.NextRoom != 102 {
+ t.Errorf("expected next room 102, got %d", o1.NextRoom)
+ }
+ if o1.CompletionXP != 0 {
+ t.Errorf("first obstacle should have no completion xp, got %d", o1.CompletionXP)
+ }
+
+ // Obstacle 2: legacy migration, derived fail chance (nil)
+ o2 := cs.GetObstacle(102)
+ if o2 == nil {
+ t.Fatal("expected obstacle for room 102")
+ }
+ if len(o2.Phases) != 3 || !o2.Phases[1].FailCheck {
+ t.Errorf("legacy obstacle phases not migrated: %+v", o2.Phases)
+ }
+ if o2.FailChance != nil {
+ t.Errorf("legacy obstacle should have nil (derived) fail_chance, got %v", *o2.FailChance)
+ }
+ if o2.CompletionXP != 50 {
+ t.Errorf("last obstacle should carry completion_xp, got %d", o2.CompletionXP)
+ }
+ if o2.NextRoom != 0 {
+ t.Errorf("last obstacle should have next_room 0, got %d", o2.NextRoom)
+ }
+
+ if cs.GetObstacle(999) != nil {
+ t.Error("expected nil for unrelated room")
+ }
+} \ No newline at end of file