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}