code wiki / (root) / nx_folkgame_align_gate.nx

nx_folkgame_align_gate.nx source

↩ module page · 234 lines · 11011 B

1// nx_folkgame_align_gate.nx -- GATE for the align family of nx_folkgame_lib. 2// 3// THE LOAD-BEARING TOOTH IS T5. The number of possible games of Tic-Tac-Toe -- counting every 4// distinct sequence of moves and stopping each line the moment somebody wins -- is 255,168. That 5// is a PUBLISHED COMBINATORIAL CONSTANT, not a number this estate computed and then asserted about 6// itself. A move generator that is wrong anywhere (one illegal move admitted, one legal move 7// missed, a win not detected, a draw detected early) cannot land on it by accident. Every other 8// tooth here checks a shape; T5 checks the RULES. 9// 10// The neg-controls are equally deliberate: three malformed specs that MUST be refused, because a 11// parser that accepts everything passes every positive test ever written against it. 12// license_tier: ORIGINAL No hw writes (Rule 26). 13import "nx_folkgame_lib.nx" 14// The opponent is proven HERE, in-process, so a mutation bite reaches it. Forking the player binary 15// would put a process boundary between the gate and the search and every mutant would read NOT-REACHED. 16import "nx_folkgame_ai_lib.nx" 17import "nx_lineconf_lib.nx" 18import "nx_gate_verdict.nx" 19 20const FGA_TTT_GAMES: i64 = 255168 21const FGA_TTT_CELLS: i64 = 9 22const FGA_TTT_LINES: i64 = 8 23const FGA_NMM_CELLS: i64 = 24 24const FGA_NMM_LINES: i64 = 16 25const FGA_MOR_LINES: i64 = 20 26const FGA_GOM_LINES: i64 = 572 27const FGA_ACHI_PLACEMENTS: i64 = 8 28const FGA_DARA_CELLS: i64 = 30 29const FGA_DARA_W: i64 = 6 30const FGA_DARA_H: i64 = 5 31const FGA_DARA_L: i64 = 3 32// 20 horizontal + 18 vertical on a 6x5 board of run 3. 33const FGA_DARA_LINES: i64 = 38 34// The same board WITH both diagonals. The two numbers differing is what proves the mode field is 35// read rather than ignored -- if it were ignored these would be equal and the control below fails. 36const FGA_DARA_LINES_DIAG: i64 = 62 37const FGA_SPEC: *u8 = "knowledge/compare/folkgames.align" 38const FGA_CONF: *u8 = "knowledge/folkgames.conf" 39 40func fga_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 41 42// Exhaustive game-tree walk. One move buffer and one snapshot slot PER PLY, allocated once by the 43// caller: allocating inside this would be an allocation in the hottest loop in the gate. 44func fga_games(base: i64, snap: *i64, sw: i64, mvb: *i64, mm: i64, scratch: *i64, d: i64) -> i64 { 45 let t: i64 = fg_terminal(base, scratch) 46 if t != FG_T_ONGOING { return 1 } 47 let out: *i64 = ((mvb as i64) + d * mm * FG_WORD) as *i64 48 let n: i64 = fg_moves(base, out) 49 let sv: *i64 = ((snap as i64) + d * sw * FG_WORD) as *i64 50 var total: i64 = 0 51 var i: i64 = 0 52 while i < n { 53 fg_save(base, sv) 54 fg_apply(base, out[i]) 55 total = total + fga_games(base, snap, sw, mvb, mm, scratch, d + 1) 56 fg_load(base, sv) 57 i = i + 1 58 } 59 return total 60} 61 62func fga_open_moves(base: i64) -> i64 { 63 let mm: i64 = fg_maxmoves(base) 64 let out: *i64 = sys_mmap(mm * FG_WORD) as *i64 65 let n: i64 = fg_moves(base, out) 66 sys_munmap(out as *u8, mm * FG_WORD) 67 return n 68} 69 70func main() -> i64 { 71 let ctr: *i64 = gv_ctr() 72 gv_head("nx_folkgame_align -- the align family plays by its own spec, and the spec parser refuses malformed rules" as *u8) 73 74 let ttt: i64 = fg_parse_named_file(FGA_SPEC, "tictactoe" as *u8) 75 gv_check("T1 tictactoe section parses from the shared family spec" as *u8, (ttt != 0) as i64, ctr) 76 var ok2: i64 = 0 77 var ok3: i64 = 0 78 var ok4: i64 = 0 79 var ok5: i64 = 0 80 if ttt != 0 { 81 if fg_cells(ttt) == FGA_TTT_CELLS { ok2 = 1 } 82 if fg_nlines(ttt) == FGA_TTT_LINES { ok3 = 1 } 83 if fga_open_moves(ttt) == FGA_TTT_CELLS { ok4 = 1 } 84 } 85 gv_check("T2 tictactoe board is 9 cells" as *u8, ok2, ctr) 86 gv_check("T3 the grid generator produced 8 lines: 3 rows, 3 columns, 2 diagonals" as *u8, ok3, ctr) 87 gv_check("T4 nine legal opening placements on an empty board" as *u8, ok4, ctr) 88 89 if ttt != 0 { 90 let sw: i64 = fg_snapwords(ttt) 91 let mm: i64 = fg_maxmoves(ttt) 92 let depth: i64 = FGA_TTT_CELLS + 1 93 let snap: *i64 = sys_mmap(depth * sw * FG_WORD) as *i64 94 let mvb: *i64 = sys_mmap(depth * mm * FG_WORD) as *i64 95 let scr: *i64 = sys_mmap(mm * FG_WORD) as *i64 96 fg_reset(ttt) 97 let g: i64 = fga_games(ttt, snap, sw, mvb, mm, scr, 0) 98 gv_puts(" measured complete tictactoe games: " as *u8) 99 gv_num(g) 100 gv_puts(" expected 255168\n" as *u8) 101 if g == FGA_TTT_GAMES { ok5 = 1 } 102 } 103 gv_check("T5 the exhaustive tictactoe game tree is exactly 255168, the published constant" as *u8, ok5, ctr) 104 105 let nmm: i64 = fg_parse_named_file(FGA_SPEC, "nine-mens-morris" as *u8) 106 gv_check("T6 nine-mens-morris section parses" as *u8, (nmm != 0) as i64, ctr) 107 var ok7: i64 = 0 108 var ok8: i64 = 0 109 var ok9: i64 = 0 110 var ok10: i64 = 0 111 if nmm != 0 { 112 if fg_cells(nmm) == FGA_NMM_CELLS { ok7 = 1 } 113 if fg_nlines(nmm) == FGA_NMM_LINES { ok8 = 1 } 114 if fga_open_moves(nmm) == FGA_NMM_CELLS { ok9 = 1 } 115 fg_reset(nmm) 116 fg_apply(nmm, 0) 117 fg_apply(nmm, 3) 118 fg_apply(nmm, 1) 119 fg_apply(nmm, 4) 120 fg_apply(nmm, 2) 121 if fg_phase(nmm) == FG_PH_CAPTURE { 122 if fg_side(nmm) == 1 { ok10 = 1 } 123 } 124 } 125 gv_check("T7 nine-mens-morris board is 24 points" as *u8, ok7, ctr) 126 gv_check("T8 sixteen mills are declared" as *u8, ok8, ctr) 127 gv_check("T9 twenty-four legal opening placements" as *u8, ok9, ctr) 128 gv_check("T10 completing mill 0-1-2 hands the SAME side a capture, it does not pass the turn" as *u8, ok10, ctr) 129 130 var ok11: i64 = 0 131 var ok12: i64 = 0 132 let mor: i64 = fg_parse_named_file(FGA_SPEC, "morabaraba" as *u8) 133 if mor != 0 { if fg_nlines(mor) == FGA_MOR_LINES { ok11 = 1 } } 134 let gom: i64 = fg_parse_named_file(FGA_SPEC, "gomoku" as *u8) 135 if gom != 0 { if fg_nlines(gom) == FGA_GOM_LINES { ok12 = 1 } } 136 gv_check("T11 morabaraba adds the four diagonals: 20 mills, not 16" as *u8, ok11, ctr) 137 gv_check("T12 the generator expands a 15x15 five-in-a-row board to 572 lines" as *u8, ok12, ctr) 138 139 var ok13: i64 = 0 140 let ach: i64 = fg_parse_named_file(FGA_SPEC, "achi" as *u8) 141 if ach != 0 { 142 fg_reset(ach) 143 fg_apply(ach, 0) 144 fg_apply(ach, 1) 145 fg_apply(ach, 2) 146 fg_apply(ach, 4) 147 fg_apply(ach, 3) 148 fg_apply(ach, 5) 149 fg_apply(ach, 7) 150 fg_apply(ach, 6) 151 if fg_phase(ach) == FG_PH_MOVE { ok13 = 1 } 152 } 153 gv_check("T13 achi switches from placing to sliding once both hands of four are spent" as *u8, ok13, ctr) 154 155 var ok14: i64 = 0 156 var ok15: i64 = 0 157 let dar: i64 = fg_parse_named_file(FGA_SPEC, "dara" as *u8) 158 if dar != 0 { 159 if fg_cells(dar) == FGA_DARA_CELLS { ok14 = 1 } 160 if fg_nlines(dar) == FGA_DARA_LINES { ok15 = 1 } 161 } 162 gv_check("T14 dara parses to a 30 point board" as *u8, ok14, ctr) 163 gv_check("T15 dara scores rows and columns only: 38 lines, not the 62 a diagonal board carries" as *u8, ok15, ctr) 164 165 var ok16: i64 = 0 166 if fg_gridlines_n(FGA_DARA_W, FGA_DARA_H, FGA_DARA_L, FG_LINE_ORTH) == FGA_DARA_LINES { 167 if fg_gridlines_n(FGA_DARA_W, FGA_DARA_H, FGA_DARA_L, FG_LINE_ALL) == FGA_DARA_LINES_DIAG { ok16 = 1 } 168 } 169 gv_check("neg-control-line-mode-actually-gates one board counted both ways gives 38 and 62, so the mode field cannot be being ignored" as *u8, ok16, ctr) 170 171 var ok17: i64 = 0 172 let tmm: i64 = fg_parse_named_file(FGA_SPEC, "twelve-mens-morris" as *u8) 173 if tmm != 0 { 174 if mor != 0 { 175 if fg_cells(tmm) == fg_cells(mor) { 176 if fg_nlines(tmm) == fg_nlines(mor) { ok17 = 1 } 177 } 178 } 179 } 180 gv_check("T17 twelve-mens-morris resolves through its alias to morabaraba's board: one rule set, two names, no copied data" as *u8, ok17, ctr) 181 182 let ch: *u8 = "name|a|A\nfamily|align\nalias|b\nname|b|B\nfamily|align\nalias|c\nname|c|C\nfamily|align\ncells|9\nlinelen|3\nlines|grid|3|3|3\nwin|line\n" as *u8 183 let chn: i64 = fga_slen(ch) 184 let onehop: i64 = fg_parse_named(ch, chn, "b" as *u8) 185 let twohop: i64 = fg_parse_named(ch, chn, "a" as *u8) 186 gv_check("T18 a single alias hop resolves to the target section" as *u8, (onehop != 0) as i64, ctr) 187 gv_check("neg-control-alias-chain-refused an alias pointing at another alias is REFUSED, so the parser cannot loop on its own data" as *u8, (twohop == 0) as i64, ctr) 188 189 var ok16: i64 = 0 190 let ttt2: i64 = fg_parse_named_file(FGA_SPEC, "tictactoe" as *u8) 191 if ttt2 != 0 { 192 let pw: i64 = lcf_int_of(FGA_CONF, "ai-piece-weight" as *u8) 193 let wsc: i64 = lcf_int_of(FGA_CONF, "ai-win-score" as *u8) 194 let dep: i64 = lcf_int_of(FGA_CONF, "ai-depth-tictactoe" as *u8) 195 let mxp: i64 = lcf_int_of(FGA_CONF, "selfplay-max-plies" as *u8) 196 var confok: i64 = 1 197 if pw == LCF_MISS { confok = 0 } 198 if wsc == LCF_MISS { confok = 0 } 199 if dep == LCF_MISS { confok = 0 } 200 if mxp == LCF_MISS { confok = 0 } 201 gv_need("knowledge/folkgames.conf carries the opponent parameters" as *u8, confok, ctr) 202 if confok == 1 { 203 let sw2: i64 = fg_snapwords(ttt2) 204 let mm2: i64 = fg_maxmoves(ttt2) 205 let pl: i64 = FGA_TTT_CELLS + dep + 2 206 let sn: *i64 = sys_mmap(pl * sw2 * FG_WORD) as *i64 207 let mb: *i64 = sys_mmap(pl * mm2 * FG_WORD) as *i64 208 let sc2: *i64 = sys_mmap(mm2 * FG_WORD) as *i64 209 fg_reset(ttt2) 210 let res: i64 = fgai_selfplay(ttt2, dep, sn, sw2, mb, mm2, sc2, pw, wsc, mxp) 211 gv_puts(" tictactoe selfplay at depth " as *u8) 212 gv_num(dep) 213 gv_puts(" returned terminal code " as *u8) 214 gv_num(res) 215 gv_puts(" where 1 is a first-player win, 2 a second-player win and 3 a draw\n" as *u8) 216 if res == FG_T_DRAW { ok16 = 1 } 217 } 218 } 219 gv_check("T16 the opponent playing ITSELF at full depth draws tictactoe, the published perfect-play result" as *u8, ok16, ctr) 220 221 let b1: *u8 = "cells|9\nlinelen|3\nlines|grid|3|3|3\nbogus|1\n" as *u8 222 let b2: *u8 = "cells|9\nlinelen|3\n" as *u8 223 let b3: *u8 = "cells|9\nlinelen|3\nline|0,1\n" as *u8 224 let r1: i64 = fg_parse(b1, fga_slen(b1)) 225 let r2: i64 = fg_parse(b2, fga_slen(b2)) 226 let r3: i64 = fg_parse(b3, fga_slen(b3)) 227 gv_check("neg-control-unknown-key-refused a spec key nobody implements is REFUSED, never ignored" as *u8, (r1 == 0) as i64, ctr) 228 gv_check("neg-control-no-lines-refused an align game with no line can never be won, so it is REFUSED" as *u8, (r2 == 0) as i64, ctr) 229 gv_check("neg-control-short-line-refused an explicit line shorter than linelen is REFUSED" as *u8, (r3 == 0) as i64, ctr) 230 231 let rc: i64 = gv_verdict("FOLKGAME-ALIGN" as *u8, ctr, "align family plays to a published game-tree count and refuses malformed specs" as *u8) 232 sys_exit(rc) 233 return rc 234}