code wiki / (root) / nx_tictactoe_test.nx

nx_tictactoe_test.nx source

↩ module page · 173 lines · 7596 B

1// nx_tictactoe_test.nx -- C0 substrate smoke. 2// 3// 12 deterministic assertions covering: 4// 1. new game has X to move + outcome ongoing 5// 2. 9 legal moves on a fresh board 6// 3. apply X to cell 4 -> board[4]=X, turn=O, 8 legal moves left 7// 4. illegal-move (occupied cell) is no-op 8// 5. illegal-move (out-of-range) is no-op 9// 6. horizontal-row win is detected (top row XXX) 10// 7. diagonal win is detected (0-4-8 XXX) 11// 8. draw is detected after 9 non-winning moves 12// 9. state-hash is deterministic for same state 13// 10. perfect-vs-perfect always ends in draw (game-theory invariant) 14// 11. perfect-vs-random never loses (perfect's score never negative) 15// 12. evaluate updates only the OUTCOME and WINNER cells (does not mutate board) 16// 17// Each failed assertion exits with a distinct non-zero code so the smoke 18// harness identifies which check fired. 19// 20// Composes [[feedback-bug-class-prevention-additive-not-restrictive]] + 21// ยง22 bug-class table entry "move-applies-to-wrong-cell" (the tic-tac-toe 22// equivalent of the W/S inversion class). 23 24import "nx_syscalls.nx" 25import "nx_tier.nx" 26import "nx_tictactoe.nx" 27import "nx_prng.nx" 28 29func main() -> nx_int { 30 // ===== Assertion 1: new game state ================================== 31 let s1: *i64 = nx_ttt_new(NX_TTT_X) 32 if nx_ttt_turn(s1) != NX_TTT_X { return __syscall(93, 1, 0, 0, 0, 0, 0) } 33 if nx_ttt_outcome(s1) != NX_TTT_ONGOING { return __syscall(93, 1, 0, 0, 0, 0, 0) } 34 if nx_ttt_winner(s1) != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 35 36 // ===== Assertion 2: 9 legal moves on fresh board ==================== 37 if nx_ttt_legal_count(s1) != 9 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 38 39 // ===== Assertion 3: apply X to cell 4 =============================== 40 if nx_ttt_try_apply(s1, 4) != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 41 if nx_ttt_cell(s1, 4) != NX_TTT_X { return __syscall(93, 3, 0, 0, 0, 0, 0) } 42 if nx_ttt_turn(s1) != NX_TTT_O { return __syscall(93, 3, 0, 0, 0, 0, 0) } 43 if nx_ttt_legal_count(s1) != 8 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 44 45 // ===== Assertion 4: illegal move (occupied cell) is no-op =========== 46 let hash_before: i64 = nx_ttt_state_hash(s1) 47 if nx_ttt_try_apply(s1, 4) != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 48 if nx_ttt_state_hash(s1) != hash_before { return __syscall(93, 4, 0, 0, 0, 0, 0) } 49 50 // ===== Assertion 5: illegal move (out-of-range) is no-op ============ 51 if nx_ttt_try_apply(s1, -1) != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 52 if nx_ttt_try_apply(s1, 9) != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 53 if nx_ttt_state_hash(s1) != hash_before { return __syscall(93, 5, 0, 0, 0, 0, 0) } 54 55 // ===== Assertion 6: horizontal-row win detected ===================== 56 // Reset and play X: 0,1,2 / O: 3,4 (X wins top row) 57 let s2: *i64 = nx_ttt_new(NX_TTT_X) 58 nx_ttt_try_apply(s2, 0) // X@0 59 nx_ttt_try_apply(s2, 3) // O@3 60 nx_ttt_try_apply(s2, 1) // X@1 61 nx_ttt_try_apply(s2, 4) // O@4 62 nx_ttt_try_apply(s2, 2) // X@2 wins 63 if nx_ttt_outcome(s2) != NX_TTT_WIN { return __syscall(93, 6, 0, 0, 0, 0, 0) } 64 if nx_ttt_winner(s2) != NX_TTT_X { return __syscall(93, 6, 0, 0, 0, 0, 0) } 65 66 // ===== Assertion 7: diagonal win detected =========================== 67 let s3: *i64 = nx_ttt_new(NX_TTT_X) 68 nx_ttt_try_apply(s3, 0) // X@0 69 nx_ttt_try_apply(s3, 1) // O@1 70 nx_ttt_try_apply(s3, 4) // X@4 71 nx_ttt_try_apply(s3, 2) // O@2 72 nx_ttt_try_apply(s3, 8) // X@8 wins diagonal 73 if nx_ttt_outcome(s3) != NX_TTT_WIN { return __syscall(93, 7, 0, 0, 0, 0, 0) } 74 if nx_ttt_winner(s3) != NX_TTT_X { return __syscall(93, 7, 0, 0, 0, 0, 0) } 75 76 // ===== Assertion 8: draw detected =================================== 77 // X O X 78 // X O O 79 // O X X (moves: 0 1 2 4 3 6 7 8 5) 80 let s4: *i64 = nx_ttt_new(NX_TTT_X) 81 nx_ttt_try_apply(s4, 0) 82 nx_ttt_try_apply(s4, 1) 83 nx_ttt_try_apply(s4, 2) 84 nx_ttt_try_apply(s4, 4) 85 nx_ttt_try_apply(s4, 3) 86 nx_ttt_try_apply(s4, 6) 87 nx_ttt_try_apply(s4, 7) 88 nx_ttt_try_apply(s4, 8) 89 nx_ttt_try_apply(s4, 5) 90 if nx_ttt_outcome(s4) != NX_TTT_DRAW { return __syscall(93, 8, 0, 0, 0, 0, 0) } 91 92 // ===== Assertion 9: state-hash is deterministic ===================== 93 let s5: *i64 = nx_ttt_new(NX_TTT_X) 94 nx_ttt_try_apply(s5, 4) 95 nx_ttt_try_apply(s5, 0) 96 nx_ttt_try_apply(s5, 8) 97 let h_a: i64 = nx_ttt_state_hash(s5) 98 let h_b: i64 = nx_ttt_state_hash(s5) 99 if h_a != h_b { return __syscall(93, 9, 0, 0, 0, 0, 0) } 100 // Different state -> different hash (overwhelmingly likely) 101 nx_ttt_try_apply(s5, 1) 102 let h_c: i64 = nx_ttt_state_hash(s5) 103 if h_a == h_c { return __syscall(93, 9, 0, 0, 0, 0, 0) } 104 105 // ===== Assertion 10: perfect-vs-perfect always draws ================ 106 // Two minimax agents -- game theory: optimal play -> draw. 107 let s6: *i64 = nx_ttt_new(NX_TTT_X) 108 var moves_played: nx_int = 0 109 while nx_ttt_outcome(s6) == NX_TTT_ONGOING { 110 let mv: nx_int = nx_ttt_pick_perfect(s6, nx_ttt_turn(s6)) 111 if mv < 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 112 nx_ttt_try_apply(s6, mv) 113 moves_played = moves_played + 1 114 if moves_played > 9 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 115 } 116 if nx_ttt_outcome(s6) != NX_TTT_DRAW { return __syscall(93, 10, 0, 0, 0, 0, 0) } 117 118 // ===== Assertion 11: perfect-vs-random -- perfect never loses ======= 119 // 5 trials. Perfect (X) vs random (O). X must win or draw. 120 var prng_state: i64 = 0 121 let prng_ptr: *i64 = (&prng_state) as *i64 122 nx_prng_init(prng_ptr, 42 as i64) 123 var trial: nx_int = 0 124 while trial < 5 { 125 let s7: *i64 = nx_ttt_new(NX_TTT_X) 126 while nx_ttt_outcome(s7) == NX_TTT_ONGOING { 127 var mv: nx_int = -1 128 if nx_ttt_turn(s7) == NX_TTT_X { 129 mv = nx_ttt_pick_perfect(s7, NX_TTT_X) 130 } else { 131 mv = nx_ttt_pick_easy(s7, prng_ptr) 132 } 133 if mv < 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 134 nx_ttt_try_apply(s7, mv) 135 } 136 if nx_ttt_outcome(s7) == NX_TTT_WIN { 137 if nx_ttt_winner(s7) != NX_TTT_X { return __syscall(93, 11, 0, 0, 0, 0, 0) } 138 } 139 // DRAW is acceptable; LOSS (X is winner -> not X) is the failure. 140 trial = trial + 1 141 } 142 143 // ===== Assertion 12: evaluate does not mutate board cells =========== 144 let s8: *i64 = nx_ttt_new(NX_TTT_X) 145 nx_ttt_try_apply(s8, 0) 146 nx_ttt_try_apply(s8, 4) 147 nx_ttt_try_apply(s8, 1) 148 // Snapshot board cells 149 var snapshot: nx_int = 0 150 snapshot = snapshot * 3 + s8[0] 151 snapshot = snapshot * 3 + s8[1] 152 snapshot = snapshot * 3 + s8[2] 153 snapshot = snapshot * 3 + s8[3] 154 snapshot = snapshot * 3 + s8[4] 155 snapshot = snapshot * 3 + s8[5] 156 snapshot = snapshot * 3 + s8[6] 157 snapshot = snapshot * 3 + s8[7] 158 snapshot = snapshot * 3 + s8[8] 159 nx_ttt_evaluate(s8) 160 var after: nx_int = 0 161 after = after * 3 + s8[0] 162 after = after * 3 + s8[1] 163 after = after * 3 + s8[2] 164 after = after * 3 + s8[3] 165 after = after * 3 + s8[4] 166 after = after * 3 + s8[5] 167 after = after * 3 + s8[6] 168 after = after * 3 + s8[7] 169 after = after * 3 + s8[8] 170 if after != snapshot { return __syscall(93, 12, 0, 0, 0, 0, 0) } 171 172 return 0 173}