aboutsummaryrefslogtreecommitdiff
path: root/internal/game/core_course_test.go
blob: 3d9fdc23e24b1daf6aa91ccedbc81f7ab172910b (plain)
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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")
	}
}