code wiki / _hdl_build / nishi_game.nx
nishi_game.nx source
↩ module page · 305 lines · 12421 B
1// nishi_game.nx -- GAME-GUI R1: the sovereign GAME ENGINE WINDOW on native Windows. Presents ANIMATED
2// frames rendered by the SAME baselined textured rasterizer (nx_textured_tri -> nx_tex_sample bilinear,
3// pure integer) into a real Win32 window via SetDIBitsToDevice -- the game ecosystem's GUI+video rung:
4// sovereign frames, hardware-up, no gcc/SDL/GL/toolkit. Scene: textured scrolling-checker band + a
5// BOUNCING textured triangle + animated HUD bar + live frame counter (two screenshots differ => video).
6// Built by nx_pe_compile_win_game.nx (compile me with nx_compile_x86 -> /tmp/nxgame.s first).
7import "nx_syscalls.nx"
8import "nx_font8x8.nx"
9import "nx_textured_tri_fast.nx" // E1-R1 fast path (bit-identical to nx_textured_tri, ~2.8x)
10import "nx_scene3d.nx" // E6-R3: depth-buffered Lambert-lit 3D cube in the live window
11
12func win32(id: i64, args: *i64) -> i64 { return id }
13func win32addr(id: i64) -> i64 { return id }
14
15// user32/gdi32/kernel32 imports by FFI id (IAT slot), provided by the keystone wrapper.
16const W_GetModuleHandleA: i64 = 6
17const W_RegisterClassExA: i64 = 7
18const W_CreateWindowExA: i64 = 8
19const W_ShowWindow: i64 = 9
20const W_PeekMessageA: i64 = 10
21const W_TranslateMessage: i64 = 11
22const W_DispatchMessageA: i64 = 12
23const W_DefWindowProcA: i64 = 13
24const W_GetDC: i64 = 14
25const W_ReleaseDC: i64 = 15
26const W_IsWindow: i64 = 16
27const W_LoadCursorA: i64 = 17
28const W_SetDIBitsToDevice: i64 = 18
29const W_GetClientRect: i64 = 24
30
31const GW: i64 = 640
32const GH: i64 = 480
33
34func w16(p: *u8, o: i64, v: i64) -> i64 { p[o]=(v&0xff) as u8; p[o+1]=((v>>8)&0xff) as u8; return 0 }
35func w32(p: *u8, o: i64, v: i64) -> i64 {
36 p[o]=(v&0xff) as u8; p[o+1]=((v>>8)&0xff) as u8; p[o+2]=((v>>16)&0xff) as u8; p[o+3]=((v>>24)&0xff) as u8; return 0
37}
38func wp(p: *u8, o: i64, v: i64) -> i64 { w32(p,o,v); w32(p,o+4, v / 4294967296); return 0 }
39func rd32(p: *u8, o: i64) -> i64 { return (p[o]&0xff) | ((p[o+1]&0xff)<<8) | ((p[o+2]&0xff)<<16) | ((p[o+3]&0xff)<<24) }
40
41// RGB (3-byte Image) drawing helpers -- everything renders into the sovereign RGB target,
42// then ONE rgb->bgra bridge feeds the DIB blit (same split the browser uses).
43func fill_rect3(fb: *Image, x0: i64, y0: i64, rw: i64, rh: i64, r: i64, g: i64, b: i64) -> i64 {
44 let px: *u8 = fb.pixels
45 var y: i64 = y0
46 if y < 0 { y = 0 }
47 var ye: i64 = y0 + rh
48 if ye > fb.height { ye = fb.height }
49 while y < ye {
50 var x: i64 = x0
51 if x < 0 { x = 0 }
52 var xe: i64 = x0 + rw
53 if xe > fb.width { xe = fb.width }
54 while x < xe {
55 let o: i64 = y * fb.stride + x * 3
56 px[o]=r as u8; px[o+1]=g as u8; px[o+2]=b as u8
57 x = x + 1
58 }
59 y = y + 1
60 }
61 return 0
62}
63func draw_char3(fb: *Image, ft: *u8, c: i64, x0: i64, y0: i64, sc: i64, r: i64, g: i64, b: i64) -> i64 {
64 var cc: i64 = c
65 if cc < 0x20 { return 0 }
66 if cc > 0x7F { cc = 0x3F }
67 let idx: i64 = (cc - 0x20) * 8
68 var row: i64 = 0
69 while row < 8 {
70 let bits: i64 = ft[idx + row] & 0xff
71 var col: i64 = 0
72 while col < 8 {
73 if ((bits >> col) & 1) == 1 { fill_rect3(fb, x0 + col*sc, y0 + row*sc, sc, sc, r, g, b) }
74 col = col + 1
75 }
76 row = row + 1
77 }
78 return 0
79}
80func draw_text3(fb: *Image, ft: *u8, x0: i64, y0: i64, s: *u8, sc: i64, r: i64, g: i64, b: i64) -> i64 {
81 var x: i64 = x0
82 var i: i64 = 0
83 while s[i] != (0 as u8) {
84 let c: i64 = s[i] & 0xff
85 draw_char3(fb, ft, c, x, y0, sc, r, g, b)
86 x = x + 9 * sc
87 i = i + 1
88 }
89 return x
90}
91// int -> chars drawn (uses caller scratch, no per-frame alloc)
92func draw_num3(fb: *Image, ft: *u8, x0: i64, y0: i64, v: i64, sc: i64, r: i64, g: i64, b: i64, tmp: *u8) -> i64 {
93 var n: i64 = v
94 if n < 0 { n = 0 }
95 var k: i64 = 0
96 if n == 0 { tmp[0]=48 as u8; k=1 }
97 while n > 0 { tmp[k]=(48+n%10) as u8; n=n/10; k=k+1 }
98 var x: i64 = x0
99 var j: i64 = k - 1
100 while j >= 0 {
101 draw_char3(fb, ft, tmp[j] as i64, x, y0, sc, r, g, b)
102 x = x + 9 * sc
103 j = j - 1
104 }
105 return x
106}
107
108// the sovereign RGB frame -> Windows BGRA DIB bridge
109func rgb_to_bgra(dst: *u8, src: *u8, n: i64) -> i64 {
110 var i: i64 = 0
111 while i < n {
112 dst[i*4+0] = src[i*3+2]
113 dst[i*4+1] = src[i*3+1]
114 dst[i*4+2] = src[i*3+0]
115 dst[i*4+3] = 0 as u8
116 i = i + 1
117 }
118 return 0
119}
120
121// render one game frame: title chrome + textured band + PLAYER triangle + gold pickups + HUD +
122// win banner. All integer, all through the E1-R2 fast raster path.
123func game_frame(fb: *Image, tex: *Image, ft: *u8, t: i64, bx: i64, by: i64, tmp: *u8, gxa: *i64, gya: *i64, cmask: i64, gold: i64) -> i64 {
124 fill_rect3(fb, 0, 0, GW, GH, 12, 14, 24)
125 fill_rect3(fb, 0, 0, GW, 34, 24, 28, 44)
126 draw_text3(fb, ft, 12, 9, "NISHI GAME -- SOVEREIGN FRAMES" as *u8, 2, 140, 220, 160)
127 draw_text3(fb, ft, 12, 42, "frame" as *u8, 1, 150, 150, 160)
128 draw_num3(fb, ft, 66, 42, t, 1, 230, 200, 90, tmp)
129 draw_text3(fb, ft, 170, 42, "gold" as *u8, 1, 230, 200, 90)
130 let gx2: i64 = draw_num3(fb, ft, 215, 42, gold, 1, 250, 220, 100, tmp)
131 draw_text3(fb, ft, gx2, 42, "/5" as *u8, 1, 150, 150, 160)
132 draw_text3(fb, ft, 12, 424, "arrows / WASD move -- collect the gold -- ESC quits | E1-R2 fast raster, pure integer" as *u8, 1, 110, 115, 130)
133 // textured band (the baselined full-fragment path over a wide quad, E1-R2 fast lane)
134 txtri_render_fast(fb, tex, 0, 60, 0, 0, GW-1, 60, 1023, 0, 0, 219, 0, 1023)
135 txtri_render_fast(fb, tex, GW-1, 60, 1023, 0, GW-1, 219, 1023, 1023, 0, 219, 0, 1023)
136 // gold pickups (uncollected)
137 var gi: i64 = 0
138 while gi < 5 {
139 if ((cmask >> gi) & 1) == 0 { fill_rect3(fb, gxa[gi]-11, gya[gi]-11, 22, 22, 240, 200, 70) }
140 gi = gi + 1
141 }
142 // the PLAYER: textured triangle (same raster path)
143 txtri_render_fast(fb, tex, bx, by-60, 512, 0, bx+55, by+45, 1023, 1023, bx-55, by+45, 0, 1023)
144 if gold >= 5 { draw_text3(fb, ft, 132, 190, "YOU WIN" as *u8, 6, 140, 230, 150) }
145 return 0
146}
147
148func main() -> i64 {
149 let a: *i64 = sys_mmap(2048) as *i64
150 let tmp: *u8 = sys_mmap(64)
151
152 a[0] = 0
153 let hInst: i64 = win32(W_GetModuleHandleA, a)
154 a[0] = 0; a[1] = 32512
155 let hCur: i64 = win32(W_LoadCursorA, a)
156
157 let cls: *u8 = "nishigame" as *u8
158 let title: *u8 = "Nishi Game -- sovereign engine window (native Windows, hardware-up)" as *u8
159
160 let wc: *u8 = sys_mmap(80)
161 w32(wc, 0, 80); w32(wc, 4, 0)
162 wp (wc, 8, win32addr(W_DefWindowProcA))
163 w32(wc, 16, 0); w32(wc, 20, 0)
164 wp (wc, 24, hInst); wp (wc, 32, 0); wp (wc, 40, hCur); wp (wc, 48, 0); wp (wc, 56, 0)
165 wp (wc, 64, cls as i64); wp (wc, 72, 0)
166 a[0] = wc as i64
167 win32(W_RegisterClassExA, a)
168
169 a[0]=0; a[1]=cls as i64; a[2]=title as i64; a[3]=0x10CF0000
170 a[4]=90; a[5]=70; a[6]=GW; a[7]=GH; a[8]=0; a[9]=0; a[10]=hInst; a[11]=0
171 let hwnd: i64 = win32(W_CreateWindowExA, a)
172 if hwnd == 0 { sys_exit(1); return 1 }
173 a[0]=hwnd; a[1]=5
174 win32(W_ShowWindow, a)
175
176 // establish the window: drain show/paint/activate BEFORE the render loop (anti-teardown lesson)
177 let msg: *u8 = sys_mmap(64)
178 var pw0: i64 = 0
179 while pw0 < 50 {
180 a[0]=msg as i64; a[1]=0; a[2]=0; a[3]=0; a[4]=1
181 if win32(W_PeekMessageA, a) != 0 { win32(W_TranslateMessage, a); win32(W_DispatchMessageA, a) }
182 pw0 = pw0 + 1
183 }
184
185 // sovereign RGB render target + BGRA display DIB + BITMAPINFO
186 let fbi: *Image = nx_image_alloc(GW, GH, 3)
187 let fb: *u8 = sys_mmap(GW * GH * 4)
188 let bmi: *u8 = sys_mmap(64)
189 w32(bmi, 0, 40); w32(bmi, 4, GW); w32(bmi, 8, 0 - GH)
190 w16(bmi, 12, 1); w16(bmi, 14, 32); w32(bmi, 16, 0)
191 let rectbuf: *u8 = sys_mmap(16)
192 let ft: *u8 = font8x8_table()
193
194 // checkerboard texture (the bench scene's texture)
195 let tex: *Image = nx_image_alloc(64, 64, 1)
196 let tp: *u8 = tex.pixels
197 var yy: i64 = 0
198 while yy < 64 {
199 var xx: i64 = 0
200 while xx < 64 {
201 var val: i64 = 30
202 if ((xx / 8) + (yy / 8)) % 2 == 0 { val = 220 }
203 tp[yy*64 + xx] = val as u8
204 xx = xx + 1
205 }
206 yy = yy + 1
207 }
208
209 // E6-R3: a 3D lit cube + its own depth buffer, rendered into the SAME sovereign RGB target
210 let cube: *Cube3D = sc_cube_new(95, 460, 660)
211 let zb3: *i64 = sys_mmap(GW * GH * 8) as *i64
212
213 // gold pickup table (fixed, data-driven) + player state
214 let gxa: *i64 = sys_mmap(64) as *i64
215 let gya: *i64 = sys_mmap(64) as *i64
216 gxa[0]=120; gya[0]=300
217 gxa[1]=240; gya[1]=380
218 gxa[2]=360; gya[2]=320
219 gxa[3]=480; gya[3]=400
220 gxa[4]=560; gya[4]=280
221 var cmask: i64 = 0
222 var gold: i64 = 0
223 var running: i64 = 1
224 var t: i64 = 0
225 var bx: i64 = 100
226 var by: i64 = 340
227 while running == 1 {
228 // pump: WM_CLOSE/ESC = clean quit; WM_KEYDOWN (0x100) = player input; rest dispatched
229 var pd: i64 = 1
230 while pd == 1 {
231 a[0]=msg as i64; a[1]=0; a[2]=0; a[3]=0; a[4]=1
232 if win32(W_PeekMessageA, a) != 0 {
233 let m: i64 = rd32(msg, 8)
234 if m == 0x10 { running = 0 } else {
235 if m == 0x100 {
236 let vk: i64 = rd32(msg, 16)
237 if vk == 0x1B { running = 0 }
238 if vk == 0x25 { bx = bx - 14 }
239 if vk == 0x41 { bx = bx - 14 }
240 if vk == 0x27 { bx = bx + 14 }
241 if vk == 0x44 { bx = bx + 14 }
242 if vk == 0x26 { by = by - 14 }
243 if vk == 0x57 { by = by - 14 }
244 if vk == 0x28 { by = by + 14 }
245 if vk == 0x53 { by = by + 14 }
246 }
247 a[0]=msg as i64; win32(W_TranslateMessage, a)
248 a[0]=msg as i64; win32(W_DispatchMessageA, a)
249 }
250 } else { pd = 0 }
251 }
252 // playfield bounds
253 if bx < 70 { bx = 70 }
254 if bx > 570 { bx = 570 }
255 if by < 250 { by = 250 }
256 if by > 410 { by = 410 }
257 // pickup collision (player center vs gold center)
258 var ci: i64 = 0
259 while ci < 5 {
260 if ((cmask >> ci) & 1) == 0 {
261 var ddx: i64 = bx - gxa[ci]
262 if ddx < 0 { ddx = 0 - ddx }
263 var ddy: i64 = by - gya[ci]
264 if ddy < 0 { ddy = 0 - ddy }
265 if ddx < 40 { if ddy < 40 { cmask = cmask | (1 << ci); gold = gold + 1 } }
266 }
267 ci = ci + 1
268 }
269
270 // E6-R3: a spinning, depth-sorted, Lambert-lit 3D cube in the live sovereign window
271 fill_rect3(fbi, 0, 0, GW, GH, 12, 14, 24)
272 fill_rect3(fbi, 0, 0, GW, 40, 22, 26, 40)
273 depth_clear(zb3, GW * GH, 1000000000)
274 sc_cube_draw(cube, fbi, zb3, GW / 2, GH / 2 + 24, t * 3, 0 - 24)
275 draw_text3(fbi, ft, 12, 10, "NISHI 3D -- SOVEREIGN LIT CUBE" as *u8, 2, 150, 220, 170)
276 draw_text3(fbi, ft, 12, GH - 22, "depth-buffered - Lambert-lit - pure integer - no gcc/GL - frame" as *u8, 1, 120, 130, 150)
277 draw_num3(fbi, ft, 470, GH - 22, t, 1, 230, 200, 90, tmp)
278 rgb_to_bgra(fb, fbi.pixels, GW * GH)
279
280 // blit the client-visible region (clamped to the render size)
281 a[0]=hwnd; a[1]=rectbuf as i64; win32(W_GetClientRect, a)
282 var cw: i64 = rd32(rectbuf, 8)
283 var ch: i64 = rd32(rectbuf, 12)
284 if cw > GW { cw = GW }
285 if ch > GH { ch = GH }
286 if cw > 0 { if ch > 0 {
287 // keep the DIB header + scanline count matched to the CLIENT height (the browser's proven
288 // contract) so the frame is TOP-anchored -- a taller source with cScanLines=GH shows the
289 // BOTTOM rows and cuts the chrome strip
290 w32(bmi, 8, 0 - ch)
291 a[0]=hwnd
292 let hdc: i64 = win32(W_GetDC, a)
293 a[0]=hdc; a[1]=0; a[2]=0; a[3]=cw; a[4]=ch; a[5]=0; a[6]=0; a[7]=0; a[8]=ch; a[9]=fb as i64; a[10]=bmi as i64; a[11]=0
294 win32(W_SetDIBitsToDevice, a)
295 a[0]=hwnd; a[1]=hdc; win32(W_ReleaseDC, a)
296 } }
297
298 a[0]=hwnd
299 if win32(W_IsWindow, a) == 0 { running = 0 }
300 sys_sleep_ms(16)
301 t = t + 1
302 }
303 sys_exit(0)
304 return 0
305}