With some help from chat I sorted out how to randomize rooms without overlap.
This commit is contained in:
parent
47c0d4a5f0
commit
fb41c153c1
6 changed files with 126 additions and 52 deletions
|
|
@ -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<void(size_t x, size_t y)> cb) {
|
||||
// BUG: is the -1 on these an off-by-one
|
||||
std::array<Point, 4> 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<Point, bool> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,25 +9,28 @@ namespace maze {
|
|||
Matrix& $walls;
|
||||
std::vector<Room>& $rooms;
|
||||
std::vector<Point>& $dead_ends;
|
||||
std::unordered_map<Point, bool> $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<void(size_t x, size_t y)> 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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Point, wchar_t>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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.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);
|
||||
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.randomize_rooms(ROOM_SIZE);
|
||||
maze.clear();
|
||||
maze.inner_donut(4, 2);
|
||||
maze.divide({3,3}, {19,18});
|
||||
maze.divide({3,3}, {15,16});
|
||||
maze.hunt_and_kill();
|
||||
|
||||
// maze.dump("COMBINED");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue