From a0b785cb2a3b0355095a46631df6268226af6b14 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 19 May 2025 01:40:23 -0400 Subject: [PATCH] Hunt-and-kill algorithm rocks. It handles everything I need for map gen, including spawn points, room placement, and the maze like map. --- Makefile | 4 +- gui/fsm.cpp | 2 +- maze.cpp | 175 +++++++++-------------------------------------- maze.hpp | 4 +- raycaster.cpp | 10 +++ tests/mazes.cpp | 16 +---- worldbuilder.cpp | 175 +++-------------------------------------------- worldbuilder.hpp | 8 --- 8 files changed, 60 insertions(+), 334 deletions(-) diff --git a/Makefile b/Makefile index cf97b8f..fc19dfd 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests "[maze-gen]" + ./builddir/runtests "[levelmanager]" run: build test ifeq '$(OS)' 'Windows_NT' @@ -49,7 +49,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[maze-gen]" + gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[levelmanager]" win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/gui/fsm.cpp b/gui/fsm.cpp index 2ab8e7a..f507a0b 100644 --- a/gui/fsm.cpp +++ b/gui/fsm.cpp @@ -354,7 +354,7 @@ namespace gui { if($map_open) { $map_ui.render($window, $main_ui.$compass_dir); } else { - $mini_map.render($window, $main_ui.$compass_dir); + // $mini_map.render($window, $main_ui.$compass_dir); } } } diff --git a/maze.cpp b/maze.cpp index 0711e57..630c45c 100644 --- a/maze.cpp +++ b/maze.cpp @@ -18,134 +18,7 @@ inline size_t rand(size_t i, size_t j) { } -inline bool split_dir(size_t iDim, size_t jDim) { - if(iDim < jDim) { - return false; - } else if(jDim < iDim) { - return true; - } else { - return Random::uniform(0, 1); - } -} - -inline bool good_hole(Matrix &map, size_t split, size_t hole, bool horiz) { - if(hole % 2 == 0) return false; - - size_t j = horiz ? split : hole; - size_t i = horiz ? hole : split; - if(map[j][i] == WALL_PATH_LIMIT) return false; - - return true; -} - -void divide(Matrix& map, std::vector &rooms, - Point iCoords, Point jCoords, bool horizontal) { - int iDim = iCoords.y - iCoords.x; - int jDim = jCoords.y - jCoords.x; - bool punch_room = false; - - if(iDim <= 0 || jDim <= 0) { - return; - } else if(iDim <= 2 && jDim <= 2) { - fmt::println("MADE ROOM! {},{}; {},{}", - iCoords.x, iCoords.y, jCoords.x, jCoords.y); - punch_room = true; - } - - if(horizontal) { - size_t split = 0; - do { - split = rand(iCoords.x, iCoords.x + iDim + 1); - } while(split % 2); - - size_t hole = 0; - do { - hole = rand(jCoords.x, jCoords.x + jDim +1); - } while(good_hole(map, split, hole, horizontal)); - - for(size_t j = jCoords.x; j <= jCoords.y; j++) { - if(j != hole) { - map[split][j] = WALL_PATH_LIMIT; - } - } - - divide(map, rooms, - {iCoords.x, size_t(split - 1)}, - jCoords, - split_dir(split - iCoords.x - 1, jDim)); - - divide(map, rooms, - {size_t(split + 1), iCoords.y}, - jCoords, - split_dir(iCoords.x - split - 1, jDim)); - } else { - size_t split = 0; - do { - split = rand(jCoords.x, jCoords.x + jDim + 1); - } while(split % 2); - - size_t hole = 0; - do { - hole = rand(iCoords.x, iCoords.x + iDim + 1); - } while(good_hole(map, split, hole, horizontal)); - - for(size_t i = iCoords.x; i <= iCoords.y; i++) { - if(i != hole) { - map[i][split] = WALL_PATH_LIMIT; - } - } - - divide(map, rooms, - iCoords, - {jCoords.x, size_t(split - 1)}, - split_dir(iDim, split - jCoords.x - 1)); - - divide(map, rooms, - iCoords, - {size_t(split + 1), jCoords.y}, - Random::uniform(0, 1)); - } - - if(punch_room) { - // for(size_t j = jCoords.x; j <= jCoords.y; j++) { - // for(size_t i = iCoords.x; i <= iCoords.y; i++) { - // map[j][i] = 0; - // } - // } - - Room room{iCoords.x, jCoords.x, iCoords.y - iCoords.x + 1, jCoords.y - jCoords.x + 1}; - - for(auto r : rooms) { - if(r.x == room.x && r.y == room.y) { - return; - } - } - - rooms.push_back(room); - } -} - - -void maze::recursive_div(Matrix& map, std::vector& rooms) { - size_t width = matrix::width(map); - size_t height = matrix::height(map); - - for(size_t i = 0; i < height; i++) { - for(size_t j = 0; j < width; j++) { - int val = (i == 0 || - j == 0 || - i == height - 1 || - j == width - 1); - - map[i][j] = val == 1 ? WALL_PATH_LIMIT : 0; - } - } - - divide(map, rooms, {1, height - 2}, {1, width - 2}, split_dir(1, 1)); -} - - -bool complete(Matrix& maze) { +inline bool complete(Matrix& maze) { size_t width = matrix::width(maze); size_t height = matrix::height(maze); @@ -190,7 +63,7 @@ std::vector neighbors(Matrix& maze, Point on) { for(auto point : points) { if(matrix::inbounds(maze, point.x, point.y)) { - if(maze[point.y][point.x] == WALL_PATH_LIMIT) { + if(maze[point.y][point.x] == WALL_VALUE) { result.push_back(point); } } @@ -199,13 +72,13 @@ std::vector neighbors(Matrix& maze, Point on) { return result; } -std::pair findCoord(Matrix& maze) { +inline std::pair find_coord(Matrix& maze) { size_t width = matrix::width(maze); size_t height = matrix::height(maze); for(size_t y = 1; y < height; y += 2) { for(size_t x = 1; x < width; x += 2) { - if(maze[y][x] == WALL_PATH_LIMIT) { + if(maze[y][x] == WALL_VALUE) { auto found = neighborsAB(maze, {x, y}); for(auto point : found) { @@ -221,34 +94,35 @@ std::pair findCoord(Matrix& maze) { dbc::sentinel("failed to find coord?"); } -void maze::hunt_and_kill(Matrix& maze, std::vector& rooms) { - size_t width = matrix::width(maze); - size_t height = matrix::height(maze); - matrix::assign(maze, WALL_PATH_LIMIT); +void maze::hunt_and_kill(Matrix& maze, std::vector& rooms, std::vector& dead_ends) { + matrix::assign(maze, WALL_VALUE); - Room start{2, 2, 3, 3}; - rooms.push_back(start); - Room goal{width-4, height-4, 3, 3}; - rooms.push_back(goal); + Point last_even{0,0}; for(auto& room : rooms) { - for(matrix::box it{maze, room.x, room.y, 1}; it.next();) { + if(room.x % 2 == 0 && room.y % 2 == 0) { + last_even = {room.x, room.y}; + } + + for(matrix::box it{maze, room.x, room.y, room.width}; it.next();) { maze[it.y][it.x] = 0; } } Point on{1,1}; + while(!complete(maze)) { auto n = neighbors(maze, on); if(n.size() == 0) { - auto t = findCoord(maze); + dead_ends.push_back(on); + auto t = find_coord(maze); on = t.first; maze[on.y][on.x] = 0; size_t row = (on.y + t.second.y) / 2; size_t col = (on.x + t.second.x) / 2; maze[row][col] = 0; } else { - auto nb = n[Random::uniform(size_t(0), n.size() - 1)]; + auto nb = n[rand(size_t(0), n.size() - 1)]; maze[nb.y][nb.x] = 0; size_t row = (nb.y + on.y) / 2; @@ -257,4 +131,21 @@ void maze::hunt_and_kill(Matrix& maze, std::vector& rooms) { on = nb; } } + + for(auto at : dead_ends) { + for(auto& room : rooms) { + Point room_ul{room.x - room.width - 1, room.y - room.height - 1}; + Point room_lr{room.x + room.width - 1, room.y + room.height - 1}; + if(at.x >= room_ul.x && at.y >= room_ul.y && + at.x <= room_lr.x && at.y <= room_lr.y) + { + for(matrix::compass it{maze, at.x, at.y}; it.next();) { + if(maze[it.y][it.x] == 1) { + maze[it.y][it.x] = 0; + break; + } + } + } + } + } } diff --git a/maze.hpp b/maze.hpp index 6fdce07..bf4f3c7 100644 --- a/maze.hpp +++ b/maze.hpp @@ -3,7 +3,5 @@ #include "map.hpp" namespace maze { - void recursive_div(matrix::Matrix& map, std::vector& rooms); - - void hunt_and_kill(Matrix& maze, std::vector& rooms); + void hunt_and_kill(Matrix& maze, std::vector& rooms, std::vector& dead_ends); } diff --git a/raycaster.cpp b/raycaster.cpp index c6d844f..aa02b87 100644 --- a/raycaster.cpp +++ b/raycaster.cpp @@ -25,6 +25,16 @@ union ColorConv { uint32_t as_int; }; +inline uint32_t old_lighting(uint32_t pixel, float dist, int level) { + (void)level; + ColorConv conv{.as_int=pixel}; + conv.as_color.r /= dist; + conv.as_color.g /= dist; + conv.as_color.b /= dist; + + return conv.as_int; +} + /* It's hard to believe, but this is faster than any bitfiddling * I could devise. Just use a union with a struct, do the math * and I guess the compiler can handle it better than shifting diff --git a/tests/mazes.cpp b/tests/mazes.cpp index 8332409..df5314d 100644 --- a/tests/mazes.cpp +++ b/tests/mazes.cpp @@ -10,24 +10,12 @@ using std::string; using matrix::Matrix; -TEST_CASE("recursive division (garbage)", "[mazes]") { - auto map = matrix::make(21, 21); - std::vector rooms; - - maze::recursive_div(map, rooms); - matrix::dump("MAZE?", map); - - for(auto& room : rooms) { - fmt::println("room: {},{}; {},{}", - room.x, room.y, room.width, room.height); - } -} - TEST_CASE("hunt-and-kill", "[maze-gen]") { auto map = matrix::make(21, 21); std::vector rooms; + std::vector dead_ends; - maze::hunt_and_kill(map, rooms); + maze::hunt_and_kill(map, rooms, dead_ends); matrix::dump("MAZE?", map); for(auto& room : rooms) { diff --git a/worldbuilder.cpp b/worldbuilder.cpp index 1bdd275..6a3abbe 100644 --- a/worldbuilder.cpp +++ b/worldbuilder.cpp @@ -18,11 +18,6 @@ inline int make_split(Room &cur, bool horiz) { return Random::uniform(min, max); } -void WorldBuilder::set_door(Room &room, int value) { - $map.$walls[room.entry.y][room.entry.x] = value; - $map.$walls[room.exit.y][room.exit.x] = value; -} - void rand_side(Room &room, Point &door) { dbc::check(int(room.width) > 0 && int(room.height) > 0, "Weird room with 0 for height or width."); int rand_x = Random::uniform(0, room.width - 1); @@ -55,101 +50,22 @@ void WorldBuilder::add_door(Room &room) { rand_side(room, room.exit); } -void WorldBuilder::partition_map(Room &cur, int depth) { - if(cur.width >= 3 && cur.width <= 6 && - cur.height >= 3 && cur.height <= 6) - { - $map.add_room(cur); - return; - } - - bool horiz = cur.width > cur.height ? false : true; - int split = make_split(cur, horiz); - if(split <= 0) return; // end recursion - - Room left = cur; - Room right = cur; - - if(horiz) { - if(split >= int(cur.height)) return; // end recursion - - left.height = size_t(split - 1); - right.y = cur.y + split; - right.height = size_t(cur.height - split); - } else { - if(split >= int(cur.width)) return; // end recursion - - left.width = size_t(split-1); - right.x = cur.x + split, - right.width = size_t(cur.width - split); - } - - // BUG: min room size should be configurable - if(depth > 0 && left.width > 2 && left.height > 2) { - partition_map(left, depth-1); +void WorldBuilder::generate_map() { + std::vector dead_ends; + maze::hunt_and_kill($map.$walls, $map.$rooms, dead_ends); + + for(auto at : dead_ends) { + if(Random::uniform(0,1)) { + Room cur{at.x, at.y, 2, 2}; + add_door(cur); + $map.add_room(cur); + } } - // BUG: min room size should be configurable - if(depth > 0 && right.width > 2 && right.height > 2) { - partition_map(right, depth-1); - } -} + maze::hunt_and_kill($map.$walls, $map.$rooms, dead_ends); -void WorldBuilder::update_door(Point &at, int wall_or_space) { - $map.$walls[at.y][at.x] = wall_or_space; -} - - -void WorldBuilder::stylize_room(int room, string tile_name, float size) { - Point pos_out; - bool placed = $map.place_entity(room, pos_out); - dbc::check(placed, "failed to place style in room"); - (void)tile_name; - (void)size; - - //tile_name = tile_name == "FLOOR_TILE" ? "WALL_PLAIN" : tile_name; - - //for(matrix::circle it{$map.$walls, pos_out, size}; it.next();) { - // for(int x = it.left; x < it.right; x++) { - // if($map.iswall(x, it.y)) { - // // a wall tile - // $map.$tiles.set_tile(x, it.y, tile_name); - // } else { - // // a floor tile - // $map.$tiles.set_tile(x, it.y, "FLOOR_TILE"); - // } - // } - //} -} - -void WorldBuilder::generate_rooms() { - Room root{ - .x = 0, - .y = 0, - .width = $map.$width, - .height = $map.$height - }; - // BUG: depth should be configurable - partition_map(root, 10); - place_rooms(); - - dbc::check($map.room_count() > 0, "map generated zero rooms, map too small?"); -} - -void WorldBuilder::generate_map() { - maze::hunt_and_kill($map.$walls, $map.$rooms); $map.expand(); $map.load_tiles(); - - // get only the tiles with no collision to fill rooms - auto room_types = $map.$tiles.tile_names(false); - - for(size_t i = 0; i < $map.$rooms.size() - 1; i++) { - size_t room_type = Random::uniform(0, room_types.size() - 1); - int room_size = Random::uniform(100, 800); - string tile_name = room_types[room_type]; - stylize_room(i, tile_name, room_size * 0.01f); - } } @@ -260,72 +176,3 @@ void WorldBuilder::generate(DinkyECS::World &world) { generate_map(); place_entities(world); } - -void WorldBuilder::make_room(size_t origin_x, size_t origin_y, size_t w, size_t h) { - $map.INVARIANT(); - dbc::pre("y out of bounds", origin_y + h < $map.$height); - dbc::pre("x out of bounds", origin_x + w < $map.$width); - - for(size_t y = origin_y; y < origin_y + h; ++y) { - for(size_t x = origin_x; x < origin_x + w; ++x) { - $map.$walls[y][x] = INV_SPACE; - } - } -} - - -void WorldBuilder::place_rooms() { - for(auto &cur : $map.$rooms) { - add_door(cur); - make_room(cur.x, cur.y, cur.width, cur.height); - } -} - -inline bool random_path(Map &map, PointList &holes, Point src, Point target) { - bool keep_going = false; - bool target_found = false; - int count = 0; - map.set_target(target); - map.make_paths(); - Matrix &paths = map.paths(); - - Point out{src.x, src.y}; - do { - keep_going = map.neighbors(out, true); - holes.push_back(out); - target_found = paths[out.y][out.x] == 0; - } while(!target_found && keep_going && ++count < WORLDBUILD_MAX_PATH); - - map.INVARIANT(); - map.clear_target(target); - - return target_found; -} - -inline void straight_path(Map &map, PointList &holes, Point src, Point target) { - for(matrix::line dig{src, target}; dig.next();) { - holes.emplace_back(size_t(dig.x), size_t(dig.y)); - Point expand{(size_t)dig.x+1, (size_t)dig.y}; - - if(map.inmap(expand.x, expand.y)) { - // BUG? should really just move doors away from the edge - holes.push_back(expand); - } - } -} - -void WorldBuilder::tunnel_doors(PointList &holes, Room &src, Room &target) { - int path_type = Random::uniform(0, 10); - - switch(path_type) { - case 0: - // then do the rest as random with fallback - if(!random_path($map, holes, src.exit, target.entry)) { - straight_path($map, holes, src.exit, target.entry); - } - break; - default: - // for now do 25% as simple straight paths - straight_path($map, holes, src.exit, target.entry); - } -} diff --git a/worldbuilder.hpp b/worldbuilder.hpp index b55440e..d8d0356 100644 --- a/worldbuilder.hpp +++ b/worldbuilder.hpp @@ -14,15 +14,7 @@ class WorldBuilder { $components(components) { } - void partition_map(Room &cur, int depth); - void make_room(size_t origin_y, size_t origin_x, size_t width, size_t height); void add_door(Room &room); - void set_door(Room &room, int value); - void place_rooms(); - void tunnel_doors(PointList &holes, Room &src, Room &target); - void update_door(Point &at, int wall_or_space); - void stylize_room(int room, std::string tile_name, float size); - void generate_rooms(); void generate_map(); DinkyECS::Entity configure_entity_in_map(DinkyECS::World &world, nlohmann::json &entity_data, int in_room);