diff --git a/src/algos/maze.cpp b/src/algos/maze.cpp index 71969bf..d885f42 100644 --- a/src/algos/maze.cpp +++ b/src/algos/maze.cpp @@ -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; } diff --git a/src/algos/maze.hpp b/src/algos/maze.hpp index 247a564..c3fbbaf 100644 --- a/src/algos/maze.hpp +++ b/src/algos/maze.hpp @@ -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 $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 script(Map& map, nlohmann::json& config); diff --git a/src/algos/pathing.cpp b/src/algos/pathing.cpp index 57d3f69..6429e1f 100644 --- a/src/algos/pathing.cpp +++ b/src/algos/pathing.cpp @@ -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) { diff --git a/src/algos/pathing.hpp b/src/algos/pathing.hpp index d069b28..e5eebf2 100644 --- a/src/algos/pathing.hpp +++ b/src/algos/pathing.hpp @@ -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; } diff --git a/src/constants.hpp b/src/constants.hpp index b52b65c..a69d841 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -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; diff --git a/tests/map.cpp b/tests/map.cpp index b6b1cac..f8e0009 100644 --- a/tests/map.cpp +++ b/tests/map.cpp @@ -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++) { diff --git a/tests/mazes.cpp b/tests/mazes.cpp index d66a71b..8e855a1 100644 --- a/tests/mazes.cpp +++ b/tests/mazes.cpp @@ -7,7 +7,7 @@ #include "algos/maze.hpp" #include "algos/stats.hpp" -#define DUMP 0 +#define DUMP 1 using std::string; using matrix::Matrix; diff --git a/tests/pathing.cpp b/tests/pathing.cpp index 1052676..53d29e0 100644 --- a/tests/pathing.cpp +++ b/tests/pathing.cpp @@ -10,6 +10,7 @@ #include #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());