code wiki / _hdl_build / nishi_game.nx

nishi_game.nx source

↩ module page · 358 lines · 14794 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 148// win32 file write (headless dump): CreateFileA(slot20) + WriteFile(slot4) + CloseHandle(slot23) -- all in the 149// GUI PE IAT. Lets a Nishi Windows surface dump its OWN rendered framebuffer to a file = headless-safe SEE 150// (no window, no screen capture -- sidesteps the black-desktop wall). Returns bytes written, -1 on open fail. 151func win_writefile(path: *u8, buf: *u8, n: i64) -> i64 { 152 let a: *i64 = sys_mmap(2048) as *i64 153 a[0]=path as i64; a[1]=0x40000000; a[2]=0; a[3]=0; a[4]=2; a[5]=0x80; a[6]=0 154 let h: i64 = win32(20, a) 155 if h == 0 { return 0 - 1 } 156 if h == (0 - 1) { return 0 - 1 } 157 let wr: *i64 = sys_mmap(16) as *i64 158 a[0]=h; a[1]=buf as i64; a[2]=n; a[3]=wr as i64; a[4]=0 159 win32(4, a) 160 a[0]=h; win32(23, a) 161 return wr[0] 162} 163 164func win_file_exists(path: *u8) -> i64 { 165 let a: *i64 = sys_mmap(2048) as *i64 166 a[0]=path as i64; a[1]=0x80000000; a[2]=1; a[3]=0; a[4]=3; a[5]=0x80; a[6]=0 167 let h: i64 = win32(20, a) 168 if h == (0 - 1) { return 0 } 169 if h == 0 { return 0 } 170 a[0]=h; win32(23, a) 171 return 1 172} 173 174func main() -> i64 { 175 // HEADLESS DUMP: nxshot.flag in CWD -> render 1 frame -> write PPM -> exit. The GUI keystone passes no argv, 176 // so a sentinel file triggers it. Windows-native, sovereign, screen-capture-free SEE of our own surface. 177 let shot: i64 = win_file_exists("nxshot.flag\x00" as *u8) 178 if shot == 1 { 179 if shot == 1 { 180 let fbs: *Image = nx_image_alloc(GW, GH, 3) 181 let cubes: *Cube3D = sc_cube_new(95, 460, 660) 182 let zbs: *i64 = sys_mmap(GW * GH * 8) as *i64 183 fill_rect3(fbs, 0, 0, GW, GH, 12, 14, 24) 184 fill_rect3(fbs, 0, 0, GW, 40, 22, 26, 40) 185 depth_clear(zbs, GW * GH, 1000000000) 186 sc_cube_draw(cubes, fbs, zbs, GW / 2, GH / 2 + 24, 40, 0 - 24) 187 let hdr: *u8 = "P6\n640 480\n255\n" as *u8 188 var hn: i64 = 0 189 while hdr[hn] != (0 as u8) { hn = hn + 1 } 190 let npix3: i64 = GW * GH * 3 191 let ob: *u8 = sys_mmap(hn + npix3 + 16) 192 var i: i64 = 0 193 while i < hn { ob[i] = hdr[i]; i = i + 1 } 194 let px: *u8 = fbs.pixels 195 var j: i64 = 0 196 while j < npix3 { ob[hn + j] = px[j]; j = j + 1 } 197 win_writefile("nishi_game_shot.ppm\x00" as *u8, ob, hn + npix3) 198 sys_exit(0) 199 return 0 200 } 201 } 202 let a: *i64 = sys_mmap(2048) as *i64 203 let tmp: *u8 = sys_mmap(64) 204 205 a[0] = 0 206 let hInst: i64 = win32(W_GetModuleHandleA, a) 207 a[0] = 0; a[1] = 32512 208 let hCur: i64 = win32(W_LoadCursorA, a) 209 210 let cls: *u8 = "nishigame" as *u8 211 let title: *u8 = "Nishi Game -- sovereign engine window (native Windows, hardware-up)" as *u8 212 213 let wc: *u8 = sys_mmap(80) 214 w32(wc, 0, 80); w32(wc, 4, 0) 215 wp (wc, 8, win32addr(W_DefWindowProcA)) 216 w32(wc, 16, 0); w32(wc, 20, 0) 217 wp (wc, 24, hInst); wp (wc, 32, 0); wp (wc, 40, hCur); wp (wc, 48, 0); wp (wc, 56, 0) 218 wp (wc, 64, cls as i64); wp (wc, 72, 0) 219 a[0] = wc as i64 220 win32(W_RegisterClassExA, a) 221 222 a[0]=0; a[1]=cls as i64; a[2]=title as i64; a[3]=0x10CF0000 223 a[4]=90; a[5]=70; a[6]=GW; a[7]=GH; a[8]=0; a[9]=0; a[10]=hInst; a[11]=0 224 let hwnd: i64 = win32(W_CreateWindowExA, a) 225 if hwnd == 0 { sys_exit(1); return 1 } 226 a[0]=hwnd; a[1]=5 227 win32(W_ShowWindow, a) 228 229 // establish the window: drain show/paint/activate BEFORE the render loop (anti-teardown lesson) 230 let msg: *u8 = sys_mmap(64) 231 var pw0: i64 = 0 232 while pw0 < 50 { 233 a[0]=msg as i64; a[1]=0; a[2]=0; a[3]=0; a[4]=1 234 if win32(W_PeekMessageA, a) != 0 { win32(W_TranslateMessage, a); win32(W_DispatchMessageA, a) } 235 pw0 = pw0 + 1 236 } 237 238 // sovereign RGB render target + BGRA display DIB + BITMAPINFO 239 let fbi: *Image = nx_image_alloc(GW, GH, 3) 240 let fb: *u8 = sys_mmap(GW * GH * 4) 241 let bmi: *u8 = sys_mmap(64) 242 w32(bmi, 0, 40); w32(bmi, 4, GW); w32(bmi, 8, 0 - GH) 243 w16(bmi, 12, 1); w16(bmi, 14, 32); w32(bmi, 16, 0) 244 let rectbuf: *u8 = sys_mmap(16) 245 let ft: *u8 = font8x8_table() 246 247 // checkerboard texture (the bench scene's texture) 248 let tex: *Image = nx_image_alloc(64, 64, 1) 249 let tp: *u8 = tex.pixels 250 var yy: i64 = 0 251 while yy < 64 { 252 var xx: i64 = 0 253 while xx < 64 { 254 var val: i64 = 30 255 if ((xx / 8) + (yy / 8)) % 2 == 0 { val = 220 } 256 tp[yy*64 + xx] = val as u8 257 xx = xx + 1 258 } 259 yy = yy + 1 260 } 261 262 // E6-R3: a 3D lit cube + its own depth buffer, rendered into the SAME sovereign RGB target 263 let cube: *Cube3D = sc_cube_new(95, 460, 660) 264 let zb3: *i64 = sys_mmap(GW * GH * 8) as *i64 265 266 // gold pickup table (fixed, data-driven) + player state 267 let gxa: *i64 = sys_mmap(64) as *i64 268 let gya: *i64 = sys_mmap(64) as *i64 269 gxa[0]=120; gya[0]=300 270 gxa[1]=240; gya[1]=380 271 gxa[2]=360; gya[2]=320 272 gxa[3]=480; gya[3]=400 273 gxa[4]=560; gya[4]=280 274 var cmask: i64 = 0 275 var gold: i64 = 0 276 var running: i64 = 1 277 var t: i64 = 0 278 var bx: i64 = 100 279 var by: i64 = 340 280 while running == 1 { 281 // pump: WM_CLOSE/ESC = clean quit; WM_KEYDOWN (0x100) = player input; rest dispatched 282 var pd: i64 = 1 283 while pd == 1 { 284 a[0]=msg as i64; a[1]=0; a[2]=0; a[3]=0; a[4]=1 285 if win32(W_PeekMessageA, a) != 0 { 286 let m: i64 = rd32(msg, 8) 287 if m == 0x10 { running = 0 } else { 288 if m == 0x100 { 289 let vk: i64 = rd32(msg, 16) 290 if vk == 0x1B { running = 0 } 291 if vk == 0x25 { bx = bx - 14 } 292 if vk == 0x41 { bx = bx - 14 } 293 if vk == 0x27 { bx = bx + 14 } 294 if vk == 0x44 { bx = bx + 14 } 295 if vk == 0x26 { by = by - 14 } 296 if vk == 0x57 { by = by - 14 } 297 if vk == 0x28 { by = by + 14 } 298 if vk == 0x53 { by = by + 14 } 299 } 300 a[0]=msg as i64; win32(W_TranslateMessage, a) 301 a[0]=msg as i64; win32(W_DispatchMessageA, a) 302 } 303 } else { pd = 0 } 304 } 305 // playfield bounds 306 if bx < 70 { bx = 70 } 307 if bx > 570 { bx = 570 } 308 if by < 250 { by = 250 } 309 if by > 410 { by = 410 } 310 // pickup collision (player center vs gold center) 311 var ci: i64 = 0 312 while ci < 5 { 313 if ((cmask >> ci) & 1) == 0 { 314 var ddx: i64 = bx - gxa[ci] 315 if ddx < 0 { ddx = 0 - ddx } 316 var ddy: i64 = by - gya[ci] 317 if ddy < 0 { ddy = 0 - ddy } 318 if ddx < 40 { if ddy < 40 { cmask = cmask | (1 << ci); gold = gold + 1 } } 319 } 320 ci = ci + 1 321 } 322 323 // E6-R3: a spinning, depth-sorted, Lambert-lit 3D cube in the live sovereign window 324 fill_rect3(fbi, 0, 0, GW, GH, 12, 14, 24) 325 fill_rect3(fbi, 0, 0, GW, 40, 22, 26, 40) 326 depth_clear(zb3, GW * GH, 1000000000) 327 sc_cube_draw(cube, fbi, zb3, GW / 2, GH / 2 + 24, t * 3, 0 - 24) 328 draw_text3(fbi, ft, 12, 10, "NISHI 3D -- SOVEREIGN LIT CUBE" as *u8, 2, 150, 220, 170) 329 draw_text3(fbi, ft, 12, GH - 22, "depth-buffered - Lambert-lit - pure integer - no gcc/GL - frame" as *u8, 1, 120, 130, 150) 330 draw_num3(fbi, ft, 470, GH - 22, t, 1, 230, 200, 90, tmp) 331 rgb_to_bgra(fb, fbi.pixels, GW * GH) 332 333 // blit the client-visible region (clamped to the render size) 334 a[0]=hwnd; a[1]=rectbuf as i64; win32(W_GetClientRect, a) 335 var cw: i64 = rd32(rectbuf, 8) 336 var ch: i64 = rd32(rectbuf, 12) 337 if cw > GW { cw = GW } 338 if ch > GH { ch = GH } 339 if cw > 0 { if ch > 0 { 340 // keep the DIB header + scanline count matched to the CLIENT height (the browser's proven 341 // contract) so the frame is TOP-anchored -- a taller source with cScanLines=GH shows the 342 // BOTTOM rows and cuts the chrome strip 343 w32(bmi, 8, 0 - ch) 344 a[0]=hwnd 345 let hdc: i64 = win32(W_GetDC, a) 346 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 347 win32(W_SetDIBitsToDevice, a) 348 a[0]=hwnd; a[1]=hdc; win32(W_ReleaseDC, a) 349 } } 350 351 a[0]=hwnd 352 if win32(W_IsWindow, a) == 0 { running = 0 } 353 sys_sleep_ms(16) 354 t = t + 1 355 } 356 sys_exit(0) 357 return 0 358}