From fb41c153c178d0997379c9c630f80ca973d78363 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sun, 8 Mar 2026 03:28:49 -0400 Subject: [PATCH] With some help from chat I sorted out how to randomize rooms without overlap. --- src/algos/maze.cpp | 73 ++++++++++++++++++++++++--------------- src/algos/maze.hpp | 11 +++--- src/constants.hpp | 1 + src/game/map.hpp | 45 ++++++++++++++++++++++++ src/game/worldbuilder.cpp | 4 +-- tests/mazes.cpp | 44 +++++++++++++---------- 6 files changed, 126 insertions(+), 52 deletions(-) diff --git a/src/algos/maze.cpp b/src/algos/maze.cpp index b2c4744..0693364 100644 --- a/src/algos/maze.cpp +++ b/src/algos/maze.cpp @@ -7,7 +7,6 @@ using std::string; using matrix::Matrix; -constexpr const int ROOM_SIZE=1; namespace maze { inline size_t rand(size_t i, size_t j) { @@ -95,28 +94,37 @@ namespace maze { dbc::sentinel("failed to find coord?"); } - void Builder::randomize_rooms() { + bool Builder::room_should_exist(Room& room) { + if(!matrix::inbounds($walls, room.x, room.y) || + !matrix::inbounds($walls, room.x + room.width, room.y + room.height)) + { + return false; + } + + for(auto& other : $rooms) { + if(room.overlaps(other)) return false; + } + + // it's in the map and doesn't collide with another room + return true; + } + + void Builder::randomize_rooms(size_t room_size) { // use those dead ends to randomly place rooms for(auto at : $dead_ends) { // moving by +1 can randomly surround rooms with walls or not size_t offset = Random::uniform(0,1); - Room cur{at.x+offset, at.y+offset, ROOM_SIZE, ROOM_SIZE}; + Room cur{at.x+offset, at.y+offset, room_size, room_size}; + bool selected = Random::uniform(0,1) == 0; // if it's out of bounds skip it - if(!matrix::inbounds($walls, cur.x, cur.y) - || !matrix::inbounds($walls, cur.x + cur.width, cur.y + cur.height)) - { - continue; - } - - // randomly select 2/3rd - if(Random::uniform(0,2) > 0) { + if(selected && room_should_exist(cur)) { $rooms.push_back(cur); } } } - void Builder::init() { + void Builder::clear() { matrix::assign($walls, WALL_VALUE); } @@ -135,7 +143,7 @@ namespace maze { if(n.size() == 0) { // no neighbors, must be dead end - $dead_ends.push_back(on); + add_dead_end(on); auto t = find_coord($walls); on = t.first; $walls[on.y][on.x] = 0; @@ -164,7 +172,7 @@ namespace maze { void Builder::place_rooms() { for(auto& room : $rooms) { - for(matrix::box it{$walls, room.x, room.y, room.width}; it.next();) { + for(matrix::rectangle it{$walls, room.x, room.y, room.width, room.height}; it.next();) { $walls[it.y][it.x] = 0; } } @@ -201,6 +209,7 @@ namespace maze { int diff_x = at.x - it.x; int diff_y = at.y - it.y; $walls[at.y + diff_y][at.x + diff_x] = 0; + break; } } } @@ -211,10 +220,10 @@ namespace maze { // mark the rooms too for(auto& room : $rooms) { - for(matrix::box it{wall_copy, room.x, room.y, room.width}; + for(matrix::rectangle it{wall_copy, room.x, room.y, room.width, room.height}; it.next();) { - if(wall_copy[it.y][it.x] == 0) { + if(wall_copy[it.y][it.x] == 0 && wall_copy[it.y][it.x] != 3) { wall_copy[it.y][it.x] = WALL_PATH_LIMIT; } } @@ -222,6 +231,7 @@ namespace maze { // mark dead ends for(auto at : $dead_ends) { + // don't mark dead ends if there's something else there wall_copy[at.y][at.x]=32; } @@ -229,6 +239,7 @@ namespace maze { } 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} }}; @@ -253,24 +264,23 @@ namespace maze { }); } - void Builder::open_box(size_t outer_size, size_t inner_size) { + void Builder::open_box(size_t outer_size) { size_t center_x = matrix::width($walls) / 2; size_t center_y = matrix::height($walls) / 2; - // this can't be right but for now it's working - size_t x = center_x - outer_size - 1; - size_t y = center_y - outer_size - 1; - size_t width = (outer_size + 1) * 2 + 1; - size_t height = (outer_size + 1) * 2 + 1; + // compensate for the box's border now + outer_size++; - std::unordered_map ends; - for(auto& at : $dead_ends) { - ends.insert_or_assign(at, true); - } + // this can't be right but for now it's working + size_t x = center_x - outer_size; + size_t y = center_y - outer_size; + // BUG: is the + 1 here because the bug in perimeter + 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();) { - if(ends.contains({it.x, it.y})) { + if($ends_map.contains({it.x, it.y})) { $walls[y][x] = 0; break; } @@ -282,7 +292,6 @@ namespace maze { size_t x = matrix::width($walls) / 2; size_t y = matrix::height($walls) / 2; - for(matrix::box it{$walls, x, y, outer_size}; it.next();) { $walls[it.y][it.x] = 0; @@ -293,4 +302,12 @@ namespace maze { $walls[it.y][it.x] = 1; } } + + void Builder::add_dead_end(Point at) { + auto [_, inserted] = $ends_map.insert_or_assign(at, true); + + if(inserted) { + $dead_ends.push_back(at); + } + } } diff --git a/src/algos/maze.hpp b/src/algos/maze.hpp index cad27f4..5130409 100644 --- a/src/algos/maze.hpp +++ b/src/algos/maze.hpp @@ -9,25 +9,28 @@ namespace maze { Matrix& $walls; std::vector& $rooms; std::vector& $dead_ends; + std::unordered_map $ends_map; Builder(Map& map) : $walls(map.$walls), $rooms(map.$rooms), $dead_ends(map.$dead_ends) { - init(); + clear(); } - void init(); + void clear(); void hunt_and_kill(Point on={1,1}); void place_rooms(); void ensure_doors(); void enclose(); - void randomize_rooms(); + void randomize_rooms(size_t room_size); void inner_donut(float outer_rad, float inner_rad); void inner_box(size_t outer_size, size_t inner_size); 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, size_t inner_size); + void open_box(size_t outer_size); + void add_dead_end(Point at); + bool room_should_exist(Room& room); }; } diff --git a/src/constants.hpp b/src/constants.hpp index e975ad2..e89c899 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -19,6 +19,7 @@ constexpr const int MAP_TILE_DIM=64; constexpr const int ICONGEN_MAP_TILE_DIM=64; constexpr const int PLAYER_SPRITE_DIR_CORRECTION=270; constexpr const int RENDER_DISTANCE=500; +constexpr const int ROOM_SIZE=3; constexpr const int BOSS_VIEW_WIDTH=1080; constexpr const int BOSS_VIEW_HEIGHT=SCREEN_HEIGHT; diff --git a/src/game/map.hpp b/src/game/map.hpp index b62d6ee..c396db0 100644 --- a/src/game/map.hpp +++ b/src/game/map.hpp @@ -13,11 +13,56 @@ using lighting::LightSource; + +/* +struct Point { + double x; + double y; +}; + +struct Rect { + Point p1, p2, p3, p4; + // four corners (any order) + bool contains(const Point& pt) const { + double minX = std::min({p1.x, p2.x, p3.x, p4.x}); + double maxX = std::max({p1.x, p2.x, p3.x, p4.x}); + double minY = std::min({p1.y, p2.y, p3.y, p4.y}); + double maxY = std::max({p1.y, p2.y, p3.y, p4.y}); + return pt.x >= minX && pt.x <= maxX && pt.y >= minY && pt.y <= maxY; + } +}; +*/ + struct Room { size_t x = 0; size_t y = 0; size_t width = 0; size_t height = 0; + + + bool contains(Point at) { + size_t right = x + width - 1; + size_t left = x; + size_t top = y; + size_t bottom = y + height - 1; + + return at.x >= left && at.x <= right && at.y >= top && at.y <= bottom; + } + + bool overlaps(Room& other) { + return + // other left > this right == other too far right + !( other.x > x + width + + // other right < this left == other too far left + || other.x + other.width < x + + // other top > this bottom == too far below + || other.y > y + height + + // other bottom < this top == too far above + || other.y + other.width < y); + } }; using EntityGrid = std::unordered_map; diff --git a/src/game/worldbuilder.cpp b/src/game/worldbuilder.cpp index b9912f6..90b17ef 100644 --- a/src/game/worldbuilder.cpp +++ b/src/game/worldbuilder.cpp @@ -45,9 +45,9 @@ void WorldBuilder::generate_map() { maze::Builder maze($map); maze.hunt_and_kill(); - maze.init(); + maze.clear(); - maze.randomize_rooms(); + maze.randomize_rooms(ROOM_SIZE); if($map.width() > 20) { maze.inner_box(4, 2); diff --git a/tests/mazes.cpp b/tests/mazes.cpp index e7ce5cb..0b7bfce 100644 --- a/tests/mazes.cpp +++ b/tests/mazes.cpp @@ -16,25 +16,31 @@ TEST_CASE("hunt-and-kill", "[mazes]") { maze.hunt_and_kill(); // maze.dump("BASIC MAZE"); - maze.randomize_rooms(); + maze.randomize_rooms(ROOM_SIZE); maze.hunt_and_kill(); - maze.dump("ROOM MAZE"); + // maze.dump("ROOM MAZE"); REQUIRE(map.$dead_ends.size() > 0); REQUIRE(map.$rooms.size() > 0); } TEST_CASE("hunt-and-kill box", "[mazes]") { - Map map(21, 21); - maze::Builder maze(map); + for(int i = 5; i < 10000; i++) { + Map map((i / 20) + 20, (i / 20) + 20); + maze::Builder maze(map); - maze.inner_box(5, 4); - maze.hunt_and_kill(); - maze.open_box(5, 4); + maze.hunt_and_kill(); + maze.randomize_rooms(ROOM_SIZE+1); + maze.clear(); - maze.dump("INNER BOX"); + maze.inner_box(6, 3); + maze.hunt_and_kill(); + maze.open_box(6); - REQUIRE(maze.$rooms.size() == 0); + if(maze.$rooms.size() == 0) { + maze.dump("NO ROOMS"); + } + } } TEST_CASE("hunt-and-kill ring", "[mazes]") { @@ -75,15 +81,17 @@ TEST_CASE("hunt-and-kill no-dead-ends", "[mazes]") { } TEST_CASE("hunt-and-kill too much", "[mazes]") { - Map map(21, 21); - maze::Builder maze(map); + for(int i = 20; i < 10000; i++) { + Map map((i / 20) + 20, (i / 20) + 20); + maze::Builder maze(map); - maze.hunt_and_kill(); - maze.randomize_rooms(); - maze.init(); - maze.inner_donut(4, 2); - maze.divide({3,3}, {19,18}); - maze.hunt_and_kill(); + maze.hunt_and_kill(); + maze.randomize_rooms(ROOM_SIZE); + maze.clear(); + maze.inner_donut(4, 2); + maze.divide({3,3}, {15,16}); + maze.hunt_and_kill(); - // maze.dump("COMBINED"); + // maze.dump("COMBINED"); + } }