diff --git a/main.cpp b/main.cpp index 93c0f16..70a10cf 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,7 @@ #include "render.hpp" #include "save.hpp" #include "lights.hpp" +#include "worldbuilder.hpp" #include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor #include #include @@ -81,7 +82,8 @@ int main(int argc, char *argv[]) { fs::path save_path{argv[1]}; save::from_file(save_path, world, game_map); } else { - game_map.generate(); + WorldBuilder builder(game_map); + builder.generate(); configure_world(world, game_map); } diff --git a/map.cpp b/map.cpp index 08b04fb..5a34da2 100644 --- a/map.cpp +++ b/map.cpp @@ -3,7 +3,6 @@ #include #include #include -#include "rand.hpp" #include using std::vector, std::pair; @@ -19,11 +18,6 @@ void dump_map(const std::string &msg, Matrix &map) { } } -/** - * This will create an _inverted_ map that you - * can run make_rooms and generate on. It will - * NOT be valid until you actually run generate. - */ Map::Map(size_t width, size_t height) : $limit(1000), $width(width), @@ -36,72 +30,54 @@ void Map::make_paths() { $paths.compute_paths($walls); } -void Map::make_room(size_t origin_x, size_t origin_y, size_t w, size_t h) { - INVARIANT(); - dbc::pre("y out of bounds", origin_y + h < $height); - dbc::pre("x out of bounds", origin_x + w < $width); - - for(size_t y = origin_y; y < origin_y + h; ++y) { - for(size_t x = origin_x; x < origin_x + w; ++x) { - $walls[y][x] = INV_SPACE; - } - } +bool Map::inmap(size_t x, size_t y) { + return x < $width && y < $height; } -inline int make_split(Room &cur, bool horiz) { - size_t dimension = horiz ? cur.height : cur.width; - int min = dimension / 4; - int max = dimension - min; - return Random::uniform(min, max); +void Map::set_target(const Point &at, int value) { + $paths.set_target(at, value); } -void Map::partition_map(Room &cur, int depth) { - if(cur.width >= 5 && cur.width <= 10 && - cur.height >= 5 && cur.height <= 10) { - $rooms.push_back(cur); - return; - } +void Map::clear_target(const Point &at) { + $paths.clear_target(at); +} - bool horiz = cur.width > cur.height ? false : true; - int split = make_split(cur, horiz); - Room left = cur; - Room right = cur; +Point Map::place_entity(size_t room_index) { + dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms"); - if(horiz) { - dbc::check(split > 0, "split is not > 0"); - dbc::check(split < int(cur.height), "split is too big!"); - left.height = size_t(split - 1); - right.y = cur.y + split; - right.height = size_t(cur.height - split); - } else { - dbc::check(split > 0, "split is not > 0"); - dbc::check(split < int(cur.width), "split is too big!"); + Room &start = $rooms[room_index]; + return {start.x+1, start.y+1}; +} - left.width = size_t(split-1); - right.x = cur.x + split, - right.width = size_t(cur.width - split); - } +bool Map::iswall(size_t x, size_t y) { + return $walls[y][x] == WALL_VALUE; +} - if(depth > 0 && left.width > 5 && left.height > 5) { - partition_map(left, depth-1); - } +void Map::dump() { + dump_map("WALLS", $walls); +} - if(depth > 0 && right.width > 5 && right.height > 5) { - partition_map(right, depth-1); - } +bool Map::can_move(Point move_to) { + return inmap(move_to.x, move_to.y) && + !iswall(move_to.x, move_to.y); } -void Map::place_rooms(Room &cur) { - for(auto &cur : $rooms) { - cur.x += 2; - cur.y += 2; - cur.width -= 4; - cur.height -= 4; - add_door(cur); - set_door(cur, INV_SPACE); - make_room(cur.x, cur.y, cur.width, cur.height); - } +Point Map::map_to_camera(const Point &loc, const Point &cam_orig) { + return {loc.x - cam_orig.x, loc.y - cam_orig.y}; +} + +Point Map::center_camera(const Point &around, size_t view_x, size_t view_y) { + int high_x = int(width() - view_x); + int high_y = int(height() - view_y); + int center_x = int(around.x - view_x / 2); + int center_y = int(around.y - view_y / 2); + + // BUG: is clamp really the best thing here? this seems wrong. + size_t start_x = high_x > 0 ? std::clamp(center_x, 0, high_x) : 0; + size_t start_y = high_y > 0 ? std::clamp(center_y, 0, high_y) : 0; + + return {start_x, start_y}; } bool Map::neighbors(Point &out, bool greater) { @@ -142,159 +118,6 @@ bool Map::neighbors(Point &out, bool greater) { } } -bool Map::inmap(size_t x, size_t y) { - return x < $width && y < $height; -} - -void Map::set_door(Room &room, int value) { - $walls[room.entry.y][room.entry.x] = value; - $walls[room.exit.y][room.exit.x] = value; -} - -void rand_side(Room &room, Point &door) { - int rand_x = Random::uniform(0, room.width - 1); - int rand_y = Random::uniform(0, room.height - 1); - - switch(Random::uniform(0,3)) { - case 0: // north - door.x = room.x + rand_x; - door.y = room.y-1; - break; - case 1: // south - door.x = room.x + rand_x; - door.y = room.y + room.height; - break; - case 2: // east - door.x = room.x + room.width; - door.y = room.y + rand_y; - break; - case 3: // west - door.x = room.x - 1; - door.y = room.y + rand_y; - break; - default: - dbc::sentinel("impossible side"); - } -} - -void Map::add_door(Room &room) { - rand_side(room, room.entry); - rand_side(room, room.exit); -} - -bool Map::walk(Point &src, Point &target) { - Matrix &paths = $paths.$paths; - // this sets the target for the path - dbc::check($paths.$input[target.y][target.x] == 0, "target point not set to 0"); - - $walls[src.y][src.x] = INV_WALL; - $walls[target.y][target.x] = INV_WALL; - - // for the walk this needs to be walls since it's inverted? - dbc::check($walls[src.y][src.x] == INV_WALL, - "src room has a wall at exit door"); - dbc::check($walls[target.y][target.x] == INV_WALL, - "target room has a wall at entry door"); - - make_paths(); - bool found = false; - Point out{src.x, src.y}; - int count = 0; - - do { - $walls[out.y][out.x] = INV_SPACE; - found = neighbors(out, true); - - if(paths[out.y][out.x] == 0) { - $walls[out.y][out.x] = INV_SPACE; - return true; - } - } while(found && out.x > 0 && out.y > 0 && ++count < 100); - - return false; -} - -void Map::set_target(const Point &at, int value) { - $paths.set_target(at, value); -} - -void Map::clear_target(const Point &at) { - $paths.clear_target(at); -} - -Point Map::place_entity(size_t room_index) { - dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms"); - - Room &start = $rooms[room_index]; - return {start.x+1, start.y+1}; -} - -void Map::generate() { - Room root{ - .x = 0, - .y = 0, - .width = $width, - .height = $height - }; - - partition_map(root, 10); - place_rooms(root); - - for(size_t i = 0; i < $rooms.size() - 1; i++) { - Room &src = $rooms[i]; - Room &target = $rooms[i+1]; - set_target(target.entry); - bool found = walk(src.exit, target.entry); - if(!found) { - println("ROOM NOT FOUND!"); - } - clear_target(target.entry); - } - - Room &src = $rooms.back(); - Room &target = $rooms.front(); - - set_target(target.entry); - walk(src.exit, target.entry); - clear_target(target.entry); - - for(size_t y = 0; y < $height; ++y) { - for(size_t x = 0; x < $width; ++x) { - $walls[y][x] = !$walls[y][x]; - } - } -} - -bool Map::iswall(size_t x, size_t y) { - return $walls[y][x] == WALL_VALUE; -} - -void Map::dump() { - dump_map("WALLS", $walls); -} - -bool Map::can_move(Point move_to) { - return inmap(move_to.x, move_to.y) && - !iswall(move_to.x, move_to.y); -} - -Point Map::map_to_camera(const Point &loc, const Point &cam_orig) { - return {loc.x - cam_orig.x, loc.y - cam_orig.y}; -} - -Point Map::center_camera(const Point &around, size_t view_x, size_t view_y) { - int high_x = int(width() - view_x); - int high_y = int(height() - view_y); - int center_x = int(around.x - view_x / 2); - int center_y = int(around.y - view_y / 2); - - // BUG: is clamp really the best thing here? this seems wrong. - size_t start_x = high_x > 0 ? std::clamp(center_x, 0, high_x) : 0; - size_t start_y = high_y > 0 ? std::clamp(center_y, 0, high_y) : 0; - - return {start_x, start_y}; -} - bool Map::INVARIANT() { using dbc::check; diff --git a/map.hpp b/map.hpp index 27b3ad1..9ede0b4 100644 --- a/map.hpp +++ b/map.hpp @@ -56,23 +56,15 @@ public: Room &room(size_t at) { return $rooms[at]; } size_t room_count() { return $rooms.size(); } - 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); - bool can_move(Point move_to); - void generate(); - void set_door(Room &room, int value); - void place_rooms(Room &root); - Point place_entity(size_t room_index); - bool neighbors(Point &out, bool up); bool inmap(size_t x, size_t y); bool iswall(size_t x, size_t y); + bool can_move(Point move_to); + bool neighbors(Point &out, bool up); void make_paths(); void set_target(const Point &at, int value=0); void clear_target(const Point &at); - bool walk(Point &src, Point &target); Point map_to_camera(const Point &loc, const Point &cam_orig); Point center_camera(const Point &around, size_t view_x, size_t view_y); diff --git a/meson.build b/meson.build index 345cbbb..194c589 100644 --- a/meson.build +++ b/meson.build @@ -32,6 +32,7 @@ runtests = executable('runtests', [ 'render.cpp', 'pathing.cpp', 'lights.cpp', + 'worldbuilder.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', 'tests/map.cpp', @@ -45,6 +46,7 @@ runtests = executable('runtests', [ 'tests/panel.cpp', 'tests/sound.cpp', 'tests/pathing.cpp', + 'tests/worldbuilder.cpp', ], dependencies: dependencies) @@ -65,6 +67,7 @@ roguish = executable('roguish', [ 'panel.cpp', 'pathing.cpp', 'lights.cpp', + 'worldbuilder.cpp', ], dependencies: dependencies) diff --git a/tests/map.cpp b/tests/map.cpp index d84da0b..cee1e23 100644 --- a/tests/map.cpp +++ b/tests/map.cpp @@ -1,8 +1,8 @@ #include -#include "map.hpp" #include #include #include +#include "map.hpp" using namespace fmt; using namespace nlohmann; @@ -40,39 +40,3 @@ TEST_CASE("dijkstra algo test", "[map]") { // FIX ME: REQUIRE(paths == expected); } } - -TEST_CASE("bsp algo test", "[map]") { - Map map(20, 20); - map.generate(); -} - -TEST_CASE("dumping and debugging", "[map]") { - Map map(20, 20); - map.generate(); - - dump_map("GENERATED", map.paths()); - map.dump(); -} - - -TEST_CASE("camera control", "[map]") { - Map map(20,20); - map.generate(); - - Point center = map.center_camera({10,10}, 5, 5); - - REQUIRE(center.x == 8); - REQUIRE(center.y == 8); - - Point translation = map.map_to_camera({10,10}, center); - - REQUIRE(translation.x == 2); - REQUIRE(translation.y == 2); -} - -TEST_CASE("pathing", "[map]") { - Map map(20,20); - map.generate(); - REQUIRE(map.can_move({0,0}) == false); - REQUIRE(map.iswall(0,0) == true); -} diff --git a/tests/worldbuilder.cpp b/tests/worldbuilder.cpp new file mode 100644 index 0000000..83c5a98 --- /dev/null +++ b/tests/worldbuilder.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include "map.hpp" +#include "worldbuilder.hpp" + +using namespace fmt; +using namespace nlohmann; +using std::string; + +TEST_CASE("bsp algo test", "[map]") { + Map map(20, 20); + WorldBuilder builder(map); + builder.generate(); +} + +TEST_CASE("dumping and debugging", "[map]") { + Map map(20, 20); + WorldBuilder builder(map); + builder.generate(); + + dump_map("GENERATED", map.paths()); + map.dump(); +} + + +TEST_CASE("camera control", "[map]") { + Map map(20, 20); + WorldBuilder builder(map); + builder.generate(); + + Point center = map.center_camera({10,10}, 5, 5); + + REQUIRE(center.x == 8); + REQUIRE(center.y == 8); + + Point translation = map.map_to_camera({10,10}, center); + + REQUIRE(translation.x == 2); + REQUIRE(translation.y == 2); +} + +TEST_CASE("pathing", "[map]") { + Map map(20, 20); + WorldBuilder builder(map); + builder.generate(); + + REQUIRE(map.can_move({0,0}) == false); + REQUIRE(map.iswall(0,0) == true); +} diff --git a/worldbuilder.cpp b/worldbuilder.cpp new file mode 100644 index 0000000..d87dd27 --- /dev/null +++ b/worldbuilder.cpp @@ -0,0 +1,177 @@ +#include "worldbuilder.hpp" +#include "rand.hpp" +#include + +using namespace fmt; + +inline int make_split(Room &cur, bool horiz) { + size_t dimension = horiz ? cur.height : cur.width; + int min = dimension / 4; + int max = dimension - min; + + 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) { + int rand_x = Random::uniform(0, room.width - 1); + int rand_y = Random::uniform(0, room.height - 1); + + switch(Random::uniform(0,3)) { + case 0: // north + door.x = room.x + rand_x; + door.y = room.y-1; + break; + case 1: // south + door.x = room.x + rand_x; + door.y = room.y + room.height; + break; + case 2: // east + door.x = room.x + room.width; + door.y = room.y + rand_y; + break; + case 3: // west + door.x = room.x - 1; + door.y = room.y + rand_y; + break; + default: + dbc::sentinel("impossible side"); + } +} + +void WorldBuilder::add_door(Room &room) { + rand_side(room, room.entry); + rand_side(room, room.exit); +} + +void WorldBuilder::partition_map(Room &cur, int depth) { + if(cur.width >= 5 && cur.width <= 10 && + cur.height >= 5 && cur.height <= 10) { + $map.$rooms.push_back(cur); + return; + } + + bool horiz = cur.width > cur.height ? false : true; + int split = make_split(cur, horiz); + Room left = cur; + Room right = cur; + + if(horiz) { + dbc::check(split > 0, "split is not > 0"); + dbc::check(split < int(cur.height), "split is too big!"); + left.height = size_t(split - 1); + right.y = cur.y + split; + right.height = size_t(cur.height - split); + } else { + dbc::check(split > 0, "split is not > 0"); + dbc::check(split < int(cur.width), "split is too big!"); + + left.width = size_t(split-1); + right.x = cur.x + split, + right.width = size_t(cur.width - split); + } + + if(depth > 0 && left.width > 5 && left.height > 5) { + partition_map(left, depth-1); + } + + if(depth > 0 && right.width > 5 && right.height > 5) { + partition_map(right, depth-1); + } +} + +void WorldBuilder::generate() { + Room root{ + .x = 0, + .y = 0, + .width = $map.$width, + .height = $map.$height + }; + + partition_map(root, 10); + place_rooms(root); + + for(size_t i = 0; i < $map.$rooms.size() - 1; i++) { + Room &src = $map.$rooms[i]; + Room &target = $map.$rooms[i+1]; + $map.set_target(target.entry); + bool found = dig_tunnel(src.exit, target.entry); + if(!found) { + println("ROOM NOT FOUND!"); + } + $map.clear_target(target.entry); + } + + Room &src = $map.$rooms.back(); + Room &target = $map.$rooms.front(); + + $map.set_target(target.entry); + dig_tunnel(src.exit, target.entry); + $map.clear_target(target.entry); + + for(size_t y = 0; y < $map.$height; ++y) { + for(size_t x = 0; x < $map.$width; ++x) { + $map.$walls[y][x] = !$map.$walls[y][x]; + } + } +} + +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(Room &cur) { + for(auto &cur : $map.$rooms) { + cur.x += 2; + cur.y += 2; + cur.width -= 4; + cur.height -= 4; + add_door(cur); + set_door(cur, INV_SPACE); + make_room(cur.x, cur.y, cur.width, cur.height); + } +} + +bool WorldBuilder::dig_tunnel(Point &src, Point &target) { + Matrix &paths = $map.paths(); + Matrix &walls = $map.walls(); + + walls[src.y][src.x] = INV_WALL; + walls[target.y][target.x] = INV_WALL; + + // for the walk this needs to be walls since it's inverted? + dbc::check(walls[src.y][src.x] == INV_WALL, + "src room has a wall at exit door"); + dbc::check(walls[target.y][target.x] == INV_WALL, + "target room has a wall at entry door"); + + $map.make_paths(); + bool found = false; + Point out{src.x, src.y}; + int count = 0; + + do { + walls[out.y][out.x] = INV_SPACE; + found = $map.neighbors(out, true); + + if(paths[out.y][out.x] == 0) { + walls[out.y][out.x] = INV_SPACE; + return true; + } + } while(found && out.x > 0 && out.y > 0 && ++count < 100); + + return false; +} diff --git a/worldbuilder.hpp b/worldbuilder.hpp new file mode 100644 index 0000000..fd56b62 --- /dev/null +++ b/worldbuilder.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "map.hpp" + +class WorldBuilder { + public: + Map& $map; + + WorldBuilder(Map &map) : $map(map) { } + + 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 generate(); + void set_door(Room &room, int value); + void place_rooms(Room &root); + bool dig_tunnel(Point &src, Point &target); +};