package game import ( "os" "path/filepath" "testing" ) func TestResolvePhases(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}, }, } 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 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] phases: - message: "p0" delay: 0 - message: "p1" delay: 1 fail_check: true - message: "p2" delay: 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: explicit phases, 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("obstacle 2 phases wrong: %+v", o2.Phases) } if o2.FailChance != nil { t.Errorf("obstacle 2 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 != 100 { t.Errorf("last obstacle with no exit_dir should fall back to start_room 100, got %d", o2.NextRoom) } if cs.GetObstacle(999) != nil { t.Error("expected nil for unrelated room") } }