rpgroom_test.gno
3.65 Kb · 138 lines
1package rpgroom
2
3import (
4 "testing"
5
6 "gno.land/p/nt/avl/v0"
7 "gno.land/p/nt/testutils/v0"
8)
9
10func resetState() {
11 heroes = avl.Tree{}
12 roomLevel = 1
13 totalKills = 0
14 monster = spawnMonster(roomLevel)
15}
16
17func TestSpawnMonster(t *testing.T) {
18 m1 := spawnMonster(1)
19 m5 := spawnMonster(5)
20 if m1.Level != 1 || m5.Level != 5 {
21 t.Fatal("spawnMonster should carry the requested level")
22 }
23 if m5.HP <= m1.HP {
24 t.Fatal("a higher-level monster should have more HP")
25 }
26 if m5.Attack <= m1.Attack {
27 t.Fatal("a higher-level monster should hit harder")
28 }
29}
30
31func TestLevelUp(t *testing.T) {
32 h := &Hero{Level: 1, XP: 0, HP: 30, MaxHP: 30, Attack: 5}
33 h.XP = 50 // exactly the level-1 threshold
34 levelUp(h)
35 if h.Level != 2 {
36 t.Fatalf("Level = %d, want 2", h.Level)
37 }
38 if h.XP != 0 {
39 t.Fatalf("XP = %d, want 0 (fully consumed)", h.XP)
40 }
41 if h.MaxHP != 40 || h.HP != 40 {
42 t.Fatalf("MaxHP/HP = %d/%d, want 40/40 (healed on level up)", h.MaxHP, h.HP)
43 }
44 if h.Attack != 8 {
45 t.Fatalf("Attack = %d, want 8", h.Attack)
46 }
47}
48
49func TestLevelUpMultipleLevels(t *testing.T) {
50 h := &Hero{Level: 1, XP: 200, HP: 30, MaxHP: 30, Attack: 5}
51 levelUp(h)
52 // level 1->2 costs 50, 2->3 costs 100: 200 - 50 - 100 = 50 left, still short of 150 for 3->4
53 if h.Level != 3 {
54 t.Fatalf("Level = %d, want 3", h.Level)
55 }
56 if h.XP != 50 {
57 t.Fatalf("XP = %d, want 50 remaining", h.XP)
58 }
59}
60
61func TestHPBar(t *testing.T) {
62 full := hpBar(10, 10)
63 empty := hpBar(0, 10)
64 if full == empty {
65 t.Fatal("full and empty bars should render differently")
66 }
67}
68
69// Crossing: Spawn/Attack/Rest/Revive mutate realm state, so the test needs `cur realm`.
70func TestFullHeroLifecycle(cur realm, t *testing.T) {
71 resetState()
72 alice := testutils.TestAddress("alice")
73
74 testing.SetRealm(testing.NewUserRealm(alice))
75 Spawn(cross(cur), "Alice the Bold")
76
77 v := heroes.Get(alice.String())
78 if v == nil {
79 t.Fatal("expected a hero for alice")
80 }
81 hero := v.(*Hero)
82 if hero.Level != 1 || !hero.Alive {
83 t.Fatalf("fresh hero should be level 1 and alive, got level=%d alive=%v", hero.Level, hero.Alive)
84 }
85
86 // Grind the starter monster down; it starts at 35 HP with our hero
87 // hitting for 5 (ignoring the rare crit), so a handful of swings should
88 // clear it without the hero dying (monster only hits for 5).
89 startMonster := monster
90 for i := 0; i < 20 && monster == startMonster; i++ {
91 if !hero.Alive {
92 testing.SetRealm(testing.NewUserRealm(alice))
93 Revive(cross(cur))
94 }
95 testing.SetRealm(testing.NewUserRealm(alice))
96 Attack(cross(cur))
97 }
98 if monster == startMonster {
99 t.Fatal("expected the starter monster to be replaced after repeated attacks")
100 }
101 if hero.Kills < 1 {
102 t.Fatalf("Kills = %d, want at least 1", hero.Kills)
103 }
104 if hero.Gold < 1 {
105 t.Fatalf("Gold = %d, want at least 1", hero.Gold)
106 }
107 if roomLevel < 2 {
108 t.Fatalf("roomLevel = %d, want at least 2 after a kill", roomLevel)
109 }
110}
111
112func TestDoubleSpawnPanics(cur realm, t *testing.T) {
113 resetState()
114 bob := testutils.TestAddress("bob")
115 testing.SetRealm(testing.NewUserRealm(bob))
116 Spawn(cross(cur), "Bob")
117
118 testing.SetRealm(testing.NewUserRealm(bob))
119 // Spawn panics inside a cross-realm call; a plain defer/recover in this
120 // test does not catch a cross-boundary panic, so use revive().
121 rec := revive(func() { Spawn(cross(cur), "Bob Again") })
122 if rec == nil {
123 t.Fatal("expected a second Spawn for the same address to panic")
124 }
125}
126
127func TestRestRequiresDamage(cur realm, t *testing.T) {
128 resetState()
129 carol := testutils.TestAddress("carol")
130 testing.SetRealm(testing.NewUserRealm(carol))
131 Spawn(cross(cur), "Carol")
132
133 testing.SetRealm(testing.NewUserRealm(carol))
134 rec := revive(func() { Rest(cross(cur)) })
135 if rec == nil {
136 t.Fatal("expected Rest at full HP to panic")
137 }
138}