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