Now the rooms are better at having good doors.

This commit is contained in:
Zed A. Shaw 2026-03-15 03:20:29 -04:00
parent 908f5bfb3e
commit 8e2a691337
8 changed files with 69 additions and 70 deletions

View file

@ -267,7 +267,7 @@ namespace maze {
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] = $paths.$paths[it.y][it.x];
wall_copy[it.y][it.x] = $pathing.$paths[it.y][it.x];
}
}
}
@ -309,57 +309,47 @@ namespace maze {
void Builder::place_doors() {
for(auto room : $rooms) {
// walk the outer wall looking for an emergent door
int possible_doors = 0;
Point last_door{0,0};
int best_longest = 0;
Point best_door{0,0};
bool door_found = false;
// do an initial pathing check and if it's good then done
int longest = compute_paths(room.x, room.y);
if(longest < WALL_PATH_LIMIT && longest > int($width * 2)) continue;
// can't path out of the room, so now punch a hole until it can
matrix::perimeter it{room.x - 1, room.y - 1, room.width + 2, room.height + 2};
while(it.next()) {
if(space_available(it.x, it.y)) {
// IN_BOUNDS CHECK, or maybe a can_place or is_available?
last_door = {it.x, it.y};
possible_doors++;
// don't use corners
if((it.x == room.x - 1 && it.y == room.y - 1) ||
(it.x == room.x - 1 && it.y == room.y + 2) ||
(it.x == room.width + 2 && it.y == room.y + 2) ||
(it.x == room.width + 2 && it.y == room.y - 1)) {
continue;
}
}
// if only found one then make that an actual door
if(possible_doors == 1) {
$doors.insert_or_assign(last_door, true);
continue;
}
if($walls[it.y][it.x] == WALL_VALUE) {
$walls[it.y][it.x] = SPACE_VALUE;
longest = compute_paths(room.x, room.y);
// 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(!space_available(near_door.x, near_door.y)) 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
// IN_BOUNDS_HERE
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;
}
// keep track of the best door so far, which is the one with the longest path
if(longest != WALL_PATH_LIMIT && longest > best_longest) {
best_longest = longest;
best_door = {it.x, it.y};
}
$walls[it.y][it.x] = WALL_VALUE;
}
dbc::check(best_longest < WALL_PATH_LIMIT, "bad best_longest!");
if(best_longest > int($width * 2)) 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);
}
// should now have a door with the longest path
$walls[best_door.y][best_door.x] = SPACE_VALUE;
$doors.insert_or_assign(best_door, true);
}
fmt::println("map valid after doors? {}", validate());
}
void Builder::inner_box(size_t outer_size, size_t inner_size) {
@ -390,6 +380,15 @@ namespace maze {
}
}
int Builder::compute_paths(size_t x, size_t y) {
Point test{x, y};
$pathing.set_target(test);
int longest = $pathing.compute_paths($walls);
// $pathing.dump("AFTER COMPUTE");
$pathing.clear_target(test);
return longest;
}
bool Builder::validate() {
size_t width = matrix::width($walls);
size_t height = matrix::height($walls);
@ -410,15 +409,11 @@ namespace maze {
// 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(rand(0, $rooms.size() - 1));
Point test{test_room.x, test_room.y};
$paths.set_target(test);
$paths.compute_paths($walls);
// $paths.dump("AFTER COMPUTE");
$paths.clear_target(test);
compute_paths(test_room.x, test_room.y);
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) {
$pathing.$paths[it.y][it.x] == WALL_PATH_LIMIT) {
return false;
}
}
@ -430,8 +425,6 @@ namespace maze {
bool in_bounds = matrix::inbounds($walls, x, y);
bool is_space = $walls[y][x] == SPACE_VALUE;
bool not_perimeter = x > 0 && y > 0 && x < $width - 2 && y < $height - 2;
dbc::check(x != $width, "bad x");
dbc::check(y != $height, "bad y");
return in_bounds && is_space && not_perimeter;
}

View file

@ -16,11 +16,11 @@ namespace maze {
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;
Pathing $pathing;
Builder(Map& map) :
$width(map.$width), $height(map.$height), $walls(map.$walls),
$rooms(map.$rooms), $dead_ends(map.$dead_ends), $paths{$width, $height}
$rooms(map.$rooms), $dead_ends(map.$dead_ends), $pathing{$width, $height}
{
dbc::check($width % 2 == 1, "map width not an ODD number (perimter dead ends bug)");
dbc::check($height % 2 == 1, "map height not an ODD number (perimter dead ends bug)");
@ -46,6 +46,7 @@ namespace maze {
bool repair();
void punch_dead_end(size_t at_x, size_t at_y, size_t x, size_t y);
bool space_available(size_t x, size_t y);
int compute_paths(size_t x, size_t y);
};
std::pair<Builder, bool> script(Map& map, nlohmann::json& config);

View file

@ -14,7 +14,7 @@ inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t
}
}
void Pathing::compute_paths(Matrix &walls) {
int Pathing::compute_paths(Matrix &walls) {
INVARIANT();
dbc::check(walls[0].size() == $width,
$F("Pathing::compute_paths called with walls.width={} but paths $width={}", walls[0].size(), $width));
@ -22,44 +22,46 @@ void Pathing::compute_paths(Matrix &walls) {
dbc::check(walls.size() == $height,
$F("Pathing::compute_paths called with walls.height={} but paths $height={}", walls[0].size(), $height));
// Initialize the new array with every pixel at limit distance
// Initialize the new array with every cell at limit distance
matrix::assign($paths, WALL_PATH_LIMIT);
Matrix closed = walls;
PointList starting_pixels;
PointList open_pixels;
PointList starting_cells;
PointList open_cells;
// First pass: Add starting pixels and put them in closed
// First pass: Add starting cells and put them in closed
for(size_t counter = 0; counter < $height * $width; counter++) {
size_t x = counter % $width;
size_t y = counter / $width;
if($input[y][x] == 0) {
$paths[y][x] = 0;
closed[y][x] = 1;
starting_pixels.emplace_back(x,y);
starting_cells.emplace_back(x,y);
}
}
// Second pass: Add border to open
for(auto sp : starting_pixels) {
add_neighbors(open_pixels, closed, sp.y, sp.x);
for(auto sp : starting_cells) {
add_neighbors(open_cells, closed, sp.y, sp.x);
}
// Third pass: Iterate filling in the open list
int counter = 1; // leave this here so it's available below
for(; counter < WALL_PATH_LIMIT && !open_pixels.empty(); ++counter) {
for(; counter < WALL_PATH_LIMIT && !open_cells.empty(); ++counter) {
PointList next_open;
for(auto sp : open_pixels) {
for(auto sp : open_cells) {
$paths[sp.y][sp.x] = counter;
add_neighbors(next_open, closed, sp.y, sp.x);
}
open_pixels = next_open;
open_cells = next_open;
}
// Last pass: flood last pixels
for(auto sp : open_pixels) {
// Last pass: flood last cells
for(auto sp : open_cells) {
$paths[sp.y][sp.x] = counter;
}
return counter;
}
void Pathing::set_target(const Point &at, int value) {

View file

@ -28,7 +28,7 @@ public:
$input(height, matrix::Row(width, 1))
{}
void compute_paths(Matrix &walls);
int compute_paths(Matrix &walls);
void set_target(const Point &at, int value=0);
void clear_target(const Point &at);
Matrix &paths() { return $paths; }

View file

@ -71,8 +71,8 @@ constexpr int COMBAT_UI_Y = RAY_VIEW_HEIGHT;
constexpr int COMBAT_UI_WIDTH = RAY_VIEW_WIDTH ;
constexpr int COMBAT_UI_HEIGHT = SCREEN_HEIGHT - RAY_VIEW_HEIGHT;
constexpr int INITIAL_MAP_W = 25;
constexpr int INITIAL_MAP_H = 25;
constexpr int INITIAL_MAP_W = 21;
constexpr int INITIAL_MAP_H = 21;
constexpr float DEFAULT_ROTATE=0.25f;

View file

@ -38,7 +38,7 @@ TEST_CASE("camera control", "[map]") {
TEST_CASE("map placement test", "[map-fail]") {
GameDB::init();
for(int i = 0; i < 20; i++) {
for(int i = 0; i < 10; i++) {
auto& level = GameDB::create_level();
for(size_t rnum = 0; rnum < level.map->room_count(); rnum++) {

View file

@ -7,7 +7,7 @@
#include "algos/maze.hpp"
#include "algos/stats.hpp"
#define DUMP 0
#define DUMP 1
using std::string;
using matrix::Matrix;

View file

@ -10,6 +10,7 @@
#include <thread>
#include "algos/rand.hpp"
#include "game/systems.hpp"
#include "constants.hpp"
using namespace fmt;
using namespace nlohmann;
@ -63,7 +64,9 @@ TEST_CASE("dijkstra algo test", "[pathing-old]") {
pathing.$input = test["input"];
REQUIRE(pathing.INVARIANT());
pathing.compute_paths(walls);
int longest = pathing.compute_paths(walls);
REQUIRE(longest > 0);
REQUIRE(longest < WALL_PATH_LIMIT);
REQUIRE(pathing.INVARIANT());