From f85ae8a6c6f12a31fe84355680410515d2034d05 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Wed, 11 Mar 2026 12:31:51 -0400 Subject: [PATCH] Better map generation and a test that re-rolls maps that aren't valid to figure out the stats of map gen failures. --- src/algos/matrix.hpp | 3 +- src/algos/maze.cpp | 101 +++++++++++++++++++-------------------- src/algos/maze.hpp | 1 - src/algos/shiterator.hpp | 44 ++++++++++++++++- src/gui/backend.cpp | 2 +- src/gui/backend.hpp | 2 +- src/main.cpp | 2 +- tests/battle.cpp | 2 +- tests/mazes.cpp | 44 +++++++++++------ tools/animator.cpp | 2 +- tools/arena.cpp | 2 +- tools/storyboard.cpp | 2 +- 12 files changed, 131 insertions(+), 76 deletions(-) diff --git a/src/algos/matrix.hpp b/src/algos/matrix.hpp index e08b304..dedcd57 100644 --- a/src/algos/matrix.hpp +++ b/src/algos/matrix.hpp @@ -6,6 +6,7 @@ namespace matrix { using Matrix = shiterator::Base; using viewport = shiterator::viewport_t; + using perimeter = shiterator::perimeter_t; using each_cell = shiterator::each_cell_t; @@ -17,7 +18,7 @@ namespace matrix { using rando_rect = shiterator::rando_rect_t; using rando_rect = shiterator::rando_rect_t; using rando_box = shiterator::rando_box_t; - using line = shiterator::line; + using line = shiterator::line_t; void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1); diff --git a/src/algos/maze.cpp b/src/algos/maze.cpp index d945dd1..143e862 100644 --- a/src/algos/maze.cpp +++ b/src/algos/maze.cpp @@ -254,32 +254,15 @@ namespace maze { matrix::dump(msg, wall_copy); } - void Builder::perimeter(size_t x, size_t y, size_t width, size_t height, std::function cb) { - // BUG: is the -1 on these an off-by-one - std::array starts{{ - {x,y}, {x + width-1, y}, {x + width-1, y + height-1}, {x, y + height-1} - }}; - - std::array ends{{ - {x + width-1, y}, {x + width-1, y + height-1}, {x, y + height-1}, {x,y}, - }}; - - for(size_t i = 0; i < starts.size(); i++) { - for(matrix::line it{starts[i], ends[i]}; it.next();) { - cb(it.x, it.y); - } - } - } - void Builder::enclose() { size_t width = matrix::width($walls); size_t height = matrix::height($walls); - perimeter(0, 0, width, height, [&](size_t x, size_t y) { - $walls[y][x] = WALL_VALUE; - Point at{x,y}; + for(matrix::perimeter it{0, 0, width, height}; it.next();) { + $walls[it.y][it.x] = WALL_VALUE; + Point at{it.x,it.y}; if($doors.contains(at)) $doors.erase(at); - }); + } } void Builder::open_box(size_t outer_size) { @@ -296,46 +279,66 @@ namespace maze { size_t width = (outer_size * 2) + 1; size_t height = (outer_size * 2) + 1; - perimeter(x, y, width, height, [&](size_t x, size_t y) { - for(matrix::compass it{$walls, x, y}; it.next();) { + for(matrix::perimeter p{x, y, width, height}; p.next();) { + for(matrix::compass it{$walls, p.x, p.y}; it.next();) { if($ends_map.contains({it.x, it.y})) { $walls[y][x] = 0; break; } } - }); + } } void Builder::make_doors() { for(auto room : $rooms) { // walk the outer wall looking for an emergent door - int has_door = 0; + int possible_doors = 0; Point last_door{0,0}; - perimeter(room.x - 1, room.y - 1, room.width + 2, room.height + 2, [&](size_t x, size_t y) { - if($walls[y][x] == SPACE_VALUE) { - last_door = {x, y}; - has_door++; - } - }); + matrix::perimeter it{room.x - 1, room.y - 1, room.width + 2, room.height + 2}; - // if only found one then make that an actual door - if(has_door == 1) { - $doors.insert_or_assign(last_door, true); + while(it.next()) { + if($walls[it.y][it.x] == SPACE_VALUE) { + last_door = {it.x, it.y}; + possible_doors++; + } } - // no emergent door found, punch out one - perimeter(room.x, room.y, room.width, room.height, [&](size_t x, size_t y) { - if($ends_map.contains({x, y})) { - for(matrix::compass door_at{$walls, x, y}; door_at.next();) { - if($walls[door_at.y][door_at.x] == WALL_VALUE) { - $walls[door_at.y][door_at.x] = SPACE_VALUE; - $doors.insert_or_assign({size_t(door_at.x), size_t(door_at.y)}, true); + // if only found one then make that an actual door + if(possible_doors == 1) { + $doors.insert_or_assign(last_door, true); + continue; + } + + // no natural door found, need to make one + it = {room.x - 1, room.y - 1, room.width + 2, room.height + 2}; + last_door = {0,0}; + bool found_door = false; + + while(it.next()) { + for(matrix::compass near_door{$walls, it.x, it.y}; near_door.next();) { + // skip the wall parts + if($walls[near_door.y][near_door.x] == WALL_VALUE) continue; + + if($ends_map.contains({near_door.x, near_door.y})) { + if(room.contains({near_door.x, near_door.y})) { + // last ditch effort is use the internal dead end + last_door = {it.x, it.y}; + } else { + $walls[it.y][it.x] = SPACE_VALUE; + $doors.insert_or_assign({it.x, it.y}, true); + found_door = true; break; } } } - }); + } + + if(!found_door && last_door.x != 0) { + // didn't find an external door so punch one at the dead end + $walls[last_door.y][last_door.x] = SPACE_VALUE; + $doors.insert_or_assign({last_door.x, last_door.y}, true); + } } } @@ -376,14 +379,10 @@ namespace maze { if(!room_should_exist(room)) return false; } - bool bad_perimeter = false; - // BUG: this is dogshit, get rid of perimeter, I can't even spell it right it's so bad - perimeter(0, 0, width, height, [&](size_t x, size_t y) { - if($ends_map.contains({x, y})) bad_perimeter = true; - if($doors.contains({x, y})) bad_perimeter = true; - }); - - if(bad_perimeter) return false; + for(matrix::perimeter it{0, 0, width, height}; it.next();) { + if($ends_map.contains({it.x, it.y})) return false; + if($doors.contains({it.x, it.y})) return false; + } if($rooms.size() == 1) return true; @@ -393,7 +392,7 @@ namespace maze { Point test{test_room.x, test_room.y}; $paths.set_target(test); $paths.compute_paths($walls); - $paths.dump("AFTER COMPUTE"); + // $paths.dump("AFTER COMPUTE"); $paths.clear_target(test); for(matrix::each_cell it{$walls}; it.next();) { diff --git a/src/algos/maze.hpp b/src/algos/maze.hpp index ff8efac..a2e6259 100644 --- a/src/algos/maze.hpp +++ b/src/algos/maze.hpp @@ -35,7 +35,6 @@ namespace maze { void divide(Point start, Point end); void remove_dead_ends(); void dump(const std::string& msg); - void perimeter(size_t x, size_t y, size_t width, size_t height, std::function cb); void open_box(size_t outer_size); void add_dead_end(Point at); bool room_should_exist(Room& room, bool allow_dupes=false); diff --git a/src/algos/shiterator.hpp b/src/algos/shiterator.hpp index d925609..077fce7 100644 --- a/src/algos/shiterator.hpp +++ b/src/algos/shiterator.hpp @@ -396,7 +396,7 @@ namespace shiterator { * within your Matrix, as it's assumed _you_ did that * already. */ - struct line { + struct line_t { int x; int y; int x1; @@ -407,7 +407,7 @@ namespace shiterator { int dy; int error; - line(Point start, Point end) : + line_t(Point start, Point end) : x(start.x), y(start.y), x1(end.x), y1(end.y) { @@ -594,6 +594,46 @@ namespace shiterator { } }; + template + struct perimeter_t { + size_t x; + size_t y; + size_t width; + size_t height; + + size_t i = 0; + + std::array starts{{ + {x,y}, {x + width-1, y}, {x + width-1, y + height-1}, {x, y + height-1} + }}; + + std::array ends{{ + {x + width-1, y}, {x + width-1, y + height-1}, {x, y + height-1}, {x,y}, + }}; + + line_t it{starts[i], ends[i]}; + + bool next() { + if(i >= starts.size()) return false; + + if(it.next()) { + x = it.x; + y = it.y; + return true; + } else { + x = 0; + y = 0; + i++; + if(i < starts.size()) { + it = {starts[i], ends[i]}; + return true; + } else { + return false; + } + } + } + }; + /* * BROKEN: I'm actually not sure what I'm trying to * do here yet. diff --git a/src/gui/backend.cpp b/src/gui/backend.cpp index b9bafdc..1d02cd4 100644 --- a/src/gui/backend.cpp +++ b/src/gui/backend.cpp @@ -5,7 +5,7 @@ #include "game/config.hpp" #include "graphics/palette.hpp" -namespace sfml { +namespace gui { using namespace nlohmann; guecs::SpriteTexture Backend::get_sprite(const string& name) { diff --git a/src/gui/backend.hpp b/src/gui/backend.hpp index fc36f35..3a5f9ca 100644 --- a/src/gui/backend.hpp +++ b/src/gui/backend.hpp @@ -1,6 +1,6 @@ #include "guecs/ui.hpp" -namespace sfml { +namespace gui { using std::string; class Backend : public guecs::Backend { diff --git a/src/main.cpp b/src/main.cpp index ea68351..e10f152 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,7 @@ int main(int argc, char* argv[]) { try { - sfml::Backend backend; + gui::Backend backend; shaders::init(); components::init(); diff --git a/tests/battle.cpp b/tests/battle.cpp index d00409f..3a38f7e 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -98,7 +98,7 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") { TEST_CASE("boss/systems.cpp works", "[combat-battle]") { components::init(); - sfml::Backend backend; + gui::Backend backend; guecs::init(&backend); ai::reset(); diff --git a/tests/mazes.cpp b/tests/mazes.cpp index 7487e49..85304f6 100644 --- a/tests/mazes.cpp +++ b/tests/mazes.cpp @@ -5,8 +5,9 @@ #include "algos/rand.hpp" #include "constants.hpp" #include "algos/maze.hpp" +#include "algos/stats.hpp" -#define DUMP 0 +#define DUMP 1 using std::string; using matrix::Matrix; @@ -102,21 +103,36 @@ TEST_CASE("hunt-and-kill too much", "[mazes]") { } TEST_CASE("hunt-and-kill validator", "[mazes]") { - Map map(33, 33); - maze::Builder maze(map); + bool valid = true; + Stats mofm; - maze.hunt_and_kill(); - maze.clear(); - maze.inner_box(6, 4); - maze.randomize_rooms(ROOM_SIZE); - maze.hunt_and_kill(); - maze.open_box(6); - maze.make_doors(); - maze.enclose(); + for(int i = 0; i < 100; i++) { + Stats door_prob; - bool valid = maze.validate(); + do { + Map map(33, 33); + maze::Builder maze(map); - maze.dump("VALIDATED"); + maze.hunt_and_kill(); + maze.clear(); + maze.inner_box(6, 4); + maze.randomize_rooms(ROOM_SIZE); + maze.hunt_and_kill(); + maze.open_box(6); + maze.make_doors(); + maze.enclose(); - REQUIRE(valid == true); + valid = maze.validate(); + if(valid && i == 99) { + maze.dump("VALIDATED"); + } + + door_prob.sample(valid); + } while(!valid); + + mofm.sample(door_prob.mean()); + } + + mofm.dump(); + REQUIRE(mofm.mean() > 0.20); } diff --git a/tools/animator.cpp b/tools/animator.cpp index ee337ad..4f47b73 100644 --- a/tools/animator.cpp +++ b/tools/animator.cpp @@ -296,7 +296,7 @@ int error_usage() { int main(int argc, char* argv[]) { ai::init("ai"); components::init(); - sfml::Backend backend; + gui::Backend backend; guecs::init(&backend); shaders::init(); diff --git a/tools/arena.cpp b/tools/arena.cpp index 7c0cc8f..3f44701 100644 --- a/tools/arena.cpp +++ b/tools/arena.cpp @@ -23,7 +23,7 @@ void craft_weapon() { int main(int, char*[]) { shaders::init(); components::init(); - sfml::Backend backend; + gui::Backend backend; guecs::init(&backend); ai::init("ai"); GameDB::init(); diff --git a/tools/storyboard.cpp b/tools/storyboard.cpp index 4254ab3..8964ab6 100644 --- a/tools/storyboard.cpp +++ b/tools/storyboard.cpp @@ -11,7 +11,7 @@ int main(int, char*[]) { components::init(); - sfml::Backend backend; + gui::Backend backend; guecs::init(&backend); cinematic::init(); sound::init();