code wiki / _hdl_build / nx_maze_gen.nx
nx_maze_gen.nx source
↩ module page · 120 lines · 6544 B
1// nx_maze_gen.nx -- LIB: three foundational rungs toward AUTONOMOUS S-class game emission, in pure-integer Nishi
2// (no float, no third-party):
3// procgen-solvable : mz_gen builds a maze by integer DFS recursive-backtracker -> a SPANNING TREE, so EVERY cell
4// is reachable BY CONSTRUCTION (an emitted level can never be unsolvable).
5// gate-solvable : mz_solve is a BFS solver that PROVES a start->exit path exists (returns its length) or
6// reports UNSOLVABLE (-1). The validation gate that makes "automatically emit a game" SAFE:
7// no emitted level ships unless the solver confirms it is winnable.
8// rules-engine : the maze is a grid with rules-as-data (wall bits per cell) + movement rules + win=reach-exit.
9// mz_seal isolates a cell (the gate's negative control: a deliberately-broken level the solver MUST catch).
10// Deterministic (LCG seed) -> reproducible emission. Cell = 4 wall bits N=1 E=2 S=4 W=8 (bit4=16 visited, scratch).
11// license_tier: ORIGINAL
12import "nx_syscalls.nx"
13const K_MAGIC_1103515245: i64 = 1103515245
14const K_MAGIC_12345: i64 = 12345
15const K_MAGIC_32767: i64 = 32767
16
17func mz_rng(seedbox: *i64) -> i64 { var x: i64 = seedbox[0]; x = x * K_MAGIC_1103515245 + K_MAGIC_12345; seedbox[0] = x; return (x >> 16) & K_MAGIC_32767 }
18
19// generate a perfect maze (spanning tree) into cells (w*h bytes). Every cell becomes reachable by construction.
20func mz_gen(seed: i64, w: i64, h: i64, cells: *u8) -> i64 {
21 var i: i64 = 0
22 while i < w*h { cells[i] = 15 as u8; i = i + 1 }
23 let seedbox: *i64 = sys_mmap(16) as *i64; seedbox[0] = seed
24 let stack: *i64 = sys_mmap(8 * (w*h + 8)) as *i64
25 let nb: *i64 = sys_mmap(8*4) as *i64; let nd: *i64 = sys_mmap(8*4) as *i64
26 var sp: i64 = 0
27 cells[0] = (cells[0] as i64 | 16) as u8
28 stack[sp] = 0; sp = sp + 1
29 while sp > 0 {
30 let cur: i64 = stack[sp-1]
31 let cx: i64 = cur % w; let cy: i64 = cur / w
32 var nc: i64 = 0
33 if cy > 0 { let nn: i64 = cur - w; if (cells[nn] as i64 & 16) == 0 { nb[nc] = nn; nd[nc] = 1; nc = nc + 1 } }
34 if cx < w-1 { let nn: i64 = cur + 1; if (cells[nn] as i64 & 16) == 0 { nb[nc] = nn; nd[nc] = 2; nc = nc + 1 } }
35 if cy < h-1 { let nn: i64 = cur + w; if (cells[nn] as i64 & 16) == 0 { nb[nc] = nn; nd[nc] = 4; nc = nc + 1 } }
36 if cx > 0 { let nn: i64 = cur - 1; if (cells[nn] as i64 & 16) == 0 { nb[nc] = nn; nd[nc] = 8; nc = nc + 1 } }
37 if nc == 0 { sp = sp - 1 } else {
38 let pick: i64 = mz_rng(seedbox) % nc
39 let nxt: i64 = nb[pick]; let dir: i64 = nd[pick]
40 cells[cur] = (cells[cur] as i64 & (255 - dir)) as u8
41 var opp: i64 = 0
42 if dir == 1 { opp = 4 } else { if dir == 2 { opp = 8 } else { if dir == 4 { opp = 1 } else { opp = 2 } } }
43 cells[nxt] = (cells[nxt] as i64 & (255 - opp)) as u8
44 cells[nxt] = (cells[nxt] as i64 | 16) as u8
45 stack[sp] = nxt; sp = sp + 1
46 }
47 }
48 i = 0
49 while i < w*h { cells[i] = (cells[i] as i64 & 15) as u8; i = i + 1 }
50 return 0
51}
52
53// BFS solver. Fills dist (w*h i64; -1 = unreachable). Returns dist[exit_c] (>=0 = solvable + path length, -1 = UNSOLVABLE).
54func mz_solve(cells: *u8, w: i64, h: i64, start: i64, exit_c: i64, dist: *i64, queue: *i64) -> i64 {
55 var i: i64 = 0
56 while i < w*h { dist[i] = 0 - 1; i = i + 1 }
57 var qh: i64 = 0; var qt: i64 = 0
58 dist[start] = 0; queue[qt] = start; qt = qt + 1
59 while qh < qt {
60 let cur: i64 = queue[qh]; qh = qh + 1
61 let cx: i64 = cur % w; let cy: i64 = cur / w
62 let cv: i64 = cells[cur] as i64
63 if (cv & 1) == 0 { if cy > 0 { let nn: i64 = cur - w; if dist[nn] == (0 - 1) { dist[nn] = dist[cur] + 1; queue[qt] = nn; qt = qt + 1 } } }
64 if (cv & 2) == 0 { if cx < w-1 { let nn: i64 = cur + 1; if dist[nn] == (0 - 1) { dist[nn] = dist[cur] + 1; queue[qt] = nn; qt = qt + 1 } } }
65 if (cv & 4) == 0 { if cy < h-1 { let nn: i64 = cur + w; if dist[nn] == (0 - 1) { dist[nn] = dist[cur] + 1; queue[qt] = nn; qt = qt + 1 } } }
66 if (cv & 8) == 0 { if cx > 0 { let nn: i64 = cur - 1; if dist[nn] == (0 - 1) { dist[nn] = dist[cur] + 1; queue[qt] = nn; qt = qt + 1 } } }
67 }
68 return dist[exit_c]
69}
70// count reachable cells in dist (dist != -1) -- a perfect maze => ALL w*h reachable (the by-construction proof).
71func mz_reachable(dist: *i64, n: i64) -> i64 { var c: i64 = 0; var i: i64 = 0; while i < n { if dist[i] != (0 - 1) { c = c + 1 } i = i + 1 } return c }
72
73// NEG-CONTROL: isolate cell c (all 4 of its walls + the facing wall of each neighbor) -> unreachable. The solver MUST catch this.
74func mz_seal(cells: *u8, w: i64, h: i64, c: i64) -> i64 {
75 cells[c] = 15 as u8
76 let cx: i64 = c % w; let cy: i64 = c / w
77 if cy > 0 { cells[c-w] = (cells[c-w] as i64 | 4) as u8 }
78 if cx < w-1 { cells[c+1] = (cells[c+1] as i64 | 8) as u8 }
79 if cy < h-1 { cells[c+w] = (cells[c+w] as i64 | 1) as u8 }
80 if cx > 0 { cells[c-1] = (cells[c-1] as i64 | 2) as u8 }
81 return 0
82}
83
84// render the maze as ASCII ((2h+1)x(2w+1)): '#'=wall, ' '=passage, 'S'=start, 'E'=exit. Returns length (NUL-terminated).
85func mz_ascii(cells: *u8, w: i64, h: i64, start: i64, exit_c: i64, buf: *u8) -> i64 {
86 var o: i64 = 0
87 var ry: i64 = 0
88 while ry < 2*h+1 {
89 var rx: i64 = 0
90 while rx < 2*w+1 {
91 var ch: i64 = 35
92 if (ry % 2) == 1 {
93 if (rx % 2) == 1 {
94 let cx: i64 = (rx-1)/2; let cy: i64 = (ry-1)/2; let c: i64 = cy*w + cx
95 ch = 32
96 if c == start { ch = 83 }
97 if c == exit_c { ch = 69 }
98 } else {
99 if rx == 0 { ch = 35 } else { if rx == 2*w { ch = 35 } else {
100 let cy: i64 = (ry-1)/2; let lcx: i64 = rx/2 - 1; let c: i64 = cy*w + lcx
101 if (cells[c] as i64 & 2) == 0 { ch = 32 }
102 } }
103 }
104 } else {
105 if (rx % 2) == 1 {
106 if ry == 0 { ch = 35 } else { if ry == 2*h { ch = 35 } else {
107 let cx: i64 = (rx-1)/2; let ty: i64 = ry/2 - 1; let c: i64 = ty*w + cx
108 if (cells[c] as i64 & 4) == 0 { ch = 32 }
109 } }
110 }
111 }
112 buf[o] = ch as u8; o = o + 1
113 rx = rx + 1
114 }
115 buf[o] = 10 as u8; o = o + 1
116 ry = ry + 1
117 }
118 buf[o] = 0 as u8
119 return o
120}