"""Compile the authored 32x32 hell map and validate progression offline.""" from pathlib import Path from collections import deque ROOT = Path(__file__).resolve().parents[2] grid = [[185 for _ in range(32)] for _ in range(32)] def room(x0,y0,x1,y1): for y in range(y0,y1+1): for x in range(x0,x1+1): grid[y][x] = 0 # Winding route through five arenas, with locked final sanctum. room(2,23,10,29) # entry room(3,14,10,21) # barracks room(5,21,6,23) room(12,13,21,22) # lava court room(10,16,12,17) room(13,3,21,10) # key crypt room(17,10,18,13) room(24,3,29,20) # final approach room(21,6,24,7) room(23,23,29,29) # boss room(26,20,27,23) # Pillars and cover; enough width for movement and projectile dodging. for x,y in [(5,17),(8,18),(15,16),(19,19),(16,6),(19,7),(26,10),(27,16),(25,26)]: grid[y][x] = 186 # Two discoverable secret doors. room(2,7,8,11) room(5,11,6,14) room(24,23,25,23) room(18,25,21,28) room(21,26,23,27) doors = [(5,12,0),(6,12,0),(22,26,1),(22,27,1),(22,6,2),(22,7,2)] for x,y,_ in doors: grid[y][x] = 188 for y in range(1,31): for x in range(1,31): if grid[y][x] == 185: if x >= 23 and y >= 22: grid[y][x] = 190 # infernal sanctum elif x >= 23: grid[y][x] = 187 # flesh-lined approach elif x >= 12 and y <= 11: grid[y][x] = 186 # crypt stone # kind: 0 soldier, 1 melee, 2 imp, 3 boss. Coordinates at tile centres. enemies = [(7,25,0),(8,28,0),(4,20,0),(8,16,1),(9,20,2),(4,15,0), (14,20,2),(20,21,0),(20,14,2),(13,14,1),(18,17,1),(15,18,0), (14,8,2),(20,4,2),(15,4,0),(20,9,1),(18,5,0), (25,5,0),(28,8,2),(25,12,1),(28,15,0),(25,19,2),(28,19,1), (24,25,2),(28,25,2),(27,28,3),(4,9,1),(7,8,0)] # item kinds correspond to items.bin: health, armour, bullets, shells, shotgun, key, secret. items = [(3,27,4),(4,28,3),(9,24,2),(3,19,0),(8,14,3),(9,15,2), (13,21,1),(14,14,3),(20,20,0),(19,14,2),(14,4,0),(20,3,5), (17,9,3),(25,4,2),(28,11,0),(25,17,3),(28,20,3), (24,24,0),(29,28,3),(24,28,2),(3,8,1),(7,10,3),(19,26,0),(20,27,3)] for x,y,_ in enemies+items: assert grid[y][x] == 0, (x,y) def reachable(open_groups=()): seen = {(3,26)} todo = deque(seen) while todo: x,y = todo.popleft() for nx,ny in [(x-1,y),(x+1,y),(x,y-1),(x,y+1)]: if (nx,ny) in seen or not (0<=nx<32 and 0<=ny<32): continue if grid[ny][nx] and (nx,ny) not in [(x,y) for x,y,group in doors if group in open_groups]: continue seen.add((nx,ny)) todo.append((nx,ny)) return seen assert (20,3) in reachable() and (27,28) not in reachable() assert (27,28) not in reachable((0,1)), 'Secrets must not bypass the key gate' assert (27,28) in reachable((2,)), 'Secrets must remain optional' assert all((x,y) in reachable((0,1,2)) for x,y,_ in enemies+items) # Conservative ammunition budget: half the pellets land at minimum damage. bullets = 80 + 40*sum(kind == 2 for _,_,kind in items) shells = 16 + 12*sum(kind == 3 for _,_,kind in items) enemy_hp = sum((55,105,85,1100)[kind] for _,_,kind in enemies) assert (bullets + shells*7)*8//2 > enemy_hp lines = ['; Generated by tools/generators/make_hell_level.py.','.data','hfMapTemplate label byte'] lines += [' db '+','.join(map(str,row)) for row in grid] lines += ['hfEnemySeeds label dword'] lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in enemies] lines += [f'HF_ENEMIES equ {len(enemies)}','hfItemSeeds label dword'] lines += [f' dd {x}*FP_1+FP_HALF,{y}*FP_1+FP_HALF,{kind}' for x,y,kind in items] lines += [f'HF_ITEMS equ {len(items)}','.code'] (ROOT/'src_graphics/hell_fps_map.inc').write_text('\n'.join(lines)+'\n',encoding='ascii') print(f'Hell map: {len(enemies)} enemies, {len(items)} pickups, key gate and two secrets validated')