Improvements from the Go version of the mazes.

This commit is contained in:
Zed A. Shaw 2026-03-24 12:29:06 -04:00
parent fee925505f
commit b62cb8fe92
2 changed files with 173 additions and 160 deletions

View file

@ -12,14 +12,14 @@ namespace maze {
inline bool complete(Matrix& maze, size_t width, size_t height) { inline bool complete(Matrix& maze, size_t width, size_t height) {
for(size_t row = 1; row < height; row += 2) { for(size_t row = 1; row < height; row += 2) {
for(size_t col = 1; col < width; col += 2) { for(size_t col = 1; col < width; col += 2) {
if(maze[row][col] != 0) return false; if(maze[row][col] != SPACE_VALUE) return false;
} }
} }
return true; return true;
} }
std::vector<Point> neighborsAB(Matrix& maze, Point on) { inline std::vector<Point> neighbors(Matrix& maze, Point on) {
std::vector<Point> result; std::vector<Point> result;
std::array<Point, 4> points{{ std::array<Point, 4> points{{
@ -38,44 +38,74 @@ namespace maze {
return result; return result;
} }
std::vector<Point> neighbors(Matrix& maze, Point on) { inline std::vector<Point> neighbor_walls(Matrix& maze, Point on) {
auto near_me = neighbors(maze, on);
std::vector<Point> result; std::vector<Point> result;
std::array<Point, 4> points{{ for(auto point : near_me) {
{on.x, on.y - 2}, if(maze[point.y][point.x] == WALL_VALUE) {
{on.x, on.y + 2}, result.push_back(point);
{on.x - 2, on.y},
{on.x + 2, on.y}
}};
for(auto point : points) {
if(matrix::inbounds(maze, point.x, point.y)) {
if(maze[point.y][point.x] == WALL_VALUE) {
result.push_back(point);
}
} }
} }
return result; return result;
} }
inline std::pair<Point, Point> find_coord(Matrix& maze, size_t width, size_t height) { bool Builder::hunt_next(Point& on, Point& found) {
for(size_t y = 1; y < height; y += 2) { for(size_t y = 1; y < $height; y += 2) {
for(size_t x = 1; x < width; x += 2) { for(size_t x = 1; x < $width; x += 2) {
if(maze[y][x] == WALL_VALUE) { if($walls[y][x] != WALL_VALUE) {
auto found = neighborsAB(maze, {x, y}); continue;
}
auto near_me = neighbors($walls, {x, y});
for(auto point : found) { for(auto point : near_me) {
if(maze[point.y][point.x] == 0) { if($walls[point.y][point.x] == SPACE_VALUE) {
return {{x, y}, point}; on = {x, y};
} found = point;
return true;
} }
} }
} }
} }
matrix::dump("BAD MAZE", maze); return false;
dbc::sentinel("failed to find coord?"); }
Point Builder::hak_step(Point from, Point to) {
$walls[from.y][from.x] = SPACE_VALUE;
size_t row = (from.y + to.y) / 2;
size_t col = (from.x + to.x) / 2;
$walls[row][col] = SPACE_VALUE;
return from;
}
void Builder::hunt_and_kill(Point on) {
if($rooms.size() > 0) place_rooms();
Point found{1, 1};
while(true) {
auto n = neighbor_walls($walls, on);
if(n.size() == 0) {
// no neighbors, must be dead end
add_dead_end(on);
if(!hunt_next(on, found)) {
break;
}
on = hak_step(on, found);
} else {
// found neighbors, pick random one
auto nb = n[Random::abs(size_t(0), n.size() - 1)];
on = hak_step(nb, on);
}
}
if($rooms.size() > 0) place_rooms();
} }
bool Builder::room_should_exist(Room& room, bool allow_dupes) { bool Builder::room_should_exist(Room& room, bool allow_dupes) {
@ -136,7 +166,6 @@ namespace maze {
size_t offset = Random::uniform(0, 3); size_t offset = Random::uniform(0, 3);
// BUG: this still accidentally merges rooms
for(size_t i = 0; i < starts.size(); i++) { for(size_t i = 0; i < starts.size(); i++) {
size_t index = (i + offset) % starts.size(); size_t index = (i + offset) % starts.size();
Room cur{starts[index].x, starts[index].y, room_size, room_size}; Room cur{starts[index].x, starts[index].y, room_size, room_size};
@ -154,43 +183,6 @@ namespace maze {
matrix::assign($walls, WALL_VALUE); matrix::assign($walls, WALL_VALUE);
} }
void Builder::divide(Point start, Point end) {
for(matrix::line it{start, end}; it.next();) {
$walls[it.y][it.x] = 0;
$walls[it.y+1][it.x] = 0;
}
}
void Builder::hunt_and_kill(Point on) {
if($rooms.size() > 0) place_rooms();
while(!complete($walls, $width, $height)) {
auto n = neighbors($walls, on);
if(n.size() == 0) {
// no neighbors, must be dead end
add_dead_end(on);
auto t = find_coord($walls, $width, $height);
on = t.first;
$walls[on.y][on.x] = 0;
size_t row = (on.y + t.second.y) / 2;
size_t col = (on.x + t.second.x) / 2;
$walls[row][col] = 0;
} else {
// found neighbors, pick random one
auto nb = n[Random::abs(size_t(0), n.size() - 1)];
$walls[nb.y][nb.x] = 0;
size_t row = (nb.y + on.y) / 2;
size_t col = (nb.x + on.x) / 2;
$walls[row][col] = 0;
on = nb;
}
}
if($rooms.size() > 0) place_rooms();
}
void Builder::place_rooms() { void Builder::place_rooms() {
for(auto& room : $rooms) { for(auto& room : $rooms) {
for(matrix::rectangle it{$walls, room.x, room.y, room.width, room.height}; it.next();) { for(matrix::rectangle it{$walls, room.x, room.y, room.width, room.height}; it.next();) {
@ -199,24 +191,6 @@ namespace maze {
} }
} }
void Builder::inner_donut(float outer_rad, float inner_rad) {
size_t x = $width / 2;
size_t y = $height / 2;
for(matrix::circle it{$walls, {x, y}, outer_rad}; it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 0;
}
}
for(matrix::circle it{$walls, {x, y}, inner_rad}; it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 1;
}
}
}
void Builder::remove_dead_ends() { void Builder::remove_dead_ends() {
dbc::check($dead_ends.size() > 0, "you have to run an algo first, no dead_ends to remove"); dbc::check($dead_ends.size() > 0, "you have to run an algo first, no dead_ends to remove");
@ -231,42 +205,6 @@ namespace maze {
} }
} }
void Builder::dump(const std::string& msg, bool path_too) {
Matrix wall_copy = $walls;
// mark the rooms too, but not if pathing
if(!path_too) {
for(auto& room : $rooms) {
for(matrix::rectangle it{wall_copy, room.x, room.y, room.width, room.height};
it.next();)
{
if(wall_copy[it.y][it.x] == 0) {
wall_copy[it.y][it.x] = ROOM_SPACE_VALUE;
}
}
}
}
// 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] = DEAD_END_VALUE;
}
for(auto [at, _] : $doors) {
wall_copy[at.y][at.x] = DOOR_VALUE;
}
if(path_too) {
for(matrix::each_cell it{wall_copy}; it.next();) {
if(wall_copy[it.y][it.x] == SPACE_VALUE) {
wall_copy[it.y][it.x] = $pathing.$paths[it.y][it.x];
}
}
}
matrix::dump(msg, wall_copy);
}
void Builder::enclose() { void Builder::enclose() {
for(matrix::perimeter it{0, 0, $width, $height}; it.next();) { for(matrix::perimeter it{0, 0, $width, $height}; it.next();) {
@ -276,30 +214,6 @@ namespace maze {
} }
} }
void Builder::open_box(size_t outer_size) {
size_t center_x = $width / 2;
size_t center_y = $height / 2;
// compensate for the box's border now
outer_size++;
// 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;
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;
}
}
}
}
bool Builder::valid_door(size_t x, size_t y) { bool Builder::valid_door(size_t x, size_t y) {
return (space_available(x, y - 1) && space_available(x, y + 1)) // north south return (space_available(x, y - 1) && space_available(x, y + 1)) // north south
|| (space_available(x - 1, y) && space_available(x + 1, y)); || (space_available(x - 1, y) && space_available(x + 1, y));
@ -345,24 +259,6 @@ namespace maze {
} }
} }
void Builder::inner_box(size_t outer_size, size_t inner_size) {
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;
}
for(matrix::box it{$walls, x, y, inner_size}; it.next();)
{
$walls[it.y][it.x] = 1;
}
// make a fake room that blocks others
$no_rooms_region = {x - outer_size, y - outer_size, outer_size * 2 + 1, outer_size * 2 + 1};
}
void Builder::add_dead_end(Point at) { void Builder::add_dead_end(Point at) {
// doing this ensures no dupes, if it's !inserted then it already existed // doing this ensures no dupes, if it's !inserted then it already existed
auto [_, inserted] = $ends_map.insert_or_assign(at, true); auto [_, inserted] = $ends_map.insert_or_assign(at, true);
@ -466,6 +362,84 @@ namespace maze {
return validate(); return validate();
} }
/// scripting and shapes
void Builder::inner_box(size_t outer_size, size_t inner_size) {
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;
}
for(matrix::box it{$walls, x, y, inner_size}; it.next();)
{
$walls[it.y][it.x] = 1;
}
// make a fake room that blocks others
$no_rooms_region = {x - outer_size, y - outer_size, outer_size * 2 + 1, outer_size * 2 + 1};
}
void Builder::open_box(size_t outer_size) {
size_t center_x = $width / 2;
size_t center_y = $height / 2;
// compensate for the box's border now
outer_size++;
// 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;
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::divide(Point start, Point end) {
for(matrix::line it{start, end}; it.next();) {
$walls[it.y][it.x] = 0;
$walls[it.y+1][it.x] = 0;
}
}
void Builder::inner_donut(float outer_rad, float inner_rad) {
size_t x = $width / 2;
size_t y = $height / 2;
for(matrix::circle it{$walls, {x, y}, outer_rad}; it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 0;
}
}
for(matrix::circle it{$walls, {x, y}, inner_rad}; it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 1;
}
}
}
std::pair<Builder, bool> script(Map& map, nlohmann::json& config) { std::pair<Builder, bool> script(Map& map, nlohmann::json& config) {
maze::Builder maze(map); maze::Builder maze(map);
@ -506,4 +480,41 @@ namespace maze {
return {maze, valid}; return {maze, valid};
} }
void Builder::dump(const std::string& msg, bool path_too) {
Matrix wall_copy = $walls;
// mark the rooms too, but not if pathing
if(!path_too) {
for(auto& room : $rooms) {
for(matrix::rectangle it{wall_copy, room.x, room.y, room.width, room.height};
it.next();)
{
if(wall_copy[it.y][it.x] == 0) {
wall_copy[it.y][it.x] = ROOM_SPACE_VALUE;
}
}
}
}
// 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] = DEAD_END_VALUE;
}
for(auto [at, _] : $doors) {
wall_copy[at.y][at.x] = DOOR_VALUE;
}
if(path_too) {
for(matrix::each_cell it{wall_copy}; it.next();) {
if(wall_copy[it.y][it.x] == SPACE_VALUE) {
wall_copy[it.y][it.x] = $pathing.$paths[it.y][it.x];
}
}
}
matrix::dump(msg, wall_copy);
}
} }

View file

@ -30,6 +30,8 @@ namespace maze {
void clear(); void clear();
void hunt_and_kill(Point on={1,1}); void hunt_and_kill(Point on={1,1});
bool hunt_next(Point& on, Point& found);
Point hak_step(Point from, Point to);
void place_rooms(); void place_rooms();
void enclose(); void enclose();
void randomize_rooms(size_t room_size); void randomize_rooms(size_t room_size);