bgen/src/genesis_main.c

215 lines
7.3 KiB
C
Raw Normal View History

2026-04-16 21:04:50 -04:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 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;
}
}
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;
}