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 }