code wiki / (root) / nx_game_engine_lib.nx

nx_game_engine_lib.nx source

↩ module page · 432 lines · 14842 B

1// nx_game_engine_lib.nx -- the GENERIC Nishi dungeon-crawler ENGINE (reusable library). 2// 3// This is the "engine" half of autonomous game-gen: it is written ONCE and reused by 4// every generated game. All per-game variation (title, difficulty, enemy/loot counts, 5// damage model, and the COMBAT FSM transition table) arrives at runtime in a config 6// array -- nothing is hardcoded. A generated per-game module just fills the config + 7// FSM table and calls eng_run(cfg); the emitter (nx_game_emit) writes that tiny module 8// from a data game-spec. Built native by Nishi's own toolchain (nx_cc->nxasm_x86). 9// 10// Combat is fully DATA-DRIVEN: g_fsm_step reads the supplied transition table (the 11// STATE_MACHINE shape generalized to runtime data). The combat EVENT CONVENTION the 12// spec's table must follow (same as the emitter-authored _pe_dungenc): 13// states 0=START 1=PLAYER_TURN 2=ENEMY_TURN 3=WIN 4=FLEE 5=END 14// events 0=enter 1=attack 2=enemy_act 3=defeat 4=flee 5=resolve 15// 16// HONEST AUTHORSHIP: this engine library + the emitter are TUTOR-authored tooling 17// (Claude). The GAMES the emitter produces from specs are emitter-authored output = 18// the autonomy lever (feed specs -> games hands-off). The combat FSM tables come from 19// the team's STATE_MACHINE specs. license_tier: ORIGINAL 20// 21// state st[]: 0=px 1=py 2=hp 3=score 4=level 5=mode 6=fsm 7=enemy_hp 8=rng 9=foes 22// 10=msg 11=enemy_x 12=enemy_y | config: 13=walls 14=enemy_base 15=enemy_per_lvl 23// 16=ehp_base 17=ehp_per_lvl 18=loot 19=dmg_base 20=dmg_rand 21=title_ptr 24// 22=fsm_table_ptr 23=fsm_nstates 24=fsm_nevents | world grid 16x12 bytes at st+256 25import "nx_syscalls.nx" 26const K_MAGIC_1103515245: i64 = 1103515245 27const K_MAGIC_12345: i64 = 12345 28const K_MAGIC_32767: i64 = 32767 29const K_MAGIC_8192: i64 = 8192 30const K_MAGIC_16384: i64 = 16384 31 32func g_rng(st: *i64) -> i64 { 33 var s: i64 = st[8] 34 s = s * K_MAGIC_1103515245 + K_MAGIC_12345 35 st[8] = s 36 return (s >> 16) & K_MAGIC_32767 37} 38func g_cell_get(st: *i64, x: i64, y: i64) -> i64 { 39 if x < 0 { return 1 } 40 if y < 0 { return 1 } 41 if x >= 16 { return 1 } 42 if y >= 12 { return 1 } 43 let w: *u8 = ((st as i64) + 256) as *u8 44 return w[y * 16 + x] as i64 45} 46func g_cell_set(st: *i64, x: i64, y: i64, v: i64) -> i64 { 47 let w: *u8 = ((st as i64) + 256) as *u8 48 w[y * 16 + x] = v as u8 49 return 0 50} 51// generic data-driven FSM step (reads the supplied transition table) 52func g_fsm_step(st: *i64, s: i64, e: i64) -> i64 { 53 let ns: i64 = st[23] 54 let ne: i64 = st[24] 55 if s < 0 { return 0 - 1 } 56 if e < 0 { return 0 - 1 } 57 if s >= ns { return 0 - 1 } 58 if e >= ne { return 0 - 1 } 59 let t: *i64 = st[22] as *i64 60 return t[s * ne + e] 61} 62func g_place(st: *i64, kind: i64, n: i64) -> i64 { 63 var placed: i64 = 0 64 var tries: i64 = 0 65 while placed < n { 66 if tries > 400 { return placed } 67 tries = tries + 1 68 let rx: i64 = 1 + (g_rng(st) % 14) 69 let ry: i64 = 1 + (g_rng(st) % 10) 70 var ok: i64 = 1 71 if g_cell_get(st, rx, ry) != 0 { ok = 0 } 72 if rx < 3 { if ry < 3 { ok = 0 } } 73 if ok == 1 { g_cell_set(st, rx, ry, kind); placed = placed + 1 } 74 } 75 return placed 76} 77func g_build_level(st: *i64) -> i64 { 78 var y: i64 = 0 79 while y < 12 { 80 var x: i64 = 0 81 while x < 16 { 82 var c: i64 = 0 83 if x == 0 { c = 1 } 84 if y == 0 { c = 1 } 85 if x == 15 { c = 1 } 86 if y == 11 { c = 1 } 87 g_cell_set(st, x, y, c) 88 x = x + 1 89 } 90 y = y + 1 91 } 92 var i: i64 = 0 93 while i < st[13] { 94 let rx: i64 = 1 + (g_rng(st) % 14) 95 let ry: i64 = 1 + (g_rng(st) % 10) 96 var ok: i64 = 1 97 if rx < 3 { if ry < 3 { ok = 0 } } 98 if ok == 1 { g_cell_set(st, rx, ry, 1) } 99 i = i + 1 100 } 101 g_cell_set(st, 1, 1, 0) 102 let lvl: i64 = st[4] 103 var ne: i64 = st[14] + lvl * st[15] 104 if ne > 10 { ne = 10 } 105 st[9] = g_place(st, 2, ne) 106 g_place(st, 3, 1) 107 g_place(st, 5, st[18]) 108 st[0] = 1 109 st[1] = 1 110 st[5] = 0 111 st[6] = 0 112 return 0 113} 114func gi_init(st: *i64, seed: i64) -> i64 { 115 st[8] = seed 116 st[2] = 100 117 st[3] = 0 118 st[4] = 1 119 st[5] = 0 120 st[6] = 0 121 st[7] = 0 122 st[10] = 0 123 g_build_level(st) 124 return 0 125} 126func gi_next(st: *i64) -> i64 { 127 st[4] = st[4] + 1 128 st[3] = st[3] + 100 129 st[2] = st[2] + 25 130 if st[2] > 100 { st[2] = 100 } 131 g_build_level(st) 132 return 0 133} 134func g_try_move(st: *i64, dx: i64, dy: i64) -> i64 { 135 let nx: i64 = st[0] + dx 136 let ny: i64 = st[1] + dy 137 let c: i64 = g_cell_get(st, nx, ny) 138 if c == 1 { return 0 } 139 if c == 2 { 140 st[5] = 1 141 st[6] = g_fsm_step(st, 0, 0) 142 st[7] = st[16] + st[4] * st[17] 143 st[11] = nx 144 st[12] = ny 145 st[10] = 1 146 return 1 147 } 148 st[0] = nx 149 st[1] = ny 150 if c == 3 { st[5] = 2; st[10] = 2; return 2 } 151 if c == 5 { g_cell_set(st, nx, ny, 0); st[3] = st[3] + 25; st[10] = 3 } 152 return 0 153} 154func g_attack(st: *i64) -> i64 { 155 if st[5] != 1 { return 0 } 156 let dmg: i64 = st[19] + (g_rng(st) % st[20]) 157 st[7] = st[7] - dmg 158 if st[7] <= 0 { 159 st[6] = g_fsm_step(st, 1, 3) 160 st[6] = g_fsm_step(st, st[6], 5) 161 g_cell_set(st, st[11], st[12], 0) 162 st[3] = st[3] + 50 163 st[9] = st[9] - 1 164 st[5] = 0 165 st[6] = 0 166 st[10] = 4 167 return 1 168 } 169 st[6] = g_fsm_step(st, 1, 1) 170 let edmg: i64 = 4 + (g_rng(st) % (6 + st[4])) 171 st[2] = st[2] - edmg 172 st[6] = g_fsm_step(st, 2, 2) 173 if st[2] <= 0 { st[2] = 0; st[5] = 3; st[10] = 5 } 174 return 0 175} 176func g_flee(st: *i64) -> i64 { 177 if st[5] != 1 { return 0 } 178 st[6] = g_fsm_step(st, 1, 4) 179 st[6] = g_fsm_step(st, st[6], 5) 180 st[5] = 0 181 st[6] = 0 182 st[10] = 6 183 return 0 184} 185func gi_input(st: *i64, key: i64) -> i64 { 186 let mode: i64 = st[5] 187 if mode == 3 { 188 if key == 4 { gi_init(st, st[8] + 7) } 189 if key == 5 { gi_init(st, st[8] + 7) } 190 return 0 191 } 192 if mode == 2 { 193 if key == 4 { gi_next(st) } 194 return 0 195 } 196 if mode == 1 { 197 if key == 4 { g_attack(st) } 198 if key == 0 { g_flee(st) } 199 if key == 1 { g_flee(st) } 200 if key == 2 { g_flee(st) } 201 if key == 3 { g_flee(st) } 202 return 0 203 } 204 if key == 0 { g_try_move(st, 0, 0 - 1) } 205 if key == 1 { g_try_move(st, 0, 1) } 206 if key == 2 { g_try_move(st, 0 - 1, 0) } 207 if key == 3 { g_try_move(st, 1, 0) } 208 return 0 209} 210func bputc(buf: *u8, off: *i64, c: i64) -> i64 { buf[off[0]] = c as u8; off[0] = off[0] + 1; return 0 } 211func bput(buf: *u8, off: *i64, s: *u8) -> i64 { 212 var i: i64 = 0 213 while s[i] != (0 as u8) { buf[off[0] + i] = s[i]; i = i + 1 } 214 off[0] = off[0] + i 215 return 0 216} 217func bputn(buf: *u8, off: *i64, v: i64) -> i64 { 218 var m: i64 = v 219 if m < 0 { m = 0 } 220 if m == 0 { bputc(buf, off, 48); return 0 } 221 let tmp: *u8 = sys_mmap(32) 222 var k: i64 = 0 223 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 224 var j: i64 = 0 225 while j < k { bputc(buf, off, tmp[k - 1 - j] as i64); j = j + 1 } 226 return 0 227} 228func bput_bg(buf: *u8, off: *i64, r: i64, g: i64, b: i64) -> i64 { 229 bputc(buf, off, 27); bput(buf, off, "[48;2;" as *u8) 230 bputn(buf, off, r); bputc(buf, off, 59) 231 bputn(buf, off, g); bputc(buf, off, 59) 232 bputn(buf, off, b); bputc(buf, off, 109) 233 return 0 234} 235func bput_fg(buf: *u8, off: *i64, r: i64, g: i64, b: i64) -> i64 { 236 bputc(buf, off, 27); bput(buf, off, "[38;2;" as *u8) 237 bputn(buf, off, r); bputc(buf, off, 59) 238 bputn(buf, off, g); bputc(buf, off, 59) 239 bputn(buf, off, b); bputc(buf, off, 109) 240 return 0 241} 242func bput_reset(buf: *u8, off: *i64) -> i64 { bputc(buf, off, 27); bput(buf, off, "[0m" as *u8); return 0 } 243func bput_clear(buf: *u8, off: *i64) -> i64 { 244 bputc(buf, off, 27); bput(buf, off, "[2J" as *u8); bputc(buf, off, 27); bput(buf, off, "[H" as *u8); return 0 245} 246func draw_cell(buf: *u8, off: *i64, d: i64) -> i64 { 247 if d == 1 { bput_bg(buf, off, 70, 80, 110); bput(buf, off, " " as *u8); bput_reset(buf, off); return 0 } 248 bput_bg(buf, off, 24, 22, 30) 249 if d == 0 { bput(buf, off, " " as *u8) } 250 if d == 2 { bput_fg(buf, off, 90, 230, 120); bput(buf, off, "()" as *u8) } 251 if d == 3 { bput_fg(buf, off, 220, 60, 60); bput(buf, off, "gg" as *u8) } 252 if d == 4 { bput_fg(buf, off, 240, 220, 72); bput(buf, off, ">>" as *u8) } 253 if d == 5 { bput_fg(buf, off, 248, 176, 44); bput(buf, off, "**" as *u8) } 254 bput_reset(buf, off) 255 return 0 256} 257func render_hud(buf: *u8, off: *i64, st: *i64) -> i64 { 258 bput(buf, off, " " as *u8); bput(buf, off, st[21] as *u8) 259 bput(buf, off, " HP " as *u8); bputn(buf, off, st[2]) 260 bput(buf, off, "/100 Score " as *u8); bputn(buf, off, st[3]) 261 bput(buf, off, " Depth " as *u8); bputn(buf, off, st[4]) 262 bput(buf, off, " Foes " as *u8); bputn(buf, off, st[9]) 263 if (st[25] & 1) != 0 { bput(buf, off, " Best " as *u8); bputn(buf, off, st[27]) } 264 bputc(buf, off, 10); bputc(buf, off, 10) 265 return 0 266} 267func render_grid(buf: *u8, off: *i64, st: *i64) -> i64 { 268 let px: i64 = st[0] 269 let py: i64 = st[1] 270 var y: i64 = 0 271 while y < 12 { 272 var x: i64 = 0 273 while x < 16 { 274 var d: i64 = 0 275 let cell: i64 = g_cell_get(st, x, y) 276 if cell == 1 { d = 1 } 277 if cell == 2 { d = 3 } 278 if cell == 3 { d = 4 } 279 if cell == 5 { d = 5 } 280 if x == px { if y == py { d = 2 } } 281 draw_cell(buf, off, d) 282 x = x + 1 283 } 284 bput_reset(buf, off); bputc(buf, off, 10) 285 y = y + 1 286 } 287 return 0 288} 289func render_bar(buf: *u8, off: *i64, val: i64, max: i64, r: i64, g: i64) -> i64 { 290 var n: i64 = val * 20 / max 291 if n < 0 { n = 0 } 292 if n > 20 { n = 20 } 293 bputc(buf, off, 91); bput_fg(buf, off, r, g, 60) 294 var i: i64 = 0 295 while i < 20 { if i < n { bputc(buf, off, 35) } else { bputc(buf, off, 45) } i = i + 1 } 296 bput_reset(buf, off) 297 bputc(buf, off, 93); bputc(buf, off, 32); bputn(buf, off, val); bputc(buf, off, 10) 298 return 0 299} 300func render_combat(buf: *u8, off: *i64, st: *i64) -> i64 { 301 bputc(buf, off, 10) 302 bput(buf, off, " -- COMBAT (data-driven FSM) --" as *u8); bputc(buf, off, 10); bputc(buf, off, 10) 303 bput(buf, off, " YOU " as *u8); render_bar(buf, off, st[2], 100, 90, 230) 304 var emax: i64 = st[16] + st[4] * st[17] 305 if emax < 1 { emax = 1 } 306 var ehp: i64 = st[7] 307 if ehp < 0 { ehp = 0 } 308 bput(buf, off, " FOE " as *u8); render_bar(buf, off, ehp, emax, 220, 60) 309 bputc(buf, off, 10) 310 return 0 311} 312func render_foot(buf: *u8, off: *i64, st: *i64) -> i64 { 313 let mode: i64 = st[5] 314 bputc(buf, off, 10) 315 if mode == 0 { bput(buf, off, " move: w a s d reach >> to descend quit: q" as *u8) } 316 if mode == 1 { bput(buf, off, " space = attack w/a/s/d = flee quit: q" as *u8) } 317 if mode == 2 { bput(buf, off, " LEVEL CLEAR! space = descend deeper quit: q" as *u8) } 318 if mode == 3 { bput(buf, off, " YOU DIED. r = new run quit: q" as *u8) } 319 bputc(buf, off, 10) 320 return 0 321} 322func eng_render(buf: *u8, st: *i64) -> i64 { 323 let off: *i64 = sys_mmap(16) as *i64 324 off[0] = 0 325 bput_clear(buf, off) 326 render_hud(buf, off, st) 327 if st[5] == 1 { render_combat(buf, off, st) } else { render_grid(buf, off, st) } 328 render_foot(buf, off, st) 329 sys_write(1, buf, off[0]) 330 return 0 331} 332func map_key(c: i64) -> i64 { 333 if c == 119 { return 0 } 334 if c == 115 { return 1 } 335 if c == 97 { return 2 } 336 if c == 100 { return 3 } 337 if c == 32 { return 4 } 338 if c == 102 { return 4 } 339 if c == 13 { return 4 } 340 if c == 10 { return 4 } 341 if c == 114 { return 5 } 342 return 9 343} 344func term_raw_on(orig: *u8, raw: *u8) -> i64 { 345 let g: i64 = sys_ioctl(0, 0x5401, orig as i64) 346 if g != 0 { return 0 } 347 var i: i64 = 0 348 while i < 60 { raw[i] = orig[i]; i = i + 1 } 349 var lf: i64 = raw[12] as i64 350 lf = lf & (255 - 2) 351 lf = lf & (255 - 8) 352 raw[12] = lf as u8 353 raw[17 + 6] = 1 as u8 354 raw[17 + 5] = 0 as u8 355 sys_ioctl(0, 0x5402, raw as i64) 356 return 1 357} 358func term_raw_off(orig: *u8) -> i64 { sys_ioctl(0, 0x5402, orig as i64); return 0 } 359 360// ---- COMPOSED PART: persistence (needs save). cfg[25]=needs-mask (bit0=save), 361// cfg[26]=save-file path, st[27]=best score. Demonstrates the parts-composition 362// doctrine: a game declares `needs save` in its spec -> the emitter wires this part -> 363// the engine persists across runs. Same wiring path that networking/audio/accounts use. 364func eng_load_best(st: *i64) -> i64 { 365 if (st[25] & 1) == 0 { return 0 } 366 let lenp: *i64 = sys_mmap(16) as *i64 367 let buf: *u8 = sys_read_file(st[26] as *u8, lenp) 368 if buf == (0 as i64) as *u8 { return 0 } 369 let n: i64 = lenp[0] 370 var v: i64 = 0 371 var i: i64 = 0 372 var go: i64 = 1 373 while go == 1 { 374 if i >= n { go = 0 } 375 if go == 1 { 376 let c: i64 = buf[i] as i64 377 if c < 48 { go = 0 } 378 if c > 57 { go = 0 } 379 if go == 1 { v = v * 10 + (c - 48); i = i + 1 } 380 } 381 } 382 st[27] = v 383 return 0 384} 385func eng_save_best(st: *i64) -> i64 { 386 if (st[25] & 1) == 0 { return 0 } 387 let fd: i64 = sys_openat_wr(st[26] as *u8, 0x1a4) 388 if fd < 0 { return 0 } 389 let off: *i64 = sys_mmap(16) as *i64 390 off[0] = 0 391 let b: *u8 = sys_mmap(32) 392 bputn(b, off, st[27]) 393 sys_write(fd, b, off[0]) 394 sys_close(fd) 395 return 0 396} 397 398// the engine entry point: caller fills cfg[13..24] params + cfg[25..26] composed-part 399// config (needs-mask + save path) + the fsm table, then calls this. 400func eng_run(cfg: *i64) -> i64 { 401 let st: *i64 = sys_mmap(K_MAGIC_8192) as *i64 402 var i: i64 = 13 403 while i < 27 { st[i] = cfg[i]; i = i + 1 } 404 let orig: *u8 = sys_mmap(128) 405 let raw: *u8 = sys_mmap(128) 406 let is_tty: i64 = term_raw_on(orig, raw) 407 gi_init(st, sys_now_ms()) 408 st[27] = 0 409 eng_load_best(st) 410 let buf: *u8 = sys_mmap(K_MAGIC_16384) 411 let ib: *u8 = sys_mmap(8) 412 var running: i64 = 1 413 while running == 1 { 414 eng_render(buf, st) 415 let n: i64 = sys_read(0, ib, 1) 416 if n <= 0 { running = 0 } 417 if n > 0 { 418 let c: i64 = ib[0] as i64 419 if c == 113 { running = 0 } 420 if c == 81 { running = 0 } 421 if c != 113 { if c != 81 { gi_input(st, map_key(c)) } } 422 } 423 if st[3] > st[27] { st[27] = st[3]; eng_save_best(st) } 424 } 425 if is_tty == 1 { term_raw_off(orig) } 426 let off: *i64 = sys_mmap(16) as *i64 427 off[0] = 0 428 bput_clear(buf, off) 429 bput(buf, off, "thanks for playing -- " as *u8); bput(buf, off, st[21] as *u8); bputc(buf, off, 10) 430 sys_write(1, buf, off[0]) 431 return st[3] 432}