code wiki / (root) / nx_tactics_native.nx

nx_tactics_native.nx source

↩ module page · 372 lines · 13384 B

1// nx_tactics_native.nx -- PLAYABLE XCOM-style turn-based TACTICS game (a 2nd game MODE, 2// proving the Nishi game ecosystem generalizes beyond the dungeon-crawler). Native binary 3// via Nishi's own toolchain (nx_cc->nxasm_x86, no gcc), terminal-rendered, hostable in the 4// arcade. Reuses the engine library's terminal helpers (bput*/term_raw_*); the tactics 5// logic (squads, action points, cover/hit-chance, enemy AI) is new here. 6// 7// HONEST AUTHORSHIP: tutor (Claude) authored this game module + the engine lib helpers it 8// reuses. Generalizing tactics into a spec-driven emitter (like nx_game_emit does for 9// dungeons) is the named next rung (G-ECO-005 more modes). license_tier: ORIGINAL 10// 11// state region (i64 at st): 0=turn(0=player 1=enemy) 1=cur(active player unit) 2=mode(0play 12// 1win 2lose) 3=rng 4=msg 5=moves_left 6=has_shot 13// units (NUNITS=4: 0,1 player / 2,3 enemy): ux=st[16+i] uy=st[22+i] uhp=st[28+i] 14// uteam=st[34+i] ualive=st[40+i]. world grid 16x12 bytes at st+2048 (0 floor 1 wall 2 cover). 15import "nx_syscalls.nx" 16import "nx_game_engine_lib.nx" 17const K_MAGIC_1103515245: i64 = 1103515245 18const K_MAGIC_12345: i64 = 12345 19const K_MAGIC_32767: i64 = 32767 20const K_MAGIC_2048: i64 = 2048 21const K_MAGIC_9999: i64 = 9999 22const K_MAGIC_8192: i64 = 8192 23const K_MAGIC_16384: i64 = 16384 24 25func tac_rng(st: *i64) -> i64 { 26 var s: i64 = st[3] 27 s = s * K_MAGIC_1103515245 + K_MAGIC_12345 28 st[3] = s 29 return (s >> 16) & K_MAGIC_32767 30} 31func tac_cell_get(st: *i64, x: i64, y: i64) -> i64 { 32 if x < 0 { return 1 } 33 if y < 0 { return 1 } 34 if x >= 16 { return 1 } 35 if y >= 12 { return 1 } 36 let w: *u8 = ((st as i64) + K_MAGIC_2048) as *u8 37 return w[y * 16 + x] as i64 38} 39func tac_cell_set(st: *i64, x: i64, y: i64, v: i64) -> i64 { 40 let w: *u8 = ((st as i64) + K_MAGIC_2048) as *u8 41 w[y * 16 + x] = v as u8 42 return 0 43} 44func tac_unit_at(st: *i64, x: i64, y: i64) -> i64 { 45 var i: i64 = 0 46 while i < 4 { 47 if st[40 + i] == 1 { 48 if st[16 + i] == x { if st[22 + i] == y { return i } } 49 } 50 i = i + 1 51 } 52 return 0 - 1 53} 54func tac_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 55func tac_dist(ax: i64, ay: i64, bx: i64, by: i64) -> i64 { return tac_abs(ax - bx) + tac_abs(ay - by) } 56func tac_team_alive(st: *i64, team: i64) -> i64 { 57 var c: i64 = 0 58 var i: i64 = 0 59 while i < 4 { 60 if st[40 + i] == 1 { if st[34 + i] == team { c = c + 1 } } 61 i = i + 1 62 } 63 return c 64} 65// nearest alive unit of the OTHER team to (x,y); -1 if none 66func tac_nearest_foe(st: *i64, x: i64, y: i64, myteam: i64) -> i64 { 67 var best: i64 = 0 - 1 68 var bd: i64 = K_MAGIC_9999 69 var i: i64 = 0 70 while i < 4 { 71 if st[40 + i] == 1 { 72 if st[34 + i] != myteam { 73 let d: i64 = tac_dist(x, y, st[16 + i], st[22 + i]) 74 if d < bd { bd = d; best = i } 75 } 76 } 77 i = i + 1 78 } 79 return best 80} 81func tac_first_player(st: *i64) -> i64 { 82 var i: i64 = 0 83 while i < 4 { 84 if st[40 + i] == 1 { if st[34 + i] == 0 { return i } } 85 i = i + 1 86 } 87 return 0 - 1 88} 89func tac_next_player(st: *i64, from: i64) -> i64 { 90 var i: i64 = from + 1 91 while i < 4 { 92 if st[40 + i] == 1 { if st[34 + i] == 0 { return i } } 93 i = i + 1 94 } 95 return 0 - 1 96} 97// place a unit on a random free floor cell within a corner box 98func tac_place_unit(st: *i64, idx: i64, x0: i64, y0: i64, team: i64) -> i64 { 99 var tries: i64 = 0 100 while tries < 80 { 101 tries = tries + 1 102 let rx: i64 = x0 + (tac_rng(st) % 5) 103 let ry: i64 = y0 + (tac_rng(st) % 5) 104 var ok: i64 = 1 105 if tac_cell_get(st, rx, ry) != 0 { ok = 0 } 106 if tac_unit_at(st, rx, ry) >= 0 { ok = 0 } 107 if ok == 1 { 108 st[16 + idx] = rx; st[22 + idx] = ry; st[28 + idx] = 10 109 st[34 + idx] = team; st[40 + idx] = 1 110 return 0 111 } 112 } 113 st[16 + idx] = x0; st[22 + idx] = y0; st[28 + idx] = 10; st[34 + idx] = team; st[40 + idx] = 1 114 return 0 115} 116func tac_build(st: *i64) -> i64 { 117 var y: i64 = 0 118 while y < 12 { 119 var x: i64 = 0 120 while x < 16 { 121 var c: i64 = 0 122 if x == 0 { c = 1 } 123 if y == 0 { c = 1 } 124 if x == 15 { c = 1 } 125 if y == 11 { c = 1 } 126 tac_cell_set(st, x, y, c) 127 x = x + 1 128 } 129 y = y + 1 130 } 131 var i: i64 = 0 132 while i < 12 { 133 let rx: i64 = 2 + (tac_rng(st) % 12) 134 let ry: i64 = 2 + (tac_rng(st) % 8) 135 tac_cell_set(st, rx, ry, 1) 136 i = i + 1 137 } 138 var j: i64 = 0 139 while j < 10 { 140 let rx: i64 = 2 + (tac_rng(st) % 12) 141 let ry: i64 = 2 + (tac_rng(st) % 8) 142 if tac_cell_get(st, rx, ry) == 0 { tac_cell_set(st, rx, ry, 2) } 143 j = j + 1 144 } 145 tac_place_unit(st, 0, 1, 1, 0) 146 tac_place_unit(st, 1, 1, 6, 0) 147 tac_place_unit(st, 2, 11, 2, 1) 148 tac_place_unit(st, 3, 11, 7, 1) 149 return 0 150} 151func tac_init(st: *i64, seed: i64) -> i64 { 152 st[3] = seed 153 tac_build(st) 154 st[0] = 0 155 st[1] = tac_first_player(st) 156 st[2] = 0 157 st[4] = 0 158 st[5] = 5 159 st[6] = 0 160 return 0 161} 162func tac_check_end(st: *i64) -> i64 { 163 if tac_team_alive(st, 1) == 0 { st[2] = 1 } 164 if tac_team_alive(st, 0) == 0 { st[2] = 2 } 165 return 0 166} 167// hit chance (out of 10) for shooter at distance d vs target standing on `cover` (1/0) 168func tac_chance(d: i64, cover: i64) -> i64 { 169 var c: i64 = 8 - (d / 3) 170 if cover == 1 { c = c - 3 } 171 if c < 1 { c = 1 } 172 if c > 9 { c = 9 } 173 return c 174} 175func tac_move_unit(st: *i64, ui: i64, dx: i64, dy: i64) -> i64 { 176 if st[5] <= 0 { st[4] = 7; return 0 } 177 let nx: i64 = st[16 + ui] + dx 178 let ny: i64 = st[22 + ui] + dy 179 if tac_cell_get(st, nx, ny) == 1 { return 0 } 180 if tac_unit_at(st, nx, ny) >= 0 { return 0 } 181 st[16 + ui] = nx; st[22 + ui] = ny 182 st[5] = st[5] - 1 183 return 0 184} 185func tac_end_unit(st: *i64) -> i64 { 186 let nxt: i64 = tac_next_player(st, st[1]) 187 if nxt >= 0 { st[1] = nxt; st[5] = 5; st[6] = 0; return 0 } 188 // no more player units -> enemy turn 189 tac_enemy_turn(st) 190 st[0] = 0 191 st[1] = tac_first_player(st) 192 st[5] = 5 193 st[6] = 0 194 tac_check_end(st) 195 return 0 196} 197func tac_shoot(st: *i64, ui: i64) -> i64 { 198 let ei: i64 = tac_nearest_foe(st, st[16 + ui], st[22 + ui], st[34 + ui]) 199 if ei < 0 { st[4] = 8; return 0 } 200 let d: i64 = tac_dist(st[16 + ui], st[22 + ui], st[16 + ei], st[22 + ei]) 201 if d > 9 { st[4] = 9; return 0 } 202 var cover: i64 = 0 203 if tac_cell_get(st, st[16 + ei], st[22 + ei]) == 2 { cover = 1 } 204 let ch: i64 = tac_chance(d, cover) 205 let roll: i64 = tac_rng(st) % 10 206 if roll < ch { 207 st[28 + ei] = st[28 + ei] - 5 208 if st[28 + ei] <= 0 { st[40 + ei] = 0; st[4] = 11 } else { st[4] = 12 } 209 } else { st[4] = 13 } 210 return 0 211} 212func tac_enemy_turn(st: *i64) -> i64 { 213 var i: i64 = 2 214 while i < 4 { 215 if st[40 + i] == 1 { 216 let pi: i64 = tac_nearest_foe(st, st[16 + i], st[22 + i], 1) 217 if pi >= 0 { 218 var steps: i64 = 0 219 while steps < 4 { 220 let tx: i64 = st[16 + pi] 221 let ty: i64 = st[22 + pi] 222 var dx: i64 = 0 223 var dy: i64 = 0 224 if tac_abs(tx - st[16 + i]) >= tac_abs(ty - st[22 + i]) { 225 if tx > st[16 + i] { dx = 1 } 226 if tx < st[16 + i] { dx = 0 - 1 } 227 } else { 228 if ty > st[22 + i] { dy = 1 } 229 if ty < st[22 + i] { dy = 0 - 1 } 230 } 231 let nx: i64 = st[16 + i] + dx 232 let ny: i64 = st[22 + i] + dy 233 var moved: i64 = 0 234 if tac_cell_get(st, nx, ny) != 1 { 235 if tac_unit_at(st, nx, ny) < 0 { 236 if tac_dist(nx, ny, tx, ty) >= 1 { st[16 + i] = nx; st[22 + i] = ny; moved = 1 } 237 } 238 } 239 if moved == 0 { steps = 4 } 240 steps = steps + 1 241 } 242 let d: i64 = tac_dist(st[16 + i], st[22 + i], st[16 + pi], st[22 + pi]) 243 if d <= 9 { 244 var cover: i64 = 0 245 if tac_cell_get(st, st[16 + pi], st[22 + pi]) == 2 { cover = 1 } 246 let ch: i64 = tac_chance(d, cover) 247 let roll: i64 = tac_rng(st) % 10 248 if roll < ch { 249 st[28 + pi] = st[28 + pi] - 5 250 if st[28 + pi] <= 0 { st[40 + pi] = 0 } 251 } 252 } 253 } 254 } 255 i = i + 1 256 } 257 return 0 258} 259// key: 0 up 1 down 2 left 3 right 4 shoot 5 end-unit 6 restart 260func tac_input(st: *i64, key: i64) -> i64 { 261 if st[2] != 0 { 262 if key == 6 { tac_init(st, st[3] + 7) } 263 return 0 264 } 265 if st[0] != 0 { return 0 } 266 let ci: i64 = st[1] 267 if ci < 0 { return 0 } 268 if key == 0 { tac_move_unit(st, ci, 0, 0 - 1) } 269 if key == 1 { tac_move_unit(st, ci, 0, 1) } 270 if key == 2 { tac_move_unit(st, ci, 0 - 1, 0) } 271 if key == 3 { tac_move_unit(st, ci, 1, 0) } 272 if key == 4 { tac_shoot(st, ci); st[6] = 1; tac_check_end(st); if st[2] == 0 { tac_end_unit(st) } } 273 if key == 5 { tac_end_unit(st) } 274 return 0 275} 276func tac_draw_cell(buf: *u8, off: *i64, st: *i64, x: i64, y: i64) -> i64 { 277 let ui: i64 = tac_unit_at(st, x, y) 278 if ui >= 0 { 279 if st[34 + ui] == 0 { 280 bput_bg(buf, off, 24, 40, 28) 281 if ui == st[1] { bput_fg(buf, off, 250, 240, 90) } else { bput_fg(buf, off, 90, 230, 120) } 282 bput(buf, off, "P" as *u8); bputn(buf, off, ui + 1) 283 } else { 284 bput_bg(buf, off, 40, 24, 24); bput_fg(buf, off, 230, 70, 70); bput(buf, off, "E" as *u8); bputn(buf, off, ui - 1) 285 } 286 bput_reset(buf, off) 287 return 0 288 } 289 let c: i64 = tac_cell_get(st, x, y) 290 if c == 1 { bput_bg(buf, off, 70, 80, 110); bput(buf, off, " " as *u8); bput_reset(buf, off); return 0 } 291 if c == 2 { bput_bg(buf, off, 86, 70, 40); bput(buf, off, "##" as *u8); bput_reset(buf, off); return 0 } 292 bput_bg(buf, off, 24, 22, 30); bput(buf, off, " " as *u8); bput_reset(buf, off) 293 return 0 294} 295func tac_render(buf: *u8, st: *i64) -> i64 { 296 let off: *i64 = sys_mmap(16) as *i64 297 off[0] = 0 298 bput_clear(buf, off) 299 bput(buf, off, " NISHI TACTICS Turn: " as *u8) 300 if st[0] == 0 { bput(buf, off, "PLAYER" as *u8) } else { bput(buf, off, "ENEMY" as *u8) } 301 bput(buf, off, " Squad " as *u8); bputn(buf, off, tac_team_alive(st, 0)) 302 bput(buf, off, " Enemies " as *u8); bputn(buf, off, tac_team_alive(st, 1)) 303 let ci: i64 = st[1] 304 if ci >= 0 { bput(buf, off, " ActiveHP " as *u8); bputn(buf, off, st[28 + ci]); bput(buf, off, " Moves " as *u8); bputn(buf, off, st[5]) } 305 bputc(buf, off, 10) 306 let m: i64 = st[4] 307 bput(buf, off, " " as *u8) 308 if m == 11 { bput(buf, off, ">> ENEMY DOWN!" as *u8) } 309 if m == 12 { bput(buf, off, ">> hit!" as *u8) } 310 if m == 13 { bput(buf, off, ">> missed" as *u8) } 311 if m == 9 { bput(buf, off, ">> out of range (move closer)" as *u8) } 312 if m == 7 { bput(buf, off, ">> no moves left (space=shoot, n=end)" as *u8) } 313 if st[2] == 1 { bput(buf, off, " *** VICTORY -- squad cleared the map! (r = new battle)" as *u8) } 314 if st[2] == 2 { bput(buf, off, " *** DEFEAT -- squad wiped (r = new battle)" as *u8) } 315 bputc(buf, off, 10); bputc(buf, off, 10) 316 var y: i64 = 0 317 while y < 12 { 318 var x: i64 = 0 319 while x < 16 { 320 tac_draw_cell(buf, off, st, x, y) 321 x = x + 1 322 } 323 bput_reset(buf, off); bputc(buf, off, 10) 324 y = y + 1 325 } 326 bputc(buf, off, 10) 327 bput(buf, off, " w/a/s/d move active unit space = shoot nearest foe n = end unit r restart q quit" as *u8) 328 bputc(buf, off, 10) 329 bput(buf, off, " green P# = your squad (yellow = active) red E# = enemy ## = cover (harder to hit)" as *u8) 330 bputc(buf, off, 10) 331 sys_write(1, buf, off[0]) 332 return 0 333} 334func tac_key(c: i64) -> i64 { 335 if c == 119 { return 0 } 336 if c == 115 { return 1 } 337 if c == 97 { return 2 } 338 if c == 100 { return 3 } 339 if c == 32 { return 4 } 340 if c == 110 { return 5 } 341 if c == 114 { return 6 } 342 return 9 343} 344func main() -> i64 { 345 let st: *i64 = sys_mmap(K_MAGIC_8192) as *i64 346 let orig: *u8 = sys_mmap(128) 347 let raw: *u8 = sys_mmap(128) 348 let is_tty: i64 = term_raw_on(orig, raw) 349 tac_init(st, sys_now_ms()) 350 let buf: *u8 = sys_mmap(K_MAGIC_16384) 351 let ib: *u8 = sys_mmap(8) 352 var running: i64 = 1 353 while running == 1 { 354 tac_render(buf, st) 355 let n: i64 = sys_read(0, ib, 1) 356 if n <= 0 { running = 0 } 357 if n > 0 { 358 let c: i64 = ib[0] as i64 359 if c == 113 { running = 0 } 360 if c == 81 { running = 0 } 361 if c != 113 { if c != 81 { tac_input(st, tac_key(c)) } } 362 } 363 } 364 if is_tty == 1 { term_raw_off(orig) } 365 let off: *i64 = sys_mmap(16) as *i64 366 off[0] = 0 367 bput_clear(buf, off) 368 bput(buf, off, "thanks for playing -- NISHI TACTICS\n" as *u8) 369 sys_write(1, buf, off[0]) 370 sys_exit(0) 371 return 0 372}