code wiki / (root) / nx_wasm_platformer.nx

nx_wasm_platformer.nx source

↩ module page · 148 lines · 7175 B

1// nx_wasm_platformer.nx -- the THIRD sovereign browser game genre: a PLATFORMER auto-runner, pure-Nishi NO-FLOAT, 2// BASE-RELATIVE (same code native [base=mmap -> PNG] and in our WASM [base=0]). The hero auto-runs right; the 3// player JUMPS (integer parabola: vy=-IMPULSE, +GRAV/frame) to grab floating coins and clears ground foes; 4// reaching the exit wins. Structurally distinct from the collect-arcade (gravity + jump arc, not free movement) 5// and 2048 (slide+merge) -> a third genre on the SAME engine. Integer state machine + integer rasteriser + the 6// sovereign Nishi font for the HUD score. Exports init/tick/render/score/ww/hh/fb_off = the SAME wasm interface 7// the arcade/2048 use, so the SAME blit-only html packager serves it. This is the browser delivery of the 8// Platformer genre the universal-builder catalog scaffolds (nx_game_render / nx_game_scaffold). license_tier: ORIGINAL 9import "nx_nishi_font_core.nx" 10 11const W: i64 = 320 12const H: i64 = 120 13const O_FB: i64 = 0 // W*H i64 packed RGB = 307200 bytes 14const O_ST: i64 = 307200 // state ints (64 i64) 15const O_COIN: i64 = 307712 // coin collected flags (16 i64) 16const O_FOE: i64 = 307840 // foe cleared flags (16 i64) 17const O_MSK: i64 = 307968 // font glyph scratch (base-relative, 16384 bytes) 18const O_STR: i64 = 324352 // HUD number string scratch (64 bytes) 19 20const NCOIN: i64 = 4 21const NFOE: i64 = 2 22const RUN: i64 = 2 // hero auto-run px/frame 23const GRAV: i64 = 1 // gravity px/frame^2 (integer) 24const IMPULSE: i64 = 9 // jump launch velocity (apex ~45px) 25const GROUNDY: i64 = 92 // hero top-y when standing (hero bottom = 108 = ground band) 26const HEROW: i64 = 12 27const HEROH: i64 = 16 28const COINY: i64 = 58 // floating coin center-y (must jump to reach) 29const EXITX: i64 = 300 30 31// state index helpers: 0=hero_x 1=hero_y 2=vy 3=on_ground 4=score 5=coins 6=foes 7=won 8=frame 32func sget(base: i64, i: i64) -> i64 { let p: *i64 = (base + O_ST) as *i64; return p[i] } 33func sset(base: i64, i: i64, v: i64) -> i64 { let p: *i64 = (base + O_ST) as *i64; p[i] = v; return 0 } 34func cget(base: i64, i: i64) -> i64 { let p: *i64 = (base + O_COIN) as *i64; return p[i] } 35func cset(base: i64, i: i64, v: i64) -> i64 { let p: *i64 = (base + O_COIN) as *i64; p[i] = v; return 0 } 36func fget(base: i64, i: i64) -> i64 { let p: *i64 = (base + O_FOE) as *i64; return p[i] } 37func fset(base: i64, i: i64, v: i64) -> i64 { let p: *i64 = (base + O_FOE) as *i64; p[i] = v; return 0 } 38 39func coin_x(i: i64) -> i64 { if i == 0 { return 56 } if i == 1 { return 116 } if i == 2 { return 176 } return 236 } 40func foe_x(i: i64) -> i64 { if i == 0 { return 92 } return 206 } 41 42func rgb(r: i64, g: i64, b: i64) -> i64 { return (r & 255) | ((g & 255) << 8) | ((b & 255) << 16) } 43func fillr(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, c: i64) -> i64 { 44 let fb: *i64 = (base + O_FB) as *i64 45 var yy: i64 = y0; if yy < 0 { yy = 0 } 46 var ye: i64 = y1; if ye > H { ye = H } 47 while yy < ye { 48 var xx: i64 = x0; if xx < 0 { xx = 0 } 49 var xe: i64 = x1; if xe > W { xe = W } 50 while xx < xe { fb[yy*W + xx] = c; xx = xx + 1 } 51 yy = yy + 1 52 } 53 return 0 54} 55 56func init_impl(base: i64) -> i64 { 57 sset(base, 0, 8); sset(base, 1, GROUNDY); sset(base, 2, 0); sset(base, 3, 1) 58 sset(base, 4, 0); sset(base, 5, 0); sset(base, 6, 0); sset(base, 7, 0); sset(base, 8, 0) 59 var i: i64 = 0 60 while i < NCOIN { cset(base, i, 0); i = i + 1 } 61 i = 0 62 while i < NFOE { fset(base, i, 0); i = i + 1 } 63 return 0 64} 65 66// one frame of the game. cmd==0 requests a JUMP (only if grounded); EVERY tick advances one frame (auto-run + 67// gravity + collisions + win). So under the blit-only keydown packager each arrow press steps the world, and Up 68// also jumps; under a real-time loop the same tick is called per animation frame. 69func tick_impl(base: i64, cmd: i64) -> i64 { 70 if sget(base, 7) == 1 { return 0 } 71 if cmd == 0 { if sget(base, 3) == 1 { sset(base, 2, 0 - IMPULSE); sset(base, 3, 0) } } 72 sset(base, 0, sget(base, 0) + RUN) 73 sset(base, 2, sget(base, 2) + GRAV) 74 sset(base, 1, sget(base, 1) + sget(base, 2)) 75 if sget(base, 1) >= GROUNDY { sset(base, 1, GROUNDY); sset(base, 2, 0); sset(base, 3, 1) } 76 let hx: i64 = sget(base, 0); let hy: i64 = sget(base, 1) 77 var i: i64 = 0 78 while i < NCOIN { 79 if cget(base, i) == 0 { 80 let cx: i64 = coin_x(i) 81 if hx + HEROW > cx - 12 { if hx < cx + 12 { if hy < 74 { 82 cset(base, i, 1); sset(base, 5, sget(base, 5) + 1); sset(base, 4, sget(base, 4) + 1) 83 } } } 84 } 85 i = i + 1 86 } 87 var j: i64 = 0 88 while j < NFOE { 89 if fget(base, j) == 0 { 90 let fx: i64 = foe_x(j) 91 if hx + HEROW > fx - 10 { if hx < fx + 10 { 92 fset(base, j, 1); sset(base, 6, sget(base, 6) + 1); sset(base, 4, sget(base, 4) + 1) 93 } } 94 } 95 j = j + 1 96 } 97 if sget(base, 0) >= EXITX { sset(base, 7, 1) } 98 sset(base, 8, sget(base, 8) + 1) 99 return 0 100} 101 102func num_str(base: i64, v: i64) -> i64 { 103 let s: *u8 = (base + O_STR) as *u8 104 if v == 0 { s[0] = 48 as u8; s[1] = 0 as u8; return 1 } 105 let t: *u8 = (base + O_STR + 16) as *u8 106 var k: i64 = 0; var m: i64 = v 107 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 108 var jj: i64 = 0 109 while jj < k { s[jj] = t[k - 1 - jj]; jj = jj + 1 } 110 s[k] = 0 as u8 111 return k 112} 113 114func render_impl(base: i64) -> i64 { 115 let fb: *i64 = (base + O_FB) as *i64 116 let sky: i64 = rgb(28, 38, 60) 117 var p: i64 = 0 118 while p < W*H { fb[p] = sky; p = p + 1 } 119 fillr(base, 0, GROUNDY + HEROH, W, H, rgb(46, 84, 52)) // ground band 120 fillr(base, 0, GROUNDY + HEROH, W, GROUNDY + HEROH + 2, rgb(70, 120, 76)) // ground edge 121 fillr(base, EXITX, 36, EXITX + 8, GROUNDY + HEROH, rgb(60, 200, 210)) // exit pole (cyan) 122 fillr(base, EXITX, 36, EXITX + 22, 50, rgb(60, 200, 210)) // exit flag 123 var i: i64 = 0 124 while i < NCOIN { 125 if cget(base, i) == 0 { let cx: i64 = coin_x(i); fillr(base, cx - 7, COINY - 7, cx + 7, COINY + 7, rgb(255, 210, 70)) } 126 i = i + 1 127 } 128 var j: i64 = 0 129 while j < NFOE { 130 if fget(base, j) == 0 { let fx: i64 = foe_x(j); fillr(base, fx - 8, GROUNDY, fx + 8, GROUNDY + HEROH, rgb(224, 80, 58)) } 131 j = j + 1 132 } 133 let hx: i64 = sget(base, 0); let hy: i64 = sget(base, 1) 134 fillr(base, hx, hy, hx + HEROW, hy + HEROH, rgb(90, 210, 122)) // hero (green) 135 let slen: i64 = num_str(base, sget(base, 4)) 136 let mask: *u8 = (base + O_MSK) as *u8 137 nf_draw_text_mem(fb, W, H, mask, 6, 5, 14, (base + O_STR) as *u8, slen, rgb(245, 246, 250)) 138 return 0 139} 140 141// ---- WASM exports (base = 0); the SAME interface arcade/2048 use ---- 142func init() -> i64 { return init_impl(0) } 143func tick(dir: i64) -> i64 { return tick_impl(0, dir) } 144func render() -> i64 { return render_impl(0) } 145func score() -> i64 { let st: *i64 = (0 + O_ST) as *i64; return st[4] } 146func fb_off() -> i64 { return O_FB } 147func ww() -> i64 { return W } 148func hh() -> i64 { return H }