code wiki / (root) / nx_checkers_test.nx

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}