I can validate that a map is pathable, but can't fix it.
This commit is contained in:
parent
74f92dfe2c
commit
8090251a71
7 changed files with 116 additions and 21 deletions
2
Makefile
2
Makefile
|
|
@ -60,7 +60,7 @@ clean:
|
|||
meson compile --clean -C builddir
|
||||
|
||||
debug_test: build
|
||||
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests
|
||||
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests "[mazes]"
|
||||
|
||||
win_installer:
|
||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
using std::string;
|
||||
using matrix::Matrix;
|
||||
|
||||
|
||||
namespace maze {
|
||||
inline size_t rand(size_t i, size_t j) {
|
||||
if(i < j) {
|
||||
|
|
@ -94,7 +93,7 @@ namespace maze {
|
|||
dbc::sentinel("failed to find coord?");
|
||||
}
|
||||
|
||||
bool Builder::room_should_exist(Room& room) {
|
||||
bool Builder::room_should_exist(Room& room, bool allow_dupes) {
|
||||
if(!matrix::inbounds($walls, room.x, room.y) ||
|
||||
!matrix::inbounds($walls, room.x + room.width, room.y + room.height))
|
||||
{
|
||||
|
|
@ -106,7 +105,11 @@ namespace maze {
|
|||
}
|
||||
|
||||
for(auto& other : $rooms) {
|
||||
if(room.overlaps(other)) return false;
|
||||
bool is_duped = allow_dupes ? room == other : room != other;
|
||||
|
||||
if(is_duped && room.overlaps(other)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// it's in the map and doesn't collide with another room
|
||||
|
|
@ -181,8 +184,6 @@ namespace maze {
|
|||
on = nb;
|
||||
}
|
||||
}
|
||||
|
||||
enclose();
|
||||
}
|
||||
|
||||
void Builder::place_rooms() {
|
||||
|
|
@ -191,8 +192,6 @@ namespace maze {
|
|||
$walls[it.y][it.x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
enclose();
|
||||
}
|
||||
|
||||
void Builder::inner_donut(float outer_rad, float inner_rad) {
|
||||
|
|
@ -245,7 +244,11 @@ 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;
|
||||
wall_copy[at.y][at.x] = 32;
|
||||
}
|
||||
|
||||
for(auto [at, _] : $doors) {
|
||||
wall_copy[at.y][at.x] = 0xd;
|
||||
}
|
||||
|
||||
matrix::dump(msg, wall_copy);
|
||||
|
|
@ -273,7 +276,9 @@ namespace maze {
|
|||
size_t height = matrix::height($walls);
|
||||
|
||||
perimeter(0, 0, width, height, [&](size_t x, size_t y) {
|
||||
$walls[y][x] = 1;
|
||||
$walls[y][x] = WALL_VALUE;
|
||||
Point at{x,y};
|
||||
if($doors.contains(at)) $doors.erase(at);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -303,20 +308,35 @@ namespace maze {
|
|||
|
||||
void Builder::make_doors() {
|
||||
for(auto room : $rooms) {
|
||||
// BUG: still makes rooms without doors, so detect if no door added and brute force one in
|
||||
perimeter(room.x, room.y, room.width, room.height, [&](auto x, auto y) {
|
||||
// walk the outer wall looking for an emergent door
|
||||
int has_door = 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++;
|
||||
}
|
||||
});
|
||||
|
||||
// if only found one then make that an actual door
|
||||
if(has_door == 1) {
|
||||
$doors.insert_or_assign(last_door, true);
|
||||
}
|
||||
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
enclose();
|
||||
}
|
||||
|
||||
void Builder::inner_box(size_t outer_size, size_t inner_size) {
|
||||
|
|
@ -338,10 +358,51 @@ namespace maze {
|
|||
}
|
||||
|
||||
void Builder::add_dead_end(Point at) {
|
||||
// doing this ensures no dupes, if it's !inserted then it already existed
|
||||
auto [_, inserted] = $ends_map.insert_or_assign(at, true);
|
||||
|
||||
// so skip it, it isn't new
|
||||
if(inserted) {
|
||||
$dead_ends.push_back(at);
|
||||
}
|
||||
}
|
||||
|
||||
bool Builder::validate() {
|
||||
size_t width = matrix::width($walls);
|
||||
size_t height = matrix::height($walls);
|
||||
|
||||
// no rooms can overlap
|
||||
for(auto& room : $rooms) {
|
||||
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;
|
||||
|
||||
if($rooms.size() == 1) return true;
|
||||
|
||||
// initial path test can just use one room then look for
|
||||
// any cells that are empty in the walls map but unpathed in the paths
|
||||
Room test_room = $rooms.at(0);
|
||||
Point test{test_room.x, test_room.y};
|
||||
$paths.set_target(test);
|
||||
$paths.compute_paths($walls);
|
||||
$paths.dump("AFTER COMPUTE");
|
||||
$paths.clear_target(test);
|
||||
|
||||
for(matrix::each_cell it{$walls}; it.next();) {
|
||||
if($walls[it.y][it.x] == SPACE_VALUE &&
|
||||
$paths.$paths[it.y][it.x] == WALL_PATH_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,17 @@ namespace maze {
|
|||
std::vector<Point>& $dead_ends;
|
||||
std::unordered_map<Point, bool> $ends_map;
|
||||
Room $no_rooms_region{0,0,0,0};
|
||||
// BUG: instead of bool map it to the room?
|
||||
std::unordered_map<Point, bool> $doors;
|
||||
Pathing $paths;
|
||||
|
||||
Builder(Map& map) :
|
||||
$walls(map.$walls), $rooms(map.$rooms), $dead_ends(map.$dead_ends)
|
||||
$walls(map.$walls), $rooms(map.$rooms), $dead_ends(map.$dead_ends),
|
||||
$paths{matrix::width(map.$walls), matrix::height(map.$walls)}
|
||||
{
|
||||
dbc::check(map.$width % 2 == 1, "map width not an ODD number (perimter dead ends bug)");
|
||||
dbc::check(map.$height % 2 == 1, "map height not an ODD number (perimter dead ends bug)");
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +38,8 @@ namespace maze {
|
|||
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);
|
||||
void add_dead_end(Point at);
|
||||
bool room_should_exist(Room& room);
|
||||
bool room_should_exist(Room& room, bool allow_dupes=false);
|
||||
void make_doors();
|
||||
bool validate();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,3 +132,7 @@ bool Pathing::INVARIANT() {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Pathing::dump(const std::string& msg) {
|
||||
matrix::dump(msg, $paths);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,4 +37,5 @@ public:
|
|||
PathingResult find_path(Point &out, int direction, bool diag);
|
||||
|
||||
bool INVARIANT();
|
||||
void dump(const std::string& msg);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ struct Room {
|
|||
size_t width = 0;
|
||||
size_t height = 0;
|
||||
|
||||
|
||||
bool contains(Point at) {
|
||||
return at.x >= x
|
||||
&& at.x <= x + width -1
|
||||
|
|
@ -41,6 +40,8 @@ struct Room {
|
|||
// other bottom < this top == too far above
|
||||
|| other.y + other.height < y);
|
||||
}
|
||||
|
||||
bool operator==(const Room&) const = default;
|
||||
};
|
||||
|
||||
using EntityGrid = std::unordered_map<Point, wchar_t>;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ TEST_CASE("hunt-and-kill", "[mazes]") {
|
|||
}
|
||||
|
||||
TEST_CASE("hunt-and-kill box", "[mazes]") {
|
||||
for(int i = 5; i < 100; i++) {
|
||||
Map map((i / 2) + 20, (i / 2) + 20);
|
||||
for(int i = 25; i < 65; i += 2) {
|
||||
Map map(i, i);
|
||||
maze::Builder maze(map);
|
||||
|
||||
maze.hunt_and_kill();
|
||||
|
|
@ -83,8 +83,8 @@ TEST_CASE("hunt-and-kill no-dead-ends", "[mazes]") {
|
|||
}
|
||||
|
||||
TEST_CASE("hunt-and-kill too much", "[mazes]") {
|
||||
for(int i = 5; i < 100; i++) {
|
||||
Map map((i / 2) + 20, (i / 2) + 20);
|
||||
for(int i = 25; i < 65; i += 2) {
|
||||
Map map(i, i);
|
||||
maze::Builder maze(map);
|
||||
|
||||
maze.hunt_and_kill();
|
||||
|
|
@ -100,3 +100,23 @@ TEST_CASE("hunt-and-kill too much", "[mazes]") {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("hunt-and-kill validator", "[mazes]") {
|
||||
Map map(33, 33);
|
||||
maze::Builder maze(map);
|
||||
|
||||
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();
|
||||
|
||||
bool valid = maze.validate();
|
||||
|
||||
maze.dump("VALIDATED");
|
||||
|
||||
REQUIRE(valid == true);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue