nx_folkgame_race_gate.nx source
↩ module page · 167 lines · 7862 B
1// nx_folkgame_race_gate.nx -- GATE for the race (track) family of nx_folkgame_race_lib.
2//
3// THE LOAD-BEARING TOOTH IS T2/T3: the opening pip count is 167 PER SIDE. That is a published
4// constant of the game -- two checkers 24 points from home, five 13, three 8, five 6 -- not a number
5// this estate picked. It is checked for BOTH players because a single side proves almost nothing: an
6// off-by-one in the track indexing, a reversed direction, or a pip formula that reads the wrong end
7// would still let ONE side total 167 by luck. Both sides landing on it at once pins the direction,
8// the indexing and the formula together.
9// T6 IS THE OTHER HALF: a race is not deterministic but a REPLAY of one must be. The randomiser's
10// seed lives inside the saved position, so the same seed must produce the same sequence -- and a
11// SECOND seed must produce a different one, or the tooth would pass on a die that always returns the
12// same face.
13// license_tier: ORIGINAL No hw writes (Rule 26).
14import "nx_folkgame_race_lib.nx"
15import "nx_gate_verdict.nx"
16
17const FGRG_SPEC: *u8 = "knowledge/compare/folkgames.race"
18// Published: the backgammon opening position is 167 pips for each player.
19const FGRG_PIP: i64 = 167
20const FGRG_TRACK: i64 = 24
21const FGRG_PIECES: i64 = 15
22// Two arbitrary but FIXED seeds. They are not tuned to make anything pass -- any two distinct values
23// exercise the same property, and the second exists only so "same seed reproduces" cannot be
24// satisfied by a die that ignores its seed entirely.
25const FGRG_SEED_A: i64 = 20260827
26const FGRG_SEED_B: i64 = 99999937
27const FGRG_ROLLS: i64 = 64
28const FGRG_PLIES: i64 = 200
29
30func fgrg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
31
32func main() -> i64 {
33 let ctr: *i64 = gv_ctr()
34 gv_head("nx_folkgame_race -- the track family opens at the published pip count and conserves its checkers" as *u8)
35
36 let bg: i64 = fg_parse_named_file(FGRG_SPEC, "backgammon" as *u8)
37 gv_check("T1 backgammon section parses from the shared family spec" as *u8, (bg != 0) as i64, ctr)
38 var ok2: i64 = 0
39 var ok3: i64 = 0
40 var ok4: i64 = 0
41 var ok5: i64 = 0
42 var p1: i64 = 0 - 1
43 var p2: i64 = 0 - 1
44 if bg != 0 {
45 if fg_cells(bg) == FGRG_TRACK { if fgr_pieces(bg) == FGRG_PIECES { ok2 = 1 } }
46 fgr_reset(bg, FGRG_SEED_A)
47 p1 = fgr_pip(bg, 1)
48 p2 = fgr_pip(bg, 2)
49 gv_puts(" opening pips: first player " as *u8)
50 gv_num(p1)
51 gv_puts(", second player " as *u8)
52 gv_num(p2)
53 gv_puts(", published constant 167\n" as *u8)
54 if p1 == FGRG_PIP { ok3 = 1 }
55 if p2 == FGRG_PIP { ok4 = 1 }
56 if fgr_total(bg, 1) == FGRG_PIECES { if fgr_total(bg, 2) == FGRG_PIECES { ok5 = 1 } }
57 }
58 gv_check("T1b twenty-four points and fifteen checkers a side" as *u8, ok2, ctr)
59 gv_check("T2 the FIRST player opens on exactly 167 pips, the published constant" as *u8, ok3, ctr)
60 gv_check("T3 the SECOND player opens on exactly 167 too, which pins direction and indexing together" as *u8, ok4, ctr)
61 gv_check("T4 fifteen checkers a side at the opening, counting board, bar and borne off" as *u8, ok5, ctr)
62
63 var ok6: i64 = 0
64 var ok7: i64 = 0
65 var inrange: i64 = 1
66 if bg != 0 {
67 let a: *i64 = sys_mmap(FGRG_ROLLS * FG_WORD) as *i64
68 let b: *i64 = sys_mmap(FGRG_ROLLS * FG_WORD) as *i64
69 let c: *i64 = sys_mmap(FGRG_ROLLS * FG_WORD) as *i64
70 fgr_reset(bg, FGRG_SEED_A)
71 var i: i64 = 0
72 while i < FGRG_ROLLS { a[i] = fgr_die(bg); i = i + 1 }
73 fgr_reset(bg, FGRG_SEED_A)
74 var j: i64 = 0
75 while j < FGRG_ROLLS { b[j] = fgr_die(bg); j = j + 1 }
76 fgr_reset(bg, FGRG_SEED_B)
77 var k: i64 = 0
78 while k < FGRG_ROLLS { c[k] = fgr_die(bg); k = k + 1 }
79 var same: i64 = 1
80 var diff: i64 = 0
81 var m: i64 = 0
82 while m < FGRG_ROLLS {
83 if a[m] != b[m] { same = 0 }
84 if a[m] != c[m] { diff = 1 }
85 if a[m] < 1 { inrange = 0 }
86 if a[m] > 6 { inrange = 0 }
87 m = m + 1
88 }
89 if same == 1 { ok6 = 1 }
90 if diff == 1 { ok7 = 1 }
91 }
92 gv_check("T5 every die face is inside one to six, so the randomiser cannot produce an impossible roll" as *u8, inrange, ctr)
93 gv_check("T6 the SAME seed reproduces the same sequence, so a saved position replays the die that follows it" as *u8, ok6, ctr)
94 gv_check("neg-control-a-DIFFERENT-seed-diverges so T6 cannot be satisfied by a die that ignores its seed" as *u8, ok7, ctr)
95
96 var ok8: i64 = 0
97 var plies: i64 = 0
98 var bad: i64 = 0
99 if bg != 0 {
100 let mm: i64 = fg_maxmoves(bg)
101 let out: *i64 = sys_mmap(mm * FG_WORD) as *i64
102 fgr_reset(bg, FGRG_SEED_A)
103 var t: i64 = 0
104 while t < FGRG_PLIES {
105 if fgr_terminal(bg) != FG_T_ONGOING { t = FGRG_PLIES }
106 if t < FGRG_PLIES {
107 let d: i64 = fgr_die(bg)
108 let n: i64 = fgr_moves(bg, d, out)
109 if n > 0 {
110 fgr_apply(bg, out[0], d)
111 plies = plies + 1
112 }
113 if fgr_total(bg, 1) != FGRG_PIECES { bad = bad + 1 }
114 if fgr_total(bg, 2) != FGRG_PIECES { bad = bad + 1 }
115 let sc: *i64 = fg_scal(bg)
116 sc[FG_S_SIDE] = 3 - sc[FG_S_SIDE]
117 t = t + 1
118 }
119 }
120 gv_puts(" played " as *u8)
121 gv_num(plies)
122 gv_puts(" plies, checker-count violations: " as *u8)
123 gv_num(bad)
124 gv_puts("\n" as *u8)
125 if bad == 0 { if plies > 0 { ok8 = 1 } }
126 }
127 gv_subjects("plies played under the conservation check" as *u8, plies, ctr)
128 gv_check("T7 checkers are conserved at every ply of a real played-out race, across board, bar and borne off" as *u8, ok8, ctr)
129
130 var ok9: i64 = 0
131 if bg != 0 {
132 fgr_reset(bg, FGRG_SEED_A)
133 let before: i64 = fgr_pip(bg, 1)
134 fgr_apply(bg, 0, 1)
135 let after: i64 = fgr_pip(bg, 1)
136 if after == before - 1 { ok9 = 1 }
137 }
138 gv_check("neg-control-the-pip-count-can-move moving one checker one point lowers the first player's pips by exactly one, so the 167 teeth are measuring something live" as *u8, ok9, ctr)
139
140 var ok10: i64 = 0
141 let nd: i64 = fg_parse_named_file(FGRG_SPEC, "nard" as *u8)
142 if nd != 0 {
143 if bg != 0 {
144 fgr_reset(nd, FGRG_SEED_A)
145 let hdn: *i64 = fg_hdr(nd)
146 let hdb: *i64 = fg_hdr(bg)
147 if fgr_pip(nd, 1) == FGRG_PIP {
148 if hdn[FG_H_HIT] == 0 { if hdb[FG_H_HIT] == 1 { ok10 = 1 } }
149 }
150 }
151 }
152 gv_check("T8 nard opens on the same 167 pips as backgammon and differs from it in exactly the hit flag, which is a DATA difference and not a second program" as *u8, ok10, ctr)
153
154 let b1: *u8 = "family|race\ncells|24\npieces|15\ndice|2|6\n" as *u8
155 let b2: *u8 = "family|race\ncells|24\ntrack|24\npieces|15\n" as *u8
156 let b3: *u8 = "family|race\ncells|20\ntrack|24\npieces|15\ndice|2|6\n" as *u8
157 let r1: i64 = fg_parse(b1, fgrg_slen(b1))
158 let r2: i64 = fg_parse(b2, fgrg_slen(b2))
159 let r3: i64 = fg_parse(b3, fgrg_slen(b3))
160 gv_check("neg-control-no-track-refused a race game without a track row is REFUSED" as *u8, (r1 == 0) as i64, ctr)
161 gv_check("neg-control-no-dice-refused a race with no randomiser is not a race, and is REFUSED by name" as *u8, (r2 == 0) as i64, ctr)
162 gv_check("neg-control-track-mismatch-refused cells that do not match the declared track are REFUSED with both numbers named" as *u8, (r3 == 0) as i64, ctr)
163
164 let rc: i64 = gv_verdict("FOLKGAME-RACE" as *u8, ctr, "the track family opens at the published 167 pips on both sides and conserves its checkers" as *u8)
165 sys_exit(rc)
166 return rc
167}