minichess_test.gno
8.70 Kb · 269 lines
1package minichess
2
3import (
4 "strings"
5 "testing"
6
7 mc "gno.land/p/g1um2pwwnx3gn8p3ryfdert8mhpu4u4up49sc9ru/minichess"
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11)
12
13var (
14 alice = testutils.TestAddress("alice")
15 bob = testutils.TestAddress("bob")
16 carol = testutils.TestAddress("carol")
17)
18
19func reset() {
20 games = avl.NewTree()
21 openGames = avl.NewTree()
22 playerGames = avl.NewTree()
23 nextID = 1
24}
25
26func TestSoloAsWhite(cur realm, t *testing.T) {
27 reset()
28 testing.SetOriginCaller(alice)
29 testing.SetRealm(testing.NewUserRealm(alice))
30 id := NewSoloGame(cross(cur), "white", 1)
31 g := mustGetGame(id)
32 uassert.Equal(t, alice.String(), g.White.String())
33 uassert.Equal(t, "", g.Black.String())
34 uassert.Equal(t, 0, len(g.Moves))
35
36 out := Move(cross(cur), id, "b1c3")
37 uassert.True(t, strings.Contains(out, "bot played"), out)
38 uassert.Equal(t, 2, len(g.Moves))
39 uassert.Equal(t, int(mc.White), int(g.Pos.Turn))
40}
41
42func TestSoloAsBlackBotMovesFirst(cur realm, t *testing.T) {
43 reset()
44 testing.SetOriginCaller(alice)
45 testing.SetRealm(testing.NewUserRealm(alice))
46 id := NewSoloGame(cross(cur), "black", 2)
47 g := mustGetGame(id)
48 uassert.Equal(t, 1, len(g.Moves))
49 uassert.Equal(t, int(mc.Black), int(g.Pos.Turn))
50}
51
52func TestMoveValidation(cur realm, t *testing.T) {
53 reset()
54 testing.SetOriginCaller(alice)
55 testing.SetRealm(testing.NewUserRealm(alice))
56 id := NewSoloGame(cross(cur), "white", 1)
57
58 uassert.AbortsWithMessage(t, cur, "illegal move: e2e5", func() {
59 Move(cross(cur), id, "e2e5")
60 })
61 uassert.AbortsWithMessage(t, cur, "chess: move must look like e2e4 or e7e8q", func() {
62 Move(cross(cur), id, "b2")
63 })
64 testing.SetOriginCaller(bob)
65 testing.SetRealm(testing.NewUserRealm(bob))
66 uassert.AbortsWithMessage(t, cur, "you are not a player in this game", func() {
67 Move(cross(cur), id, "b2b3")
68 })
69 uassert.AbortsWithMessage(t, cur, "game 99 not found", func() {
70 Move(cross(cur), 99, "b2b3")
71 })
72 uassert.AbortsWithMessage(t, cur, "level must be between 1 and 3", func() {
73 NewSoloGame(cross(cur), "white", 4)
74 })
75}
76
77func TestTwoPlayerCheckmate(cur realm, t *testing.T) {
78 reset()
79 testing.SetOriginCaller(alice)
80 testing.SetRealm(testing.NewUserRealm(alice))
81 id := Challenge(cross(cur), "", "white")
82 g := mustGetGame(id)
83 uassert.Equal(t, int(StateWaiting), int(g.State))
84 uassert.True(t, strings.Contains(Render(""), "func=AcceptChallenge&gameID=1"))
85
86 uassert.AbortsWithMessage(t, cur, "you cannot accept your own challenge", func() {
87 AcceptChallenge(cross(cur), id)
88 })
89 testing.SetOriginCaller(bob)
90 testing.SetRealm(testing.NewUserRealm(bob))
91 AcceptChallenge(cross(cur), id)
92 uassert.Equal(t, int(StateActive), int(g.State))
93 uassert.Equal(t, bob.String(), g.Black.String())
94 uassert.True(t, strings.Contains(Render(""), "_None right now._"))
95
96 uassert.AbortsWithMessage(t, cur, "it is not your turn", func() {
97 Move(cross(cur), id, "e7e5")
98 })
99
100 // Scholar's mate, with castling-free development: 4. Qxf7#.
101 seq := []string{"e2e4", "e7e5", "f1c4", "b8c6", "d1h5", "g8f6", "h5f7"}
102 for i, m := range seq {
103 if i%2 == 0 {
104 testing.SetOriginCaller(alice)
105 testing.SetRealm(testing.NewUserRealm(alice))
106 } else {
107 testing.SetOriginCaller(bob)
108 testing.SetRealm(testing.NewUserRealm(bob))
109 }
110 Move(cross(cur), id, m)
111 }
112 uassert.Equal(t, int(StateWhiteWon), int(g.State))
113 uassert.Equal(t, "checkmate", g.Reason)
114
115 testing.SetOriginCaller(bob)
116 testing.SetRealm(testing.NewUserRealm(bob))
117 uassert.AbortsWithMessage(t, cur, "game is not in progress (white won)", func() {
118 Move(cross(cur), id, "e8f7")
119 })
120}
121
122func TestInviteOnlyAndCancel(cur realm, t *testing.T) {
123 reset()
124 testing.SetOriginCaller(alice)
125 testing.SetRealm(testing.NewUserRealm(alice))
126 uassert.AbortsWithMessage(t, cur, "you cannot challenge yourself", func() {
127 Challenge(cross(cur), alice.String(), "white")
128 })
129 uassert.AbortsWithMessage(t, cur, "invalid opponent address", func() {
130 Challenge(cross(cur), "not-an-address", "white")
131 })
132 id := Challenge(cross(cur), bob.String(), "black")
133
134 testing.SetOriginCaller(carol)
135 testing.SetRealm(testing.NewUserRealm(carol))
136 uassert.AbortsWithMessage(t, cur, "this challenge is for another player", func() {
137 AcceptChallenge(cross(cur), id)
138 })
139 uassert.AbortsWithMessage(t, cur, "only the creator can cancel this challenge", func() {
140 CancelChallenge(cross(cur), id)
141 })
142
143 testing.SetOriginCaller(alice)
144 testing.SetRealm(testing.NewUserRealm(alice))
145 CancelChallenge(cross(cur), id)
146 uassert.Equal(t, int(StateCancelled), int(mustGetGame(id).State))
147
148 testing.SetOriginCaller(bob)
149 testing.SetRealm(testing.NewUserRealm(bob))
150 uassert.AbortsWithMessage(t, cur, "game is not waiting for an opponent", func() {
151 AcceptChallenge(cross(cur), id)
152 })
153}
154
155func TestDrawAndResign(cur realm, t *testing.T) {
156 reset()
157 testing.SetOriginCaller(alice)
158 testing.SetRealm(testing.NewUserRealm(alice))
159 id := Challenge(cross(cur), bob.String(), "white")
160 testing.SetOriginCaller(bob)
161 testing.SetRealm(testing.NewUserRealm(bob))
162 AcceptChallenge(cross(cur), id)
163 g := mustGetGame(id)
164
165 testing.SetOriginCaller(alice)
166 testing.SetRealm(testing.NewUserRealm(alice))
167 OfferDraw(cross(cur), id)
168 uassert.AbortsWithMessage(t, cur, "there is no draw offer from your opponent", func() {
169 AcceptDraw(cross(cur), id)
170 })
171 testing.SetOriginCaller(bob)
172 testing.SetRealm(testing.NewUserRealm(bob))
173 AcceptDraw(cross(cur), id)
174 uassert.Equal(t, int(StateDraw), int(g.State))
175
176 // New game: offer is declined by moving, then black resigns.
177 testing.SetOriginCaller(alice)
178 testing.SetRealm(testing.NewUserRealm(alice))
179 id2 := Challenge(cross(cur), "", "white")
180 testing.SetOriginCaller(bob)
181 testing.SetRealm(testing.NewUserRealm(bob))
182 AcceptChallenge(cross(cur), id2)
183 g2 := mustGetGame(id2)
184 testing.SetOriginCaller(alice)
185 testing.SetRealm(testing.NewUserRealm(alice))
186 Move(cross(cur), id2, "b1c3")
187 testing.SetOriginCaller(alice)
188 testing.SetRealm(testing.NewUserRealm(alice))
189 OfferDraw(cross(cur), id2)
190 testing.SetOriginCaller(bob)
191 testing.SetRealm(testing.NewUserRealm(bob))
192 Move(cross(cur), id2, "g8f6")
193 uassert.Equal(t, "", g2.DrawOffer.String())
194 Resign(cross(cur), id2)
195 uassert.Equal(t, int(StateWhiteWon), int(g2.State))
196}
197
198func TestThreefoldRepetition(cur realm, t *testing.T) {
199 reset()
200 testing.SetOriginCaller(alice)
201 testing.SetRealm(testing.NewUserRealm(alice))
202 id := Challenge(cross(cur), "", "white")
203 testing.SetOriginCaller(bob)
204 testing.SetRealm(testing.NewUserRealm(bob))
205 AcceptChallenge(cross(cur), id)
206 // Knights hop out and back twice: the start position occurs 3 times.
207 moves := []string{"g1f3", "g8f6", "f3g1", "f6g8", "g1f3", "g8f6", "f3g1", "f6g8"}
208 for i, m := range moves {
209 if i%2 == 0 {
210 testing.SetOriginCaller(alice)
211 testing.SetRealm(testing.NewUserRealm(alice))
212 } else {
213 testing.SetOriginCaller(bob)
214 testing.SetRealm(testing.NewUserRealm(bob))
215 }
216 Move(cross(cur), id, m)
217 }
218 g := mustGetGame(id)
219 uassert.Equal(t, int(StateDraw), int(g.State))
220 uassert.Equal(t, "threefold repetition", g.Reason)
221}
222
223func TestSpecialMovesInRealm(cur realm, t *testing.T) {
224 reset()
225 testing.SetOriginCaller(alice)
226 testing.SetRealm(testing.NewUserRealm(alice))
227 id := Challenge(cross(cur), bob.String(), "white")
228 testing.SetOriginCaller(bob)
229 testing.SetRealm(testing.NewUserRealm(bob))
230 AcceptChallenge(cross(cur), id)
231 g := mustGetGame(id)
232 // Castling and en passant through the public API.
233 moves := []string{"e2e4", "a7a6", "e4e5", "d7d5", "e5d6", "c7d6", "g1f3", "b8c6", "f1e2", "c8g4", "e1g1"}
234 for i, m := range moves {
235 if i%2 == 0 {
236 testing.SetOriginCaller(alice)
237 testing.SetRealm(testing.NewUserRealm(alice))
238 } else {
239 testing.SetOriginCaller(bob)
240 testing.SetRealm(testing.NewUserRealm(bob))
241 }
242 Move(cross(cur), id, m)
243 }
244 uassert.Equal(t, int(mc.King), int(g.Pos.PieceAt("g1")))
245 uassert.Equal(t, int(mc.Rook), int(g.Pos.PieceAt("f1")))
246 uassert.Equal(t, int(mc.Empty), int(g.Pos.PieceAt("d5")))
247 uassert.Equal(t, int(StateActive), int(g.State))
248}
249
250func TestRender(cur realm, t *testing.T) {
251 reset()
252 testing.SetOriginCaller(alice)
253 testing.SetRealm(testing.NewUserRealm(alice))
254 id := NewSoloGame(cross(cur), "white", 1)
255 home := Render("")
256 uassert.True(t, strings.Contains(home, "# ♟ Mini Chess"))
257 uassert.True(t, strings.Contains(home, "$help&func=NewSoloGame&color=white&level=1"))
258
259 page := Render("game/1")
260 uassert.True(t, strings.Contains(page, "data:image/svg+xml;base64,"))
261 uassert.True(t, strings.Contains(page, "$help&func=Move&gameID=1&move=b1c3"), page)
262 uassert.True(t, strings.Contains(page, "White to move"))
263
264 uassert.True(t, strings.Contains(Render("player/"+alice.String()), "#1"))
265 uassert.True(t, strings.Contains(Render("rules"), "en passant"))
266 uassert.True(t, strings.Contains(Render("game/42"), "does not exist"))
267 uassert.Equal(t, mc.StartFEN, GameFEN(id))
268 uassert.Equal(t, "in progress, white to move", GameStatus(id))
269}