nx_wasm_2048.nx source
↩ module page · 204 lines · 8272 B
1// nx_wasm_2048.nx -- the SECOND sovereign browser game genre: a 2048 sliding-tile PUZZLE, pure-Nishi NO-FLOAT,
2// BASE-RELATIVE (same code native [base=mmap, render a PNG] and in our WASM [base=0]). Structurally distinct
3// from the collect-class arcade (merge logic + RNG spawns, not avatar movement) -> proves the emitter generalises
4// across genres ([[feedback-games-as-test-avenues-for-layer-completeness]]). Integer state machine + integer
5// rasteriser + the sovereign Nishi font for tile numbers. Exports init/tick/render/score/ww/hh/fb_off = the SAME
6// wasm interface as the arcade, so the SAME blit-only html packager serves it. license_tier: ORIGINAL
7import "nx_nishi_font_core.nx"
8const O_MAGIC_1103515245: i64 = 1103515245
9const O_MAGIC_12345: i64 = 12345
10const O_MAGIC_2025: i64 = 2025
11const W: i64 = 240
12const H: i64 = 240
13const N: i64 = 4
14const MARGIN: i64 = 12
15const CELL: i64 = 50
16const GAP: i64 = 5
17const O_FB: i64 = 0 // W*H i64 packed RGB = 460800
18const O_GRID: i64 = 460800 // N*N i64 tile values (0=empty, else power of 2)
19const O_ST: i64 = 461824 // [0]=score [1]=rng [2]=moved [3]=won
20const O_LINE: i64 = 462848 // scratch line (N i64)
21const O_TMP: i64 = 463360 // scratch line (N i64)
22const O_MSK: i64 = 464384 // font glyph scratch (base-relative)
23const O_STR: i64 = 473600 // tile-number string scratch
24
25func rgb(r: i64, g: i64, b: i64) -> i64 { return (r & 255) | ((g & 255) << 8) | ((b & 255) << 16) }
26func fillr(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, c: i64) -> i64 {
27 let fb: *i64 = (base + O_FB) as *i64
28 var yy: i64 = y0; if yy < 0 { yy = 0 }
29 var ye: i64 = y1; if ye > H { ye = H }
30 while yy < ye {
31 var xx: i64 = x0; if xx < 0 { xx = 0 }
32 var xe: i64 = x1; if xe > W { xe = W }
33 while xx < xe { fb[yy*W + xx] = c; xx = xx + 1 }
34 yy = yy + 1
35 }
36 return 0
37}
38func gget(base: i64, i: i64) -> i64 { let g: *i64 = (base + O_GRID) as *i64; return g[i] }
39func gset(base: i64, i: i64, v: i64) -> i64 { let g: *i64 = (base + O_GRID) as *i64; g[i] = v; return 0 }
40func lget(base: i64, off: i64, i: i64) -> i64 { let p: *i64 = (base + off) as *i64; return p[i] }
41func lset(base: i64, off: i64, i: i64, v: i64) -> i64 { let p: *i64 = (base + off) as *i64; p[i] = v; return 0 }
42
43// integer LCG (no float) -> a deterministic 31-bit stream.
44func rng_next(base: i64) -> i64 { let st: *i64 = (base + O_ST) as *i64; st[1] = (st[1]*O_MAGIC_1103515245 + O_MAGIC_12345) & 0x7fffffff; return st[1] }
45
46// place a 2 in a random empty cell (deterministic via the LCG).
47func spawn(base: i64) -> i64 {
48 var empties: i64 = 0
49 var i: i64 = 0
50 while i < N*N { if gget(base,i)==0 { empties = empties + 1 } i = i + 1 }
51 if empties == 0 { return 0 }
52 let pick: i64 = rng_next(base) % empties
53 var seen: i64 = 0
54 var placed: i64 = 0
55 i = 0
56 while i < N*N {
57 if placed == 0 { if gget(base,i)==0 { if seen==pick { gset(base,i,2); placed=1 } seen = seen + 1 } }
58 i = i + 1
59 }
60 return 0
61}
62func init_impl(base: i64) -> i64 {
63 let st: *i64 = (base + O_ST) as *i64
64 st[0]=0; st[1]=O_MAGIC_2025; st[2]=0; st[3]=0
65 var i: i64 = 0
66 while i < N*N { gset(base,i,0); i = i + 1 }
67 spawn(base); spawn(base)
68 return 0
69}
70
71// compact+merge O_LINE (N cells) toward index 0; returns score delta; sets st[2]=moved if the line changed.
72func line_move(base: i64) -> i64 {
73 var w: i64 = 0
74 var i: i64 = 0
75 while i < N { let v: i64 = lget(base,O_LINE,i); if v != 0 { lset(base,O_TMP,w,v); w = w + 1 } i = i + 1 }
76 while w < N { lset(base,O_TMP,w,0); w = w + 1 }
77 var score: i64 = 0
78 i = 0
79 while i < N-1 {
80 let a: i64 = lget(base,O_TMP,i)
81 if a != 0 { if a == lget(base,O_TMP,i+1) { let m: i64 = a*2; lset(base,O_TMP,i,m); lset(base,O_TMP,i+1,0); score = score + m; i = i + 2 } else { i = i + 1 } }
82 else { i = i + 1 }
83 }
84 // compact O_TMP in place (close the gap the merge opened)
85 var w2: i64 = 0
86 i = 0
87 while i < N { let v: i64 = lget(base,O_TMP,i); if v != 0 { lset(base,O_TMP,w2,v); if i != w2 { lset(base,O_TMP,i,0) } w2 = w2 + 1 } i = i + 1 }
88 // change detection + write back into O_LINE
89 var changed: i64 = 0
90 i = 0
91 while i < N { if lget(base,O_TMP,i) != lget(base,O_LINE,i) { changed = 1 } i = i + 1 }
92 if changed == 1 { let st: *i64 = (base + O_ST) as *i64; st[2] = 1 }
93 i = 0
94 while i < N { lset(base,O_LINE,i, lget(base,O_TMP,i)); i = i + 1 }
95 return score
96}
97// read the k-th line (row or col) into O_LINE in moving order (index 0 = the edge tiles slide toward).
98func read_line(base: i64, dir: i64, k: i64) -> i64 {
99 var i: i64 = 0
100 while i < N {
101 var idx: i64 = 0
102 if dir == 0 { idx = i*N + k } // UP : col k, top edge
103 if dir == 1 { idx = (N-1-i)*N + k } // DOWN : col k, bottom edge
104 if dir == 2 { idx = k*N + i } // LEFT : row k, left edge
105 if dir == 3 { idx = k*N + (N-1-i) } // RIGHT : row k, right edge
106 lset(base, O_LINE, i, gget(base, idx))
107 i = i + 1
108 }
109 return 0
110}
111func write_line(base: i64, dir: i64, k: i64) -> i64 {
112 var i: i64 = 0
113 while i < N {
114 var idx: i64 = 0
115 if dir == 0 { idx = i*N + k }
116 if dir == 1 { idx = (N-1-i)*N + k }
117 if dir == 2 { idx = k*N + i }
118 if dir == 3 { idx = k*N + (N-1-i) }
119 gset(base, idx, lget(base, O_LINE, i))
120 i = i + 1
121 }
122 return 0
123}
124func tick_impl(base: i64, dir: i64) -> i64 {
125 let st: *i64 = (base + O_ST) as *i64
126 st[2] = 0
127 var k: i64 = 0
128 while k < N {
129 read_line(base, dir, k)
130 let sc: i64 = line_move(base)
131 write_line(base, dir, k)
132 st[0] = st[0] + sc
133 k = k + 1
134 }
135 if st[2] == 1 { spawn(base) } // a new tile appears only if the board actually moved
136 return 0
137}
138
139func tile_color(v: i64) -> i64 {
140 if v == 0 { return rgb(58,54,48) }
141 if v == 2 { return rgb(238,228,218) }
142 if v == 4 { return rgb(237,224,200) }
143 if v == 8 { return rgb(242,177,121) }
144 if v == 16 { return rgb(245,149,99) }
145 if v == 32 { return rgb(246,124,95) }
146 if v == 64 { return rgb(246,94,59) }
147 if v == 128 { return rgb(237,207,114) }
148 if v == 256 { return rgb(237,204,97) }
149 if v == 512 { return rgb(237,200,80) }
150 return rgb(237,197,63)
151}
152// write decimal of v into O_STR; return length.
153func num_str(base: i64, v: i64) -> i64 {
154 let s: *u8 = (base + O_STR) as *u8
155 if v == 0 { s[0]=48 as u8; s[1]=0 as u8; return 1 }
156 let t: *u8 = (base + O_STR + 16) as *u8
157 var k: i64 = 0
158 var m: i64 = v
159 while m > 0 { t[k] = (48 + (m%10)) as u8; m = m/10; k = k + 1 }
160 var j: i64 = 0
161 while j < k { s[j] = t[k-1-j]; j = j + 1 }
162 s[k] = 0 as u8
163 return k
164}
165func render_impl(base: i64) -> i64 {
166 let fb: *i64 = (base + O_FB) as *i64
167 let bg: i64 = rgb(28,26,24)
168 var p: i64 = 0
169 while p < W*H { fb[p] = bg; p = p + 1 }
170 let mask: *u8 = (base + O_MSK) as *u8
171 var r: i64 = 0
172 while r < N {
173 var c: i64 = 0
174 while c < N {
175 let x: i64 = MARGIN + c*(CELL+GAP)
176 let y: i64 = MARGIN + r*(CELL+GAP)
177 let v: i64 = gget(base, r*N + c)
178 fillr(base, x, y, x+CELL, y+CELL, tile_color(v))
179 if v > 0 {
180 let slen: i64 = num_str(base, v)
181 let ch: i64 = 22
182 let adv: i64 = nf_adv_px(ch)
183 let tw: i64 = slen * adv
184 let tx: i64 = x + (CELL - tw)/2
185 let ty: i64 = y + (CELL - ch)/2
186 var ink: i64 = rgb(60,52,44)
187 if v > 4 { ink = rgb(248,246,242) }
188 nf_draw_text_mem(fb, W, H, mask, tx, ty, ch, (base + O_STR) as *u8, slen, ink)
189 }
190 c = c + 1
191 }
192 r = r + 1
193 }
194 return 0
195}
196
197// ---- WASM exports (base = 0); same interface the arcade uses ----
198func init() -> i64 { return init_impl(0) }
199func tick(dir: i64) -> i64 { return tick_impl(0, dir) }
200func render() -> i64 { return render_impl(0) }
201func score() -> i64 { let st: *i64 = (0 + O_ST) as *i64; return st[0] }
202func fb_off() -> i64 { return O_FB }
203func ww() -> i64 { return W }
204func hh() -> i64 { return H }