code wiki / (root) / nx_wasm_2048.nx

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 }