nx_checkers_test.nx source
↩ module page · 610 lines · 29101 B
1// nx_checkers_test.nx -- C1a substrate smoke.
2//
3// 8 assertions covering the initial setup at minimum complexity:
4// 1. new game has 24 pieces total (12 red + 12 black)
5// 2. red men occupy rows 5,6,7 dark squares (4 per row = 12 total)
6// 3. black men occupy rows 0,1,2 dark squares
7// 4. no pieces on rows 3 or 4 (the gap)
8// 5. no pieces on any LIGHT square (parity invariant)
9// 6. turn = RED (red moves first per standard rules)
10// 7. outcome = ONGOING, force_continue = -1, plies = 0
11// 8. coord helpers: is_dark + in_bounds + sq round-trip
12
13import "nx_syscalls.nx"
14import "nx_hal.nx"
15import "nx_tier.nx"
16import "nx_checkers.nx"
17
18func main() -> i64 {
19 let s: *i64 = nx_chk_new()
20
21 // ===== Assertion 1: 24 pieces total =================================
22 let n_red: i64 = nx_chk_count_side(s, NX_CHK_RED)
23 let n_black: i64 = nx_chk_count_side(s, NX_CHK_BLACK)
24 if n_red != 12 { return nx_hal_exit(1) }
25 if n_black != 12 { return nx_hal_exit(1) }
26
27 // ===== Assertion 2: red men on rows 5,6,7 dark squares ==============
28 var row: i64 = 5
29 while row < 8 {
30 var col: i64 = 0
31 var dark_count: i64 = 0
32 while col < 8 {
33 if nx_chk_is_dark(row, col) == 1 {
34 if nx_chk_cell(s, row, col) != NX_CHK_RED_MAN {
35 return nx_hal_exit(2)
36 }
37 dark_count = dark_count + 1
38 }
39 col = col + 1
40 }
41 if dark_count != 4 { return nx_hal_exit(2) }
42 row = row + 1
43 }
44
45 // ===== Assertion 3: black men on rows 0,1,2 dark squares ===========
46 row = 0
47 while row < 3 {
48 var col: i64 = 0
49 var dark_count: i64 = 0
50 while col < 8 {
51 if nx_chk_is_dark(row, col) == 1 {
52 if nx_chk_cell(s, row, col) != NX_CHK_BLACK_MAN {
53 return nx_hal_exit(3)
54 }
55 dark_count = dark_count + 1
56 }
57 col = col + 1
58 }
59 if dark_count != 4 { return nx_hal_exit(3) }
60 row = row + 1
61 }
62
63 // ===== Assertion 4: rows 3, 4 are empty =============================
64 row = 3
65 while row < 5 {
66 var col: i64 = 0
67 while col < 8 {
68 if nx_chk_cell(s, row, col) != NX_CHK_EMPTY {
69 return nx_hal_exit(4)
70 }
71 col = col + 1
72 }
73 row = row + 1
74 }
75
76 // ===== Assertion 5: every LIGHT square is empty (parity invariant) ==
77 row = 0
78 while row < 8 {
79 var col: i64 = 0
80 while col < 8 {
81 if nx_chk_is_dark(row, col) == 0 {
82 if nx_chk_cell(s, row, col) != NX_CHK_EMPTY {
83 return nx_hal_exit(5)
84 }
85 }
86 col = col + 1
87 }
88 row = row + 1
89 }
90
91 // ===== Assertion 6: turn = RED ======================================
92 if nx_chk_turn(s) != NX_CHK_RED { return nx_hal_exit(6) }
93
94 // ===== Assertion 7: outcome=ONGOING, force_continue=-1, plies=0 ====
95 if nx_chk_outcome(s) != NX_CHK_ONGOING { return nx_hal_exit(7) }
96 if nx_chk_force_continue(s) != -1 { return nx_hal_exit(7) }
97 if nx_chk_plies(s) != 0 { return nx_hal_exit(7) }
98
99 // ===== Assertion 8: coordinate helpers ===============================
100 // (0,0) is light, (0,1) is dark, (1,0) is dark, (1,1) is light
101 if nx_chk_is_dark(0, 0) != 0 { return nx_hal_exit(8) }
102 if nx_chk_is_dark(0, 1) != 1 { return nx_hal_exit(8) }
103 if nx_chk_is_dark(1, 0) != 1 { return nx_hal_exit(8) }
104 if nx_chk_is_dark(1, 1) != 0 { return nx_hal_exit(8) }
105 // in_bounds
106 if nx_chk_in_bounds(-1, 0) != 0 { return nx_hal_exit(8) }
107 if nx_chk_in_bounds(8, 0) != 0 { return nx_hal_exit(8) }
108 if nx_chk_in_bounds(0, -1) != 0 { return nx_hal_exit(8) }
109 if nx_chk_in_bounds(0, 8) != 0 { return nx_hal_exit(8) }
110 if nx_chk_in_bounds(0, 0) != 1 { return nx_hal_exit(8) }
111 if nx_chk_in_bounds(7, 7) != 1 { return nx_hal_exit(8) }
112 // sq round-trip
113 if nx_chk_sq(0, 0) != 0 { return nx_hal_exit(8) }
114 if nx_chk_sq(7, 7) != 63 { return nx_hal_exit(8) }
115 if nx_chk_sq(3, 4) != 28 { return nx_hal_exit(8) }
116
117 // ===== Assertion 9: legal red-man forward move applies =================
118 // Red man at (5,0) moves diagonally to (4,1) -- forward (row-decreasing).
119 // (5,0) is a dark square holding a red man; (4,1) is dark and empty.
120 let s2: *i64 = nx_chk_new()
121 if nx_chk_cell(s2, 5, 0) != NX_CHK_RED_MAN { return nx_hal_exit(9) }
122 if nx_chk_cell(s2, 4, 1) != NX_CHK_EMPTY { return nx_hal_exit(9) }
123 if nx_chk_apply_simple_move(s2, 5, 0, 4, 1) != 1 { return nx_hal_exit(9) }
124 if nx_chk_cell(s2, 5, 0) != NX_CHK_EMPTY { return nx_hal_exit(9) }
125 if nx_chk_cell(s2, 4, 1) != NX_CHK_RED_MAN { return nx_hal_exit(9) }
126 if nx_chk_turn(s2) != NX_CHK_BLACK { return nx_hal_exit(9) }
127 if nx_chk_plies(s2) != 1 { return nx_hal_exit(9) }
128
129 // ===== Assertion 10: backward move REJECTED for a man ==================
130 // Black to move now. Try to move the just-moved red man backward (which
131 // would also be wrong side, so this is double-illegal). Use a fresh state
132 // to isolate the backward-man rule: red man (5,2) cannot move to (6,1).
133 let s3: *i64 = nx_chk_new()
134 // (6,1) is dark and currently holds a red man -- destination not empty.
135 // So pick (5,0) -> (6,1) which would be backward AND occupied: should fail
136 // (illegal for multiple reasons). More targeted: (5,0) -> (6,1):
137 if nx_chk_apply_simple_move(s3, 5, 0, 6, 1) != 0 { return nx_hal_exit(10) }
138 // State unchanged.
139 if nx_chk_cell(s3, 5, 0) != NX_CHK_RED_MAN { return nx_hal_exit(10) }
140 if nx_chk_cell(s3, 6, 1) != NX_CHK_RED_MAN { return nx_hal_exit(10) }
141 if nx_chk_turn(s3) != NX_CHK_RED { return nx_hal_exit(10) }
142 if nx_chk_plies(s3) != 0 { return nx_hal_exit(10) }
143
144 // ===== Assertion 11: wrong-side move REJECTED ==========================
145 // It's red's turn; try to move a BLACK man. Black man at (2,1) -> (3,0)
146 // would be a legal black move geometrically, but red's turn.
147 let s4: *i64 = nx_chk_new()
148 if nx_chk_cell(s4, 2, 1) != NX_CHK_BLACK_MAN { return nx_hal_exit(11) }
149 if nx_chk_apply_simple_move(s4, 2, 1, 3, 0) != 0 { return nx_hal_exit(11) }
150 if nx_chk_cell(s4, 2, 1) != NX_CHK_BLACK_MAN { return nx_hal_exit(11) }
151 if nx_chk_turn(s4) != NX_CHK_RED { return nx_hal_exit(11) }
152
153 // ===== Assertion 12: move to LIGHT square REJECTED =====================
154 // Pieces never go on light squares (parity invariant). (5,2) red man
155 // trying to step "straight forward" to (4,2) -- but (4,2) is dark only
156 // when (row+col) is odd: 4+2=6 even -> light. Move must fail.
157 let s5: *i64 = nx_chk_new()
158 if nx_chk_is_dark(4, 2) != 0 { return nx_hal_exit(12) }
159 if nx_chk_apply_simple_move(s5, 5, 2, 4, 2) != 0 { return nx_hal_exit(12) }
160 if nx_chk_cell(s5, 5, 2) != NX_CHK_RED_MAN { return nx_hal_exit(12) }
161
162 // ===== Assertion 13: two-move sequence alternates turns correctly ======
163 // Red plays (5,0)->(4,1), Black plays (2,1)->(3,0), state correct after both.
164 let s6: *i64 = nx_chk_new()
165 if nx_chk_apply_simple_move(s6, 5, 0, 4, 1) != 1 { return nx_hal_exit(13) }
166 if nx_chk_turn(s6) != NX_CHK_BLACK { return nx_hal_exit(13) }
167 if nx_chk_apply_simple_move(s6, 2, 1, 3, 0) != 1 { return nx_hal_exit(13) }
168 if nx_chk_turn(s6) != NX_CHK_RED { return nx_hal_exit(13) }
169 if nx_chk_cell(s6, 4, 1) != NX_CHK_RED_MAN { return nx_hal_exit(13) }
170 if nx_chk_cell(s6, 3, 0) != NX_CHK_BLACK_MAN { return nx_hal_exit(13) }
171 if nx_chk_plies(s6) != 2 { return nx_hal_exit(13) }
172
173 // ===== Assertion 14: initial red has exactly 7 legal simple moves ====
174 // Row 5 men: (5,0)=1, (5,2)=2, (5,4)=2, (5,6)=2 -> 7 total.
175 // Row 6+7 men are blocked by pieces in row 5/6.
176 let s7: *i64 = nx_chk_new()
177 let moves_buf: *i64 = (sys_mmap(192 * 8)) as *i64
178 let red_count: i64 = nx_chk_legal_moves(s7, NX_CHK_RED, moves_buf)
179 if red_count != 7 { return nx_hal_exit(14) }
180
181 // ===== Assertion 15: initial black has exactly 7 legal simple moves ==
182 // Symmetric: (2,1)=2, (2,3)=2, (2,5)=2, (2,7)=1 -> 7 total.
183 let black_count: i64 = nx_chk_legal_moves(s7, NX_CHK_BLACK, moves_buf)
184 if black_count != 7 { return nx_hal_exit(15) }
185
186 // ===== Assertion 16: single red man on empty board has correct count ==
187 // Place 1 red man at (4,1). No other pieces. Red's turn. Should have
188 // 2 legal moves: (3,0) and (3,2). Both dark, both empty.
189 let s8: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
190 var k: i64 = 0
191 while k < NX_CHK_STATE_CELLS {
192 s8[k] = 0
193 k = k + 1
194 }
195 s8[nx_chk_sq(4, 1)] = NX_CHK_RED_MAN
196 s8[NX_CHK_OFF_TURN] = NX_CHK_RED
197 s8[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
198 s8[NX_CHK_OFF_FORCE_CONTINUE] = -1
199 let single_red_count: i64 = nx_chk_legal_moves(s8, NX_CHK_RED, moves_buf)
200 if single_red_count != 2 { return nx_hal_exit(16) }
201
202 // ===== Assertion 17: each enumerated move IS legal when applied =====
203 // For initial red's 7 moves, verify each applies cleanly to a FRESH state.
204 let s9: *i64 = nx_chk_new()
205 let count9: i64 = nx_chk_legal_moves(s9, NX_CHK_RED, moves_buf)
206 if count9 != 7 { return nx_hal_exit(17) }
207 var mi: i64 = 0
208 while mi < count9 {
209 let s_try: *i64 = nx_chk_new()
210 let fr: i64 = moves_buf[mi * 4 + 0]
211 let fc: i64 = moves_buf[mi * 4 + 1]
212 let tr: i64 = moves_buf[mi * 4 + 2]
213 let tc: i64 = moves_buf[mi * 4 + 3]
214 if nx_chk_apply_simple_move(s_try, fr, fc, tr, tc) != 1 {
215 return nx_hal_exit(17)
216 }
217 mi = mi + 1
218 }
219
220 // ===== Assertion 18: king has 4 directions, man has 2 =================
221 // Place a single red KING at (4,3) on empty board. Should have 4 legal
222 // diagonal moves: (3,2), (3,4), (5,2), (5,4) -- all dark, all empty.
223 let s10: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
224 var k2: i64 = 0
225 while k2 < NX_CHK_STATE_CELLS {
226 s10[k2] = 0
227 k2 = k2 + 1
228 }
229 s10[nx_chk_sq(4, 3)] = NX_CHK_RED_KING
230 s10[NX_CHK_OFF_TURN] = NX_CHK_RED
231 s10[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
232 s10[NX_CHK_OFF_FORCE_CONTINUE] = -1
233 let king_count: i64 = nx_chk_legal_moves(s10, NX_CHK_RED, moves_buf)
234 if king_count != 4 { return nx_hal_exit(18) }
235
236 // ===== Assertion 19: no moves when outcome != ONGOING ================
237 // If the game is decided, legal_moves returns 0 regardless of pieces.
238 let s11: *i64 = nx_chk_new()
239 s11[NX_CHK_OFF_OUTCOME] = NX_CHK_WIN_RED
240 let decided_count: i64 = nx_chk_legal_moves(s11, NX_CHK_RED, moves_buf)
241 if decided_count != 0 { return nx_hal_exit(19) }
242
243 // ===== Assertion 20: legal single-jump applies + captures =============
244 // Set up: red man at (5,2), black man at (4,3), (3,4) empty. Red to move.
245 // Red jumps (5,2) -> (3,4) capturing (4,3).
246 let s12: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
247 var k20: i64 = 0
248 while k20 < NX_CHK_STATE_CELLS {
249 s12[k20] = 0
250 k20 = k20 + 1
251 }
252 s12[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
253 s12[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
254 s12[NX_CHK_OFF_TURN] = NX_CHK_RED
255 s12[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
256 s12[NX_CHK_OFF_FORCE_CONTINUE] = -1
257 if nx_chk_apply_jump(s12, 5, 2, 3, 4) != 1 { return nx_hal_exit(20) }
258 if nx_chk_cell(s12, 5, 2) != NX_CHK_EMPTY { return nx_hal_exit(20) }
259 if nx_chk_cell(s12, 4, 3) != NX_CHK_EMPTY { return nx_hal_exit(20) }
260 if nx_chk_cell(s12, 3, 4) != NX_CHK_RED_MAN { return nx_hal_exit(20) }
261 if nx_chk_turn(s12) != NX_CHK_BLACK { return nx_hal_exit(20) }
262 if nx_chk_plies(s12) != 1 { return nx_hal_exit(20) }
263
264 // ===== Assertion 21: jump with NO middle piece REJECTED ===============
265 let s13: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
266 var k21: i64 = 0
267 while k21 < NX_CHK_STATE_CELLS { s13[k21] = 0; k21 = k21 + 1 }
268 s13[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
269 // (4,3) is EMPTY
270 s13[NX_CHK_OFF_TURN] = NX_CHK_RED
271 s13[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
272 s13[NX_CHK_OFF_FORCE_CONTINUE] = -1
273 if nx_chk_apply_jump(s13, 5, 2, 3, 4) != 0 { return nx_hal_exit(21) }
274 if nx_chk_cell(s13, 5, 2) != NX_CHK_RED_MAN { return nx_hal_exit(21) }
275 if nx_chk_turn(s13) != NX_CHK_RED { return nx_hal_exit(21) }
276
277 // ===== Assertion 22: jump over OWN piece REJECTED ====================
278 let s14: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
279 var k22: i64 = 0
280 while k22 < NX_CHK_STATE_CELLS { s14[k22] = 0; k22 = k22 + 1 }
281 s14[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
282 s14[nx_chk_sq(4, 3)] = NX_CHK_RED_MAN // own piece in middle
283 s14[NX_CHK_OFF_TURN] = NX_CHK_RED
284 s14[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
285 s14[NX_CHK_OFF_FORCE_CONTINUE] = -1
286 if nx_chk_apply_jump(s14, 5, 2, 3, 4) != 0 { return nx_hal_exit(22) }
287 if nx_chk_cell(s14, 4, 3) != NX_CHK_RED_MAN { return nx_hal_exit(22) }
288
289 // ===== Assertion 23: jump to occupied square REJECTED =================
290 let s15: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
291 var k23: i64 = 0
292 while k23 < NX_CHK_STATE_CELLS { s15[k23] = 0; k23 = k23 + 1 }
293 s15[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
294 s15[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
295 s15[nx_chk_sq(3, 4)] = NX_CHK_BLACK_MAN // destination occupied
296 s15[NX_CHK_OFF_TURN] = NX_CHK_RED
297 s15[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
298 s15[NX_CHK_OFF_FORCE_CONTINUE] = -1
299 if nx_chk_apply_jump(s15, 5, 2, 3, 4) != 0 { return nx_hal_exit(23) }
300 // Both still in place; capture didn't happen.
301 if nx_chk_cell(s15, 4, 3) != NX_CHK_BLACK_MAN { return nx_hal_exit(23) }
302
303 // ===== Assertion 24: man backward-jump REJECTED ======================
304 // Red man at (3,2), black at (4,3), (5,4) empty. Red man trying to
305 // jump BACKWARD (row-increasing) is illegal (only kings jump backward).
306 let s16: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
307 var k24: i64 = 0
308 while k24 < NX_CHK_STATE_CELLS { s16[k24] = 0; k24 = k24 + 1 }
309 s16[nx_chk_sq(3, 2)] = NX_CHK_RED_MAN
310 s16[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
311 s16[NX_CHK_OFF_TURN] = NX_CHK_RED
312 s16[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
313 s16[NX_CHK_OFF_FORCE_CONTINUE] = -1
314 if nx_chk_apply_jump(s16, 3, 2, 5, 4) != 0 { return nx_hal_exit(24) }
315
316 // ===== Assertion 25: king backward-jump APPLIES ======================
317 // Same setup but red KING at (3,2). Now backward jump (3,2)->(5,4)
318 // capturing (4,3) is legal.
319 let s17: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
320 var k25: i64 = 0
321 while k25 < NX_CHK_STATE_CELLS { s17[k25] = 0; k25 = k25 + 1 }
322 s17[nx_chk_sq(3, 2)] = NX_CHK_RED_KING
323 s17[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
324 s17[NX_CHK_OFF_TURN] = NX_CHK_RED
325 s17[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
326 s17[NX_CHK_OFF_FORCE_CONTINUE] = -1
327 if nx_chk_apply_jump(s17, 3, 2, 5, 4) != 1 { return nx_hal_exit(25) }
328 if nx_chk_cell(s17, 5, 4) != NX_CHK_RED_KING { return nx_hal_exit(25) }
329 if nx_chk_cell(s17, 4, 3) != NX_CHK_EMPTY { return nx_hal_exit(25) }
330
331 // ===== Assertion 26: jump that lands on far row promotes to king =====
332 // Red man at (2,1), black at (1,2), (0,3) empty. Red jumps (2,1)->(0,3).
333 // Landing row 0 -> promote to RED_KING.
334 let s18: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
335 var k26: i64 = 0
336 while k26 < NX_CHK_STATE_CELLS { s18[k26] = 0; k26 = k26 + 1 }
337 s18[nx_chk_sq(2, 1)] = NX_CHK_RED_MAN
338 s18[nx_chk_sq(1, 2)] = NX_CHK_BLACK_MAN
339 s18[NX_CHK_OFF_TURN] = NX_CHK_RED
340 s18[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
341 s18[NX_CHK_OFF_FORCE_CONTINUE] = -1
342 if nx_chk_apply_jump(s18, 2, 1, 0, 3) != 1 { return nx_hal_exit(26) }
343 if nx_chk_cell(s18, 0, 3) != NX_CHK_RED_KING { return nx_hal_exit(26) }
344
345 // ===== Assertion 27: legal_jumps enumerates correctly =================
346 // Position: red man at (5,2), black men at (4,1) and (4,3); two jumps
347 // available -- (5,2)->(3,0) and (5,2)->(3,4).
348 let s19: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
349 var k27: i64 = 0
350 while k27 < NX_CHK_STATE_CELLS { s19[k27] = 0; k27 = k27 + 1 }
351 s19[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
352 s19[nx_chk_sq(4, 1)] = NX_CHK_BLACK_MAN
353 s19[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
354 s19[NX_CHK_OFF_TURN] = NX_CHK_RED
355 s19[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
356 s19[NX_CHK_OFF_FORCE_CONTINUE] = -1
357 let jumps_buf: *i64 = (sys_mmap(192 * 8)) as *i64
358 let jc: i64 = nx_chk_legal_jumps(s19, NX_CHK_RED, jumps_buf)
359 if jc != 2 { return nx_hal_exit(27) }
360
361 // ===== Assertion 28: each enumerated jump IS applicable ================
362 // For the position above, replay each enumerated jump against a fresh
363 // identical state; verify nx_chk_apply_jump returns 1 every time.
364 var ji: i64 = 0
365 while ji < jc {
366 let s_try: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
367 var kt: i64 = 0
368 while kt < NX_CHK_STATE_CELLS { s_try[kt] = 0; kt = kt + 1 }
369 s_try[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
370 s_try[nx_chk_sq(4, 1)] = NX_CHK_BLACK_MAN
371 s_try[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
372 s_try[NX_CHK_OFF_TURN] = NX_CHK_RED
373 s_try[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
374 s_try[NX_CHK_OFF_FORCE_CONTINUE] = -1
375 let fr: i64 = jumps_buf[ji * 4 + 0]
376 let fc: i64 = jumps_buf[ji * 4 + 1]
377 let tr: i64 = jumps_buf[ji * 4 + 2]
378 let tc: i64 = jumps_buf[ji * 4 + 3]
379 if nx_chk_apply_jump(s_try, fr, fc, tr, tc) != 1 {
380 return nx_hal_exit(28)
381 }
382 ji = ji + 1
383 }
384
385 // ===== Assertion 29: initial position has ZERO legal jumps =============
386 // Standard starting position has rows 3-4 empty so no jumps possible.
387 let s20: *i64 = nx_chk_new()
388 let init_jumps: i64 = nx_chk_legal_jumps(s20, NX_CHK_RED, jumps_buf)
389 if init_jumps != 0 { return nx_hal_exit(29) }
390 let init_jumps_b: i64 = nx_chk_legal_jumps(s20, NX_CHK_BLACK, jumps_buf)
391 if init_jumps_b != 0 { return nx_hal_exit(29) }
392
393 // ===== Assertion 30: mandatory-capture rule -- simple move REJECTED
394 // when jumps are available for the current side.
395 // Position: red man at (5,2), black at (4,3), (3,4) empty. Red can
396 // jump (5,2)->(3,4). But red ALSO has a simple move (5,2)->(4,1)
397 // [dark empty square]. Per mandatory-capture rule, only jumps are
398 // legal -- the simple move must be rejected.
399 let s21: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
400 var k30: i64 = 0
401 while k30 < NX_CHK_STATE_CELLS { s21[k30] = 0; k30 = k30 + 1 }
402 s21[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
403 s21[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
404 s21[NX_CHK_OFF_TURN] = NX_CHK_RED
405 s21[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
406 s21[NX_CHK_OFF_FORCE_CONTINUE] = -1
407 // Verify jump IS available first (sanity).
408 if nx_chk_jumps_exist(s21, NX_CHK_RED) != 1 { return nx_hal_exit(30) }
409 // Now try the simple move (5,2)->(4,1). Must be rejected.
410 if nx_chk_apply_simple_move(s21, 5, 2, 4, 1) != 0 {
411 return nx_hal_exit(30)
412 }
413 // State must be unchanged.
414 if nx_chk_cell(s21, 5, 2) != NX_CHK_RED_MAN { return nx_hal_exit(30) }
415 if nx_chk_cell(s21, 4, 3) != NX_CHK_BLACK_MAN { return nx_hal_exit(30) }
416 if nx_chk_turn(s21) != NX_CHK_RED { return nx_hal_exit(30) }
417
418 // ===== Assertion 31: simple move ACCEPTED when no jumps available =====
419 // (regression check on existing behavior). Initial position has no jumps,
420 // so apply_simple_move(5,0,4,1) should still apply.
421 let s22: *i64 = nx_chk_new()
422 if nx_chk_apply_simple_move(s22, 5, 0, 4, 1) != 1 {
423 return nx_hal_exit(31)
424 }
425 if nx_chk_cell(s22, 4, 1) != NX_CHK_RED_MAN { return nx_hal_exit(31) }
426 if nx_chk_turn(s22) != NX_CHK_BLACK { return nx_hal_exit(31) }
427 if nx_chk_outcome(s22) != NX_CHK_ONGOING { return nx_hal_exit(31) }
428
429 // ===== Assertion 32: initial state outcome stays ONGOING via check ====
430 // nx_chk_check_outcome on fresh game must NOT change outcome.
431 let s23: *i64 = nx_chk_new()
432 nx_chk_check_outcome(s23)
433 if nx_chk_outcome(s23) != NX_CHK_ONGOING { return nx_hal_exit(32) }
434
435 // ===== Assertion 33: zugzwang detection -- black has no legal move ===
436 // Construct: black man at (0,1) (top edge). Forward simple moves go
437 // to (1,0) and (1,2) -- both blocked by red. Forward jumps go to
438 // (2,-1) [out of bounds] and (2,3) [must block landing too]. With
439 // red pieces at (1,0), (1,2), AND (2,3), black has zero legal moves.
440 let s24: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
441 var k33: i64 = 0
442 while k33 < NX_CHK_STATE_CELLS { s24[k33] = 0; k33 = k33 + 1 }
443 s24[nx_chk_sq(0, 1)] = NX_CHK_BLACK_MAN
444 s24[nx_chk_sq(1, 0)] = NX_CHK_RED_MAN
445 s24[nx_chk_sq(1, 2)] = NX_CHK_RED_MAN
446 s24[nx_chk_sq(2, 3)] = NX_CHK_RED_MAN // block the forward-right jump landing
447 s24[NX_CHK_OFF_TURN] = NX_CHK_BLACK
448 s24[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
449 s24[NX_CHK_OFF_FORCE_CONTINUE] = -1
450 // has_any_legal_move(black) must return 0 with the full block in place.
451 if nx_chk_has_any_legal_move(s24, NX_CHK_BLACK) != 0 {
452 return nx_hal_exit(33)
453 }
454 nx_chk_check_outcome(s24)
455 if nx_chk_outcome(s24) != NX_CHK_WIN_RED {
456 return nx_hal_exit(33)
457 }
458
459 // ===== Assertion 34: jump capturing last opponent piece -> WIN_RED ===
460 // Red man at (5,2), black man at (4,3) (last black piece), (3,4) empty.
461 // Red to move. Red jumps (5,2)->(3,4) capturing black. Now black has
462 // 0 pieces -- after turn flip + check_outcome -- outcome = WIN_RED.
463 let s25: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
464 var k34: i64 = 0
465 while k34 < NX_CHK_STATE_CELLS { s25[k34] = 0; k34 = k34 + 1 }
466 s25[nx_chk_sq(5, 2)] = NX_CHK_RED_MAN
467 s25[nx_chk_sq(4, 3)] = NX_CHK_BLACK_MAN
468 s25[NX_CHK_OFF_TURN] = NX_CHK_RED
469 s25[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
470 s25[NX_CHK_OFF_FORCE_CONTINUE] = -1
471 if nx_chk_apply_jump(s25, 5, 2, 3, 4) != 1 {
472 return nx_hal_exit(34)
473 }
474 if nx_chk_outcome(s25) != NX_CHK_WIN_RED {
475 return nx_hal_exit(34)
476 }
477
478 // ===== Assertion 35: outcome decision is sticky ======================
479 // Once outcome is decided, further apply_* calls are rejected and the
480 // outcome does not change.
481 let s26: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
482 var k35: i64 = 0
483 while k35 < NX_CHK_STATE_CELLS { s26[k35] = 0; k35 = k35 + 1 }
484 s26[nx_chk_sq(5, 0)] = NX_CHK_RED_MAN
485 s26[NX_CHK_OFF_TURN] = NX_CHK_RED
486 s26[NX_CHK_OFF_OUTCOME] = NX_CHK_WIN_BLACK // already decided
487 s26[NX_CHK_OFF_FORCE_CONTINUE] = -1
488 if nx_chk_apply_simple_move(s26, 5, 0, 4, 1) != 0 {
489 return nx_hal_exit(35)
490 }
491 if nx_chk_outcome(s26) != NX_CHK_WIN_BLACK {
492 return nx_hal_exit(35)
493 }
494
495 // ===== Assertion 36: jump with continuation sets force_continue + ====
496 // does NOT flip turn.
497 // Setup: red(5,0), black(4,1), black(2,3), all other cells empty.
498 // Red jumps (5,0)->(3,2) capturing (4,1). After: red at (3,2) which
499 // can jump again to (1,4) capturing (2,3). force_continue must be
500 // set to sq(3,2); turn must remain RED; plies=1.
501 let s27: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
502 var k36: i64 = 0
503 while k36 < NX_CHK_STATE_CELLS { s27[k36] = 0; k36 = k36 + 1 }
504 s27[nx_chk_sq(5, 0)] = NX_CHK_RED_MAN
505 s27[nx_chk_sq(4, 1)] = NX_CHK_BLACK_MAN
506 s27[nx_chk_sq(2, 3)] = NX_CHK_BLACK_MAN
507 s27[NX_CHK_OFF_TURN] = NX_CHK_RED
508 s27[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
509 s27[NX_CHK_OFF_FORCE_CONTINUE] = -1
510 if nx_chk_apply_jump(s27, 5, 0, 3, 2) != 1 {
511 return nx_hal_exit(36)
512 }
513 if nx_chk_cell(s27, 5, 0) != NX_CHK_EMPTY { return nx_hal_exit(36) }
514 if nx_chk_cell(s27, 4, 1) != NX_CHK_EMPTY { return nx_hal_exit(36) }
515 if nx_chk_cell(s27, 3, 2) != NX_CHK_RED_MAN{ return nx_hal_exit(36) }
516 if nx_chk_turn(s27) != NX_CHK_RED { return nx_hal_exit(36) }
517 if nx_chk_plies(s27) != 1 { return nx_hal_exit(36) }
518 if nx_chk_force_continue(s27) != nx_chk_sq(3, 2) {
519 return nx_hal_exit(36)
520 }
521
522 // ===== Assertion 37: continuation jump from force_continue applies, ==
523 // chain exhausts -> force_continue=-1, turn=BLACK, plies=2.
524 // Reuse s27 from above.
525 if nx_chk_apply_jump(s27, 3, 2, 1, 4) != 1 {
526 return nx_hal_exit(37)
527 }
528 if nx_chk_cell(s27, 3, 2) != NX_CHK_EMPTY { return nx_hal_exit(37) }
529 if nx_chk_cell(s27, 2, 3) != NX_CHK_EMPTY { return nx_hal_exit(37) }
530 if nx_chk_cell(s27, 1, 4) != NX_CHK_RED_MAN{ return nx_hal_exit(37) }
531 if nx_chk_force_continue(s27) != -1 { return nx_hal_exit(37) }
532 if nx_chk_turn(s27) != NX_CHK_BLACK { return nx_hal_exit(37) }
533 if nx_chk_plies(s27) != 2 { return nx_hal_exit(37) }
534
535 // ===== Assertion 38: simple move during force_continue REJECTED ======
536 // Reach the mid-chain state again and attempt a simple move.
537 let s28: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
538 var k38: i64 = 0
539 while k38 < NX_CHK_STATE_CELLS { s28[k38] = 0; k38 = k38 + 1 }
540 s28[nx_chk_sq(5, 0)] = NX_CHK_RED_MAN
541 s28[nx_chk_sq(4, 1)] = NX_CHK_BLACK_MAN
542 s28[nx_chk_sq(2, 3)] = NX_CHK_BLACK_MAN
543 s28[nx_chk_sq(7, 0)] = NX_CHK_RED_MAN // extra red piece elsewhere
544 s28[NX_CHK_OFF_TURN] = NX_CHK_RED
545 s28[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
546 s28[NX_CHK_OFF_FORCE_CONTINUE] = -1
547 if nx_chk_apply_jump(s28, 5, 0, 3, 2) != 1 {
548 return nx_hal_exit(38)
549 }
550 // Now force_continue is set to sq(3,2). Try a simple move with the
551 // OTHER red piece at (7,0) -- (7,0)->(6,1) is normally a legal red-man
552 // forward move but the chain forbids it.
553 if nx_chk_apply_simple_move(s28, 7, 0, 6, 1) != 0 {
554 return nx_hal_exit(38)
555 }
556 // State must reflect only the first jump.
557 if nx_chk_cell(s28, 7, 0) != NX_CHK_RED_MAN { return nx_hal_exit(38) }
558 if nx_chk_cell(s28, 6, 1) != NX_CHK_EMPTY { return nx_hal_exit(38) }
559
560 // ===== Assertion 39: jump from a NON-chain square during force_continue
561 // REJECTED. Same s28 state. Set up another red piece that COULD jump
562 // (red at (5,4) with black at (4,5), landing (3,6) empty).
563 s28[nx_chk_sq(5, 4)] = NX_CHK_RED_MAN
564 s28[nx_chk_sq(4, 5)] = NX_CHK_BLACK_MAN
565 // Sanity: that jump would be legal in a vacuum. But during force_continue
566 // for the (3,2) chain piece, jump from (5,4) must be rejected.
567 if nx_chk_apply_jump(s28, 5, 4, 3, 6) != 0 {
568 return nx_hal_exit(39)
569 }
570 // Verify state untouched.
571 if nx_chk_cell(s28, 5, 4) != NX_CHK_RED_MAN { return nx_hal_exit(39) }
572 if nx_chk_cell(s28, 4, 5) != NX_CHK_BLACK_MAN { return nx_hal_exit(39) }
573
574 // ===== Assertion 40: chain piece can still jump during force_continue =
575 // Reuse s28 (mid-chain after (5,0)->(3,2)). Apply (3,2)->(1,4).
576 // No further continuation expected -> force_continue cleared.
577 if nx_chk_apply_jump(s28, 3, 2, 1, 4) != 1 {
578 return nx_hal_exit(40)
579 }
580 if nx_chk_force_continue(s28) != -1 { return nx_hal_exit(40) }
581 if nx_chk_turn(s28) != NX_CHK_BLACK { return nx_hal_exit(40) }
582
583 // ===== Assertion 41: PROMOTION mid-chain stops the chain (American rule)
584 // Setup: red man at (2,1). Black at (1,2). Black at (1,4). Cells
585 // (0,3), (0,5), (2,5) empty.
586 // Step 1: red jumps (2,1)->(0,3) capturing (1,2). Red lands on row 0
587 // -> promoted to RED_KING.
588 // In American rules, promotion stops the chain -- even though the new
589 // king COULD jump backward (0,3)->(2,5) capturing (1,4).
590 // Expected: force_continue=-1, turn=BLACK, piece at (0,3)=RED_KING.
591 let s29: *i64 = (sys_mmap(NX_CHK_STATE_CELLS * 8)) as *i64
592 var k41: i64 = 0
593 while k41 < NX_CHK_STATE_CELLS { s29[k41] = 0; k41 = k41 + 1 }
594 s29[nx_chk_sq(2, 1)] = NX_CHK_RED_MAN
595 s29[nx_chk_sq(1, 2)] = NX_CHK_BLACK_MAN
596 s29[nx_chk_sq(1, 4)] = NX_CHK_BLACK_MAN
597 s29[NX_CHK_OFF_TURN] = NX_CHK_RED
598 s29[NX_CHK_OFF_OUTCOME] = NX_CHK_ONGOING
599 s29[NX_CHK_OFF_FORCE_CONTINUE] = -1
600 if nx_chk_apply_jump(s29, 2, 1, 0, 3) != 1 {
601 return nx_hal_exit(41)
602 }
603 if nx_chk_cell(s29, 0, 3) != NX_CHK_RED_KING { return nx_hal_exit(41) }
604 if nx_chk_force_continue(s29) != -1 { return nx_hal_exit(41) }
605 if nx_chk_turn(s29) != NX_CHK_BLACK { return nx_hal_exit(41) }
606 // The other black at (1,4) is still on the board (chain didn't continue).
607 if nx_chk_cell(s29, 1, 4) != NX_CHK_BLACK_MAN { return nx_hal_exit(41) }
608
609 return 0
610}