#include #include #include #include "gen/grid.h" #include "gen/rng.h" #include "gen/room_types.h" #include "gen/events.h" #include "gen/accretion.h" #include "gen/loops.h" #include "gen/lakes.h" #include "gen/walls.h" #include "gen/stairs.h" #include "gen/chokepoints.h" #include "gen/machines.h" #include "gen/dijkstra.h" #include "json_emit.h" static int g_verbose = 0; static int g_quiet = 0; static int g_mark_unreached = 0; static int g_emit_json = 0; static void print_ascii(grid_t g) { dist_map_t dist; if (g_mark_unreached) { int sx = -1, sy = -1; for (int y = 0; y < DROWS && sx < 0; y++) for (int x = 0; x < DCOLS && sx < 0; x++) { uint8_t t = g[y][x].terrain; if (t == T_FLOOR || t == T_CORRIDOR || t == T_DOOR || t == T_BRIDGE) { sx = x; sy = y; } } if (sx >= 0) bfs_from(g, sx, sy, dist, -1, -1); } for (int y = 0; y < DROWS; y++) { for (int x = 0; x < DCOLS; x++) { char ch; uint8_t t = g[y][x].terrain; int passable = (t == T_FLOOR || t == T_CORRIDOR || t == T_DOOR || t == T_BRIDGE); if (g_mark_unreached && passable && dist[y][x] == DIST_UNREACHABLE) { ch = '!'; } else switch (t) { case T_FLOOR: ch = (g[y][x].flags & F_WREATH) ? ';' : '.'; break; case T_CORRIDOR: ch = ','; break; case T_DOOR: ch = '+'; break; case T_WALL: ch = (g[y][x].flags & F_WREATH) ? ':' : '#'; break; case T_LIQUID: switch (g[y][x].liquid) { case L_LAVA: ch = '^'; break; case L_CHASM: ch = 'v'; break; case L_BRIMSTONE: ch = '%'; break; case L_WATER: default: ch = '~'; break; } break; case T_BRIDGE: ch = '='; break; case T_STAIRS_UP: ch = '<'; break; case T_STAIRS_DOWN: ch = '>'; break; default: ch = ' '; break; } putchar(ch); } putchar('\n'); } } static void stderr_sink(void *ctx, const gen_event_t *ev) { (void)ctx; if (!g_verbose) return; fprintf(stderr, "[ev %4u] %s\n", ev->step, event_kind_name((event_kind_t)ev->kind)); } typedef struct { int counts[EV_KIND_COUNT]; } sink_ctx_t; static void counting_sink(void *ctx, const gen_event_t *ev) { sink_ctx_t *s = (sink_ctx_t *)ctx; s->counts[ev->kind]++; if (g_verbose) stderr_sink(NULL, ev); } typedef struct { int rooms, loops, lakes_proposed, lakes_rejected, lakes_placed, bridges; int machines_proposed, machines_placed; int floor_cells; int connected; int unreached; } run_stats_t; static void run_once(uint64_t seed, int depth, grid_t *gp, run_stats_t *out, int verify) { cell_t (*g)[DCOLS] = *gp; rng_t rng; rng_seed(&rng, seed); grid_fill(g, T_NOTHING); sink_ctx_t sctx = {0}; event_sink_t sink = { .fn = counting_sink, .ctx = &sctx, .step = 0 }; event_emit(&sink, EV_GEN_BEGIN, NULL, 0); accrete_rooms(g, &rng, &sink); if (verify && !is_connected(g, NULL)) fprintf(stderr, "!! seed %llu: disconnected after accretion\n", (unsigned long long)seed); add_loops(g, &sink); if (verify && !is_connected(g, NULL)) fprintf(stderr, "!! seed %llu: disconnected after loops\n", (unsigned long long)seed); place_lakes(g, &rng, &sink, depth); if (verify && !is_connected(g, NULL)) fprintf(stderr, "!! seed %llu: disconnected after lakes\n", (unsigned long long)seed); apply_wreaths(g, &sink); place_bridges(g, &sink); finish_walls(g, &sink); place_stairs(g, &sink); /* Chokepoint analysis + machine placement. */ static choke_map_t choke; static choke_flag_t is_choke, is_gate; compute_chokepoints(g, &sink, choke, is_choke, is_gate); place_machines(g, &rng, depth, &sink, choke, is_gate); event_emit(&sink, EV_GEN_END, NULL, 0); out->rooms = sctx.counts[EV_ROOM_PLACED]; out->loops = sctx.counts[EV_LOOP_ACCEPTED]; out->lakes_proposed = sctx.counts[EV_LAKE_PROPOSED]; out->lakes_rejected = sctx.counts[EV_LAKE_REJECTED_IMPASSABLE]; out->lakes_placed = sctx.counts[EV_LAKE_PLACED]; out->bridges = sctx.counts[EV_BRIDGE_PLACED]; out->machines_proposed = sctx.counts[EV_MACHINE_PROPOSED]; out->machines_placed = sctx.counts[EV_MACHINE_ACCEPTED]; out->floor_cells = grid_count_floor(g); out->connected = is_connected(g, &out->unreached); } static void print_summary(uint64_t seed, int depth, const run_stats_t *s) { fprintf(stderr, "seed=%llu depth=%d rooms=%d loops=%d " "lakes_proposed=%d lakes_rejected=%d lakes_placed=%d " "bridges=%d machines=%d/%d floor=%d connected=%d unreached=%d\n", (unsigned long long)seed, depth, s->rooms, s->loops, s->lakes_proposed, s->lakes_rejected, s->lakes_placed, s->bridges, s->machines_placed, s->machines_proposed, s->floor_cells, s->connected, s->unreached); } static void usage(FILE *f, const char *prog) { fprintf(f, "usage: %s [options]\n" "\n" "Generates a single Brogue-style 2D level and prints an ASCII map.\n" "\n" "options:\n" " --seed N RNG seed (default 1234)\n" " --depth N dungeon depth 1..26, biases lakes/machines (default 1)\n" " --verify assert connectivity after each pipeline stage\n" " --mark-unreached print '!' on passable cells BFS cannot reach from start\n" " --verbose stream generator events to stderr\n" " --quiet suppress ASCII map (summary only)\n" " --emit=json, --json emit grid JSON to stdout instead of ASCII\n" " --stress N run N seeds (from --stress-base) and report failures\n" " --stress-base N starting seed for --stress (default 1)\n" " -h, --help show this help and exit\n", prog); } static int cmd_stress(int n, uint64_t base_seed, int depth) { grid_t g; int failures = 0; for (int i = 0; i < n; i++) { uint64_t seed = base_seed + (uint64_t)i; run_stats_t s = {0}; run_once(seed, depth, &g, &s, 0); if (!s.connected) { fprintf(stderr, "FAIL seed=%llu unreached=%d\n", (unsigned long long)seed, s.unreached); failures++; } } fprintf(stderr, "stress: %d seeds, %d failures\n", n, failures); return failures == 0 ? 0 : 1; } int main(int argc, char **argv) { uint64_t seed = 1234; int depth = 1; int verify = 0; int stress_n = 0; uint64_t stress_base = 1; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--seed") && i + 1 < argc) { seed = strtoull(argv[++i], NULL, 10); } else if (!strcmp(argv[i], "--depth") && i + 1 < argc) { depth = atoi(argv[++i]); if (depth < 1) depth = 1; if (depth > 26) depth = 26; } else if (!strcmp(argv[i], "--verbose")) { g_verbose = 1; } else if (!strcmp(argv[i], "--quiet")) { g_quiet = 1; } else if (!strcmp(argv[i], "--verify")) { verify = 1; } else if (!strcmp(argv[i], "--stress") && i + 1 < argc) { stress_n = atoi(argv[++i]); } else if (!strcmp(argv[i], "--stress-base") && i + 1 < argc) { stress_base = strtoull(argv[++i], NULL, 10); } else if (!strcmp(argv[i], "--mark-unreached")) { g_mark_unreached = 1; } else if (!strcmp(argv[i], "--emit=json") || !strcmp(argv[i], "--json")) { g_emit_json = 1; } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { usage(stdout, argv[0]); return 0; } else { fprintf(stderr, "unknown arg: %s\n", argv[i]); usage(stderr, argv[0]); return 2; } } if (stress_n > 0) return cmd_stress(stress_n, stress_base, depth); grid_t g; run_stats_t s = {0}; run_once(seed, depth, &g, &s, verify); if (g_emit_json) { emit_grid_json(g, seed, depth); if (verify && !s.connected) return 1; return 0; } print_summary(seed, depth, &s); if (!g_quiet) print_ascii(g); if (verify && !s.connected) return 1; return 0; }