nx_dungeon_native.nx source
↩ module page · 442 lines · 13997 B
1// nx_dungeon_native.nx -- PLAYABLE native terminal dungeon-crawler ALPHA.
2//
3// Built by Nishi's OWN toolchain to a NATIVE binary (nx_cc -> nxasm_x86, no gcc, no
4// WebAssembly, no browser, no borrowed VM). Runs natively on the x86 host; the only
5// OS surface used is the Nishi syscall layer (write/read/ioctl/mmap). The interactive
6// surface is the TERMINAL (ANSI truecolor render to stdout + single-key input from
7// stdin via termios raw mode) -- the most portable display/input, so the SAME engine
8// retargets a framebuffer/UART driver on the operator's own hardware later ("hardware
9// up = prep to run on mine").
10//
11// HONEST AUTHORSHIP (operator law: don't pass tutor work off as team work):
12// - The COMBAT state machine g_dungenc_step is the TEAM's emitter-authored FSM: the
13// exact transition table nx_auto_builder emitted into _pe_dungenc.nx from the
14// data-only spec build_dungenc.spec. The game runs the Nishi-authored game system.
15// - World gen, terminal render, input loop = TUTOR-authored (Claude) game code. NOT
16// emitter output. The named rung to make the WHOLE game emitter-authored from a
17// spec is X-GAME-GEN-001. This alpha is the playable proof, not that claim.
18//
19// LAWS honored: <=6 args/func, flat ifs, no &&/||, integer-only, additive, <21 same-fn
20// call sites per function (nxasm rc=6 landmine).
21// license_tier: ORIGINAL (combat FSM lineage: _pe_dungenc, emitter-authored)
22import "nx_syscalls.nx"
23const K_MAGIC_1103515245: i64 = 1103515245
24const K_MAGIC_12345: i64 = 12345
25const K_MAGIC_32767: i64 = 32767
26const K_MAGIC_4096: i64 = 4096
27const K_MAGIC_16384: i64 = 16384
28
29// state slots (st[]): 0=px 1=py 2=hp 3=score 4=level 5=mode 6=fsm 7=enemy_hp
30// 8=rng 9=foes 10=msg 11=enemy_x 12=enemy_y
31// mode: 0=explore 1=combat 2=level_clear 3=dead. world grid (16x12 bytes) at st+256.
32
33func g_rng(st: *i64) -> i64 {
34 var s: i64 = st[8]
35 s = s * K_MAGIC_1103515245 + K_MAGIC_12345
36 st[8] = s
37 return (s >> 16) & K_MAGIC_32767
38}
39
40func g_cell_get(st: *i64, x: i64, y: i64) -> i64 {
41 if x < 0 { return 1 }
42 if y < 0 { return 1 }
43 if x >= 16 { return 1 }
44 if y >= 12 { return 1 }
45 let w: *u8 = ((st as i64) + 256) as *u8
46 return w[y * 16 + x] as i64
47}
48
49func g_cell_set(st: *i64, x: i64, y: i64, v: i64) -> i64 {
50 let w: *u8 = ((st as i64) + 256) as *u8
51 w[y * 16 + x] = v as u8
52 return 0
53}
54
55func g_place(st: *i64, kind: i64, n: i64) -> i64 {
56 var placed: i64 = 0
57 var tries: i64 = 0
58 while placed < n {
59 if tries > 300 { return placed }
60 tries = tries + 1
61 let rx: i64 = 1 + (g_rng(st) % 14)
62 let ry: i64 = 1 + (g_rng(st) % 10)
63 var ok: i64 = 1
64 if g_cell_get(st, rx, ry) != 0 { ok = 0 }
65 if rx < 3 { if ry < 3 { ok = 0 } }
66 if ok == 1 {
67 g_cell_set(st, rx, ry, kind)
68 placed = placed + 1
69 }
70 }
71 return placed
72}
73
74func g_build_level(st: *i64) -> i64 {
75 var y: i64 = 0
76 while y < 12 {
77 var x: i64 = 0
78 while x < 16 {
79 var c: i64 = 0
80 if x == 0 { c = 1 }
81 if y == 0 { c = 1 }
82 if x == 15 { c = 1 }
83 if y == 11 { c = 1 }
84 g_cell_set(st, x, y, c)
85 x = x + 1
86 }
87 y = y + 1
88 }
89 var i: i64 = 0
90 while i < 20 {
91 let rx: i64 = 1 + (g_rng(st) % 14)
92 let ry: i64 = 1 + (g_rng(st) % 10)
93 var ok: i64 = 1
94 if rx < 3 { if ry < 3 { ok = 0 } }
95 if ok == 1 { g_cell_set(st, rx, ry, 1) }
96 i = i + 1
97 }
98 g_cell_set(st, 1, 1, 0)
99 let lvl: i64 = st[4]
100 var ne: i64 = 3 + lvl
101 if ne > 10 { ne = 10 }
102 st[9] = g_place(st, 2, ne)
103 g_place(st, 3, 1)
104 g_place(st, 5, 2)
105 st[0] = 1
106 st[1] = 1
107 st[5] = 0
108 st[6] = 0
109 return 0
110}
111
112// the TEAM's emitter-authored combat FSM (exact _pe_dungenc transition table)
113// states 0=ENC_START 1=PLAYER_TURN 2=ENEMY_TURN 3=PLAYER_WIN 4=FLEE 5=ENC_END
114// events 0=enter 1=attack 2=enemy_act 3=defeat 4=flee 5=resolve
115func g_dungenc_step(s: i64, e: i64) -> i64 {
116 if s == 0 { if e == 0 { return 1 } }
117 if s == 1 { if e == 1 { return 2 } }
118 if s == 1 { if e == 3 { return 3 } }
119 if s == 1 { if e == 4 { return 4 } }
120 if s == 2 { if e == 2 { return 1 } }
121 if s == 3 { if e == 5 { return 5 } }
122 if s == 4 { if e == 5 { return 5 } }
123 return 0 - 1
124}
125
126func gi_init(st: *i64, seed: i64) -> i64 {
127 st[8] = seed
128 st[2] = 100
129 st[3] = 0
130 st[4] = 1
131 st[5] = 0
132 st[6] = 0
133 st[7] = 0
134 st[10] = 0
135 g_build_level(st)
136 return 0
137}
138
139func gi_next(st: *i64) -> i64 {
140 st[4] = st[4] + 1
141 st[3] = st[3] + 100
142 st[2] = st[2] + 25
143 if st[2] > 100 { st[2] = 100 }
144 g_build_level(st)
145 return 0
146}
147
148func g_try_move(st: *i64, dx: i64, dy: i64) -> i64 {
149 let nx: i64 = st[0] + dx
150 let ny: i64 = st[1] + dy
151 let c: i64 = g_cell_get(st, nx, ny)
152 if c == 1 { return 0 }
153 if c == 2 {
154 st[5] = 1
155 st[6] = g_dungenc_step(0, 0)
156 st[7] = 20 + st[4] * 5
157 st[11] = nx
158 st[12] = ny
159 st[10] = 1
160 return 1
161 }
162 st[0] = nx
163 st[1] = ny
164 if c == 3 { st[5] = 2; st[10] = 2; return 2 }
165 if c == 5 { g_cell_set(st, nx, ny, 0); st[3] = st[3] + 25; st[10] = 3 }
166 return 0
167}
168
169func g_attack(st: *i64) -> i64 {
170 if st[5] != 1 { return 0 }
171 let dmg: i64 = 8 + (g_rng(st) % 8)
172 st[7] = st[7] - dmg
173 if st[7] <= 0 {
174 st[6] = g_dungenc_step(1, 3)
175 st[6] = g_dungenc_step(st[6], 5)
176 g_cell_set(st, st[11], st[12], 0)
177 st[3] = st[3] + 50
178 st[9] = st[9] - 1
179 st[5] = 0
180 st[6] = 0
181 st[10] = 4
182 return 1
183 }
184 st[6] = g_dungenc_step(1, 1)
185 let edmg: i64 = 4 + (g_rng(st) % (6 + st[4]))
186 st[2] = st[2] - edmg
187 st[6] = g_dungenc_step(2, 2)
188 if st[2] <= 0 {
189 st[2] = 0
190 st[5] = 3
191 st[10] = 5
192 }
193 return 0
194}
195
196func g_flee(st: *i64) -> i64 {
197 if st[5] != 1 { return 0 }
198 st[6] = g_dungenc_step(1, 4)
199 st[6] = g_dungenc_step(st[6], 5)
200 st[5] = 0
201 st[6] = 0
202 st[10] = 6
203 return 0
204}
205
206// key: 0=up 1=down 2=left 3=right 4=action 5=restart 9=noop
207func gi_input(st: *i64, key: i64) -> i64 {
208 let mode: i64 = st[5]
209 if mode == 3 {
210 if key == 4 { gi_init(st, st[8] + 7) }
211 if key == 5 { gi_init(st, st[8] + 7) }
212 return 0
213 }
214 if mode == 2 {
215 if key == 4 { gi_next(st) }
216 return 0
217 }
218 if mode == 1 {
219 if key == 4 { g_attack(st) }
220 if key == 0 { g_flee(st) }
221 if key == 1 { g_flee(st) }
222 if key == 2 { g_flee(st) }
223 if key == 3 { g_flee(st) }
224 return 0
225 }
226 if key == 0 { g_try_move(st, 0, 0 - 1) }
227 if key == 1 { g_try_move(st, 0, 1) }
228 if key == 2 { g_try_move(st, 0 - 1, 0) }
229 if key == 3 { g_try_move(st, 1, 0) }
230 return 0
231}
232
233// ---- terminal frame buffer append helpers ----
234func bputc(buf: *u8, off: *i64, c: i64) -> i64 {
235 buf[off[0]] = c as u8
236 off[0] = off[0] + 1
237 return 0
238}
239func bput(buf: *u8, off: *i64, s: *u8) -> i64 {
240 var i: i64 = 0
241 while s[i] != (0 as u8) {
242 buf[off[0] + i] = s[i]
243 i = i + 1
244 }
245 off[0] = off[0] + i
246 return 0
247}
248func bputn(buf: *u8, off: *i64, v: i64) -> i64 {
249 var m: i64 = v
250 if m < 0 { m = 0 }
251 if m == 0 { bputc(buf, off, 48); return 0 }
252 let tmp: *u8 = sys_mmap(32)
253 var k: i64 = 0
254 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
255 var j: i64 = 0
256 while j < k { bputc(buf, off, tmp[k - 1 - j] as i64); j = j + 1 }
257 return 0
258}
259func bput_bg(buf: *u8, off: *i64, r: i64, g: i64, b: i64) -> i64 {
260 bputc(buf, off, 27)
261 bput(buf, off, "[48;2;" as *u8)
262 bputn(buf, off, r); bputc(buf, off, 59)
263 bputn(buf, off, g); bputc(buf, off, 59)
264 bputn(buf, off, b); bputc(buf, off, 109)
265 return 0
266}
267func bput_fg(buf: *u8, off: *i64, r: i64, g: i64, b: i64) -> i64 {
268 bputc(buf, off, 27)
269 bput(buf, off, "[38;2;" as *u8)
270 bputn(buf, off, r); bputc(buf, off, 59)
271 bputn(buf, off, g); bputc(buf, off, 59)
272 bputn(buf, off, b); bputc(buf, off, 109)
273 return 0
274}
275func bput_reset(buf: *u8, off: *i64) -> i64 {
276 bputc(buf, off, 27); bput(buf, off, "[0m" as *u8); return 0
277}
278func bput_clear(buf: *u8, off: *i64) -> i64 {
279 bputc(buf, off, 27); bput(buf, off, "[2J" as *u8)
280 bputc(buf, off, 27); bput(buf, off, "[H" as *u8)
281 return 0
282}
283
284// display code: 0 floor 1 wall 2 player 3 enemy 4 exit 5 loot
285func draw_cell(buf: *u8, off: *i64, d: i64) -> i64 {
286 if d == 1 { bput_bg(buf, off, 70, 80, 110); bput(buf, off, " " as *u8); bput_reset(buf, off); return 0 }
287 bput_bg(buf, off, 24, 22, 30)
288 if d == 0 { bput(buf, off, " " as *u8) }
289 if d == 2 { bput_fg(buf, off, 90, 230, 120); bput(buf, off, "()" as *u8) }
290 if d == 3 { bput_fg(buf, off, 220, 60, 60); bput(buf, off, "gg" as *u8) }
291 if d == 4 { bput_fg(buf, off, 240, 220, 72); bput(buf, off, ">>" as *u8) }
292 if d == 5 { bput_fg(buf, off, 248, 176, 44); bput(buf, off, "**" as *u8) }
293 bput_reset(buf, off)
294 return 0
295}
296
297func render_hud(buf: *u8, off: *i64, st: *i64) -> i64 {
298 bput(buf, off, " NISHI DUNGEON HP " as *u8); bputn(buf, off, st[2])
299 bput(buf, off, "/100 Score " as *u8); bputn(buf, off, st[3])
300 bput(buf, off, " Depth " as *u8); bputn(buf, off, st[4])
301 bput(buf, off, " Foes " as *u8); bputn(buf, off, st[9])
302 bputc(buf, off, 10); bputc(buf, off, 10)
303 return 0
304}
305
306func render_grid(buf: *u8, off: *i64, st: *i64) -> i64 {
307 let px: i64 = st[0]
308 let py: i64 = st[1]
309 var y: i64 = 0
310 while y < 12 {
311 var x: i64 = 0
312 while x < 16 {
313 var d: i64 = 0
314 let cell: i64 = g_cell_get(st, x, y)
315 if cell == 1 { d = 1 }
316 if cell == 2 { d = 3 }
317 if cell == 3 { d = 4 }
318 if cell == 5 { d = 5 }
319 if x == px { if y == py { d = 2 } }
320 draw_cell(buf, off, d)
321 x = x + 1
322 }
323 bput_reset(buf, off)
324 bputc(buf, off, 10)
325 y = y + 1
326 }
327 return 0
328}
329
330func render_bar(buf: *u8, off: *i64, val: i64, max: i64, r: i64, g: i64) -> i64 {
331 var n: i64 = val * 20 / max
332 if n < 0 { n = 0 }
333 if n > 20 { n = 20 }
334 bputc(buf, off, 91)
335 bput_fg(buf, off, r, g, 60)
336 var i: i64 = 0
337 while i < 20 {
338 if i < n { bputc(buf, off, 35) } else { bputc(buf, off, 45) }
339 i = i + 1
340 }
341 bput_reset(buf, off)
342 bputc(buf, off, 93); bputc(buf, off, 32); bputn(buf, off, val); bputc(buf, off, 10)
343 return 0
344}
345
346func render_combat(buf: *u8, off: *i64, st: *i64) -> i64 {
347 bputc(buf, off, 10)
348 bput(buf, off, " -- COMBAT (dungenc FSM) --" as *u8); bputc(buf, off, 10); bputc(buf, off, 10)
349 bput(buf, off, " YOU " as *u8); render_bar(buf, off, st[2], 100, 90, 230)
350 var emax: i64 = 20 + st[4] * 5
351 var ehp: i64 = st[7]
352 if ehp < 0 { ehp = 0 }
353 bput(buf, off, " FOE " as *u8); render_bar(buf, off, ehp, emax, 220, 60)
354 bputc(buf, off, 10)
355 return 0
356}
357
358func render_foot(buf: *u8, off: *i64, st: *i64) -> i64 {
359 let mode: i64 = st[5]
360 bputc(buf, off, 10)
361 if mode == 0 { bput(buf, off, " move: w a s d reach >> to descend quit: q" as *u8) }
362 if mode == 1 { bput(buf, off, " space = attack w/a/s/d = flee quit: q" as *u8) }
363 if mode == 2 { bput(buf, off, " LEVEL CLEAR! space = descend deeper quit: q" as *u8) }
364 if mode == 3 { bput(buf, off, " YOU DIED. r = new run quit: q" as *u8) }
365 bputc(buf, off, 10)
366 return 0
367}
368
369func render(buf: *u8, st: *i64) -> i64 {
370 let off: *i64 = sys_mmap(16) as *i64
371 off[0] = 0
372 bput_clear(buf, off)
373 render_hud(buf, off, st)
374 if st[5] == 1 { render_combat(buf, off, st) } else { render_grid(buf, off, st) }
375 render_foot(buf, off, st)
376 sys_write(1, buf, off[0])
377 return 0
378}
379
380func map_key(c: i64) -> i64 {
381 if c == 119 { return 0 } // w
382 if c == 115 { return 1 } // s
383 if c == 97 { return 2 } // a
384 if c == 100 { return 3 } // d
385 if c == 32 { return 4 } // space
386 if c == 102 { return 4 } // f
387 if c == 13 { return 4 } // enter
388 if c == 10 { return 4 } // newline
389 if c == 114 { return 5 } // r
390 return 9
391}
392
393// enable termios raw single-key mode (returns 1 if stdin is a tty, else 0)
394func term_raw_on(orig: *u8, raw: *u8) -> i64 {
395 let g: i64 = sys_ioctl(0, 0x5401, orig as i64) // TCGETS
396 if g != 0 { return 0 }
397 var i: i64 = 0
398 while i < 60 { raw[i] = orig[i]; i = i + 1 }
399 var lf: i64 = raw[12] as i64 // c_lflag low byte
400 lf = lf & (255 - 2) // clear ICANON
401 lf = lf & (255 - 8) // clear ECHO
402 raw[12] = lf as u8
403 raw[17 + 6] = 1 as u8 // VMIN = 1
404 raw[17 + 5] = 0 as u8 // VTIME = 0
405 sys_ioctl(0, 0x5402, raw as i64) // TCSETS
406 return 1
407}
408func term_raw_off(orig: *u8) -> i64 {
409 sys_ioctl(0, 0x5402, orig as i64)
410 return 0
411}
412
413func main() -> i64 {
414 let st: *i64 = sys_mmap(K_MAGIC_4096) as *i64
415 let orig: *u8 = sys_mmap(128)
416 let raw: *u8 = sys_mmap(128)
417 let is_tty: i64 = term_raw_on(orig, raw)
418 gi_init(st, sys_now_ms())
419 let buf: *u8 = sys_mmap(K_MAGIC_16384)
420 let ib: *u8 = sys_mmap(8)
421 var running: i64 = 1
422 while running == 1 {
423 render(buf, st)
424 let n: i64 = sys_read(0, ib, 1)
425 if n <= 0 {
426 running = 0
427 } else {
428 let c: i64 = ib[0] as i64
429 if c == 113 { running = 0 }
430 if c == 81 { running = 0 }
431 if c != 113 { if c != 81 { gi_input(st, map_key(c)) } }
432 }
433 }
434 if is_tty == 1 { term_raw_off(orig) }
435 let off: *i64 = sys_mmap(16) as *i64
436 off[0] = 0
437 bput_clear(buf, off)
438 bput(buf, off, "thanks for playing -- NISHI DUNGEON alpha\n" as *u8)
439 sys_write(1, buf, off[0])
440 sys_exit(0)
441 return 0
442}