diff --git a/.gdbinit b/.gdbinit index 6f17608..d86a368 100644 --- a/.gdbinit +++ b/.gdbinit @@ -7,4 +7,4 @@ set pagination off break abort #break _invalid_parameter_noinfo #break _invalid_parameter -# catch throw +catch throw diff --git a/dbc.cpp b/dbc.cpp index 4145253..13bafde 100644 --- a/dbc.cpp +++ b/dbc.cpp @@ -2,7 +2,7 @@ #include void dbc::log(const string &message) { - std::cerr << message << std::endl; + std::cerr << "!!!!!!!!!!" << message << std::endl; } void dbc::sentinel(const string &message) { diff --git a/map.cpp b/map.cpp index a4aeb31..f640722 100644 --- a/map.cpp +++ b/map.cpp @@ -44,18 +44,20 @@ void Map::clear_target(const Point &at) { $paths.clear_target(at); } -Point Map::place_entity(size_t room_index) { +bool Map::place_entity(size_t room_index, Point &out) { dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms"); Room &start = $rooms[room_index]; - size_t size = std::max(start.width, start.height); - for(matrix::box it{$walls, start.x, start.y, size}; it.next();) { - if(!iswall(it.x, it.y)) return {it.x, it.y}; + for(matrix::rando_rect it{$walls, start.x, start.y, start.width, start.height}; it.next();) { + if(!iswall(it.x, it.y)) { + out.x = it.x; + out.y = it.y; + return true; + } } - dbc::sentinel("DIDN'T FIND AN OPEN SPACE!"); - return {start.x, start.y}; + return false; } bool Map::iswall(size_t x, size_t y) { @@ -205,3 +207,32 @@ void Map::expand() { $paths = Pathing($width, $height); $tiles = TileMap($width, $height); } + +void Map::add_room(Room &room) { + // println(">>ADDING ROOM x/y={},{}; w/h={},{}; map={},{}", + // room.x, room.y, room.width, room.height, $width, $height); + + room.x++; + room.y++; + room.width--; + room.height--; + + if(room.x + room.width >= $width) { + // fix the width + room.x--; + } + + if(room.y + room.height >= $height) { + // fix the height + room.y--; + } + + $rooms.push_back(room); +} + +void Map::invert_space() { + for(matrix::each_cell it{$walls}; it.next();) { + int is_wall = !$walls[it.y][it.x]; + $walls[it.y][it.x] = is_wall; + } +} diff --git a/map.hpp b/map.hpp index 0403b70..9a8cdbc 100644 --- a/map.hpp +++ b/map.hpp @@ -53,7 +53,7 @@ public: Room &room(size_t at) { return $rooms[at]; } size_t room_count() { return $rooms.size(); } - Point place_entity(size_t room_index); + bool place_entity(size_t room_index, Point &out); bool inmap(size_t x, size_t y); bool iswall(size_t x, size_t y); bool can_move(Point move_to); @@ -73,4 +73,6 @@ public: bool INVARIANT(); void load_tiles(); + void add_room(Room &room); + void invert_space(); }; diff --git a/matrix.hpp b/matrix.hpp index e07488e..ffd5e7b 100644 --- a/matrix.hpp +++ b/matrix.hpp @@ -3,8 +3,12 @@ #include #include #include +#include +#include #include #include "point.hpp" +#include "rand.hpp" +#include "dbc.hpp" namespace matrix { using std::vector, std::queue, std::array; @@ -324,4 +328,60 @@ namespace matrix { }; using circle = circle_t; + + template + struct rectangle_t { + int x; + int y; + int left; + int right; + int width; + int height; + int bottom; + + rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : + left(start_x), + width(width), + height(height) + { + size_t h = matrix::height(mat); + size_t w = matrix::width(mat); + y = start_y - 1; + x = left - 1; // must be -1 for next() + right = min(start_x + width, w); + + y = start_y; + bottom = min(start_y + height, h); + } + + bool next() { + x = next_x(x, right); + y = next_y(x, y); + x = max(x, left); + return at_end(y, bottom); + } + }; + + using rectangle = rectangle_t; + + template + struct rando_rect_t { + int x; + int y; + rectangle_t it; + + rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : + it{mat, start_x, start_y, width, height} + { + } + + bool next() { + bool done = it.next(); + x = it.x; + y = it.y; + return done; + } + }; + + using rando_rect = rando_rect_t; } diff --git a/status.txt b/status.txt index 00486e8..fd0e2f1 100644 --- a/status.txt +++ b/status.txt @@ -1,5 +1,9 @@ TODAY'S GOAL: +* UNKNOWN COLLISION TYPE 6 +* Either reduce the amount of size_t or use amit's suggestion of 0u since small sized unsigned can be casted to size_t. + + * https://github.com/Ericsson/codechecker?tab=readme-ov-file * Goblins will be in the world and not move or are already dead. * https://pkl-lang.org/ diff --git a/tests/lighting.cpp b/tests/lighting.cpp index 1ce4583..92b32b5 100644 --- a/tests/lighting.cpp +++ b/tests/lighting.cpp @@ -13,9 +13,11 @@ TEST_CASE("lighting a map works", "[lighting]") { Map map(20,23); WorldBuilder builder(map); builder.generate_map(); + Point light1, light2; + + REQUIRE(map.place_entity(0, light1)); + REQUIRE(map.place_entity(1, light1)); - Point light1 = map.place_entity(0); - Point light2 = map.place_entity(1); LightSource source1{6, 1.0}; LightSource source2{4,3}; diff --git a/tests/map.cpp b/tests/map.cpp index 1fa2fc7..1dfba9e 100644 --- a/tests/map.cpp +++ b/tests/map.cpp @@ -30,6 +30,31 @@ TEST_CASE("camera control", "[map]") { REQUIRE(translation.y == 2); } +TEST_CASE("map placement test", "[map:placement]") { + for(int i = 0; i < 50; i++) { + size_t width = Random::uniform(9, 21); + size_t height = Random::uniform(13, 25); + Map map(width, height); + WorldBuilder builder(map); + builder.generate_rooms(); + map.invert_space(); + + for(size_t rnum = 0; rnum < map.room_count(); rnum++) { + Room &room = map.room(rnum); + Point pos; + + REQUIRE(map.place_entity(rnum, pos)); + // matrix::dump("ROOM PLACEMENT TEST", map.walls(), pos.x, pos.y); + + REQUIRE(!map.iswall(pos.x, pos.y)); + REQUIRE(pos.x >= room.x); + REQUIRE(pos.y >= room.y); + REQUIRE(pos.x <= room.x + room.width); + REQUIRE(pos.y <= room.y + room.height); + } + } +} + TEST_CASE("dijkstra algo test", "[map]") { json data = load_test_data("./tests/dijkstra.json"); diff --git a/tests/matrix.cpp b/tests/matrix.cpp index 55400b7..490e50f 100644 --- a/tests/matrix.cpp +++ b/tests/matrix.cpp @@ -194,7 +194,8 @@ TEST_CASE("prototype flood algorithm", "[matrix:flood]") { if(map.room_count() < 2) continue; - Point start = map.place_entity(map.room_count() / 2); + Point start; + REQUIRE(map.place_entity(map.room_count() / 2, start)); map.set_target(start); map.make_paths(); Matrix result = map.paths(); @@ -286,7 +287,8 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") { size_t view_width = width/2; size_t view_height = height/2; - Point player = map.place_entity(1); + Point player; + REQUIRE(map.place_entity(1, player)); Point start = map.center_camera(player, view_width, view_height); size_t end_x = std::min(view_width, map.width() - start.x); @@ -300,3 +302,71 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") { } } } + +TEST_CASE("random rectangle", "[matrix:rando_rect]") { + for(int i = 0; i < 10; i++) { + size_t width = Random::uniform(9, 21); + size_t height = Random::uniform(13, 25); + Map map(width, height); + WorldBuilder builder(map); + builder.generate_rooms(); + map.invert_space(); + auto wall_copy = map.walls(); + + for(size_t rnum = 0; rnum < map.room_count(); rnum++) { + Room &room = map.room(rnum); + Point pos; + + for(matrix::rando_rect it{map.walls(), room.x, room.y, room.width, room.height}; it.next();) + { + if(map.iswall(it.x, it.y)) { + matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y); + } + + REQUIRE(!map.iswall(it.x, it.y)); + REQUIRE(size_t(it.x) >= room.x); + REQUIRE(size_t(it.y) >= room.y); + REQUIRE(size_t(it.x) <= room.x + room.width); + REQUIRE(size_t(it.y) <= room.y + room.height); + + wall_copy[it.y][it.x] = wall_copy[it.y][it.x] + 5; + } + } + + matrix::dump("WALLS FILLED", wall_copy); + } +} + +TEST_CASE("standard rectangle", "[matrix:rectangle]") { + for(int i = 0; i < 20; i++) { + size_t width = Random::uniform(9, 21); + size_t height = Random::uniform(13, 25); + Map map(width, height); + WorldBuilder builder(map); + builder.generate_rooms(); + map.invert_space(); + auto wall_copy = map.walls(); + + for(size_t rnum = 0; rnum < map.room_count(); rnum++) { + Room &room = map.room(rnum); + Point pos; + + for(matrix::rectangle it{map.walls(), room.x, room.y, room.width, room.height}; it.next();) + { + if(map.iswall(it.x, it.y)) { + matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y); + } + + REQUIRE(!map.iswall(it.x, it.y)); + REQUIRE(size_t(it.x) >= room.x); + REQUIRE(size_t(it.y) >= room.y); + REQUIRE(size_t(it.x) <= room.x + room.width); + REQUIRE(size_t(it.y) <= room.y + room.height); + + wall_copy[it.y][it.x] = wall_copy[it.y][it.x] + 5; + } + } + + matrix::dump("WALLS FILLED", wall_copy); + } +} diff --git a/worldbuilder.cpp b/worldbuilder.cpp index 157c6ee..dc6e76b 100644 --- a/worldbuilder.cpp +++ b/worldbuilder.cpp @@ -53,9 +53,10 @@ void WorldBuilder::add_door(Room &room) { } 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); + if(cur.width >= 3 && cur.width <= 6 && + cur.height >= 3 && cur.height <= 6) + { + $map.add_room(cur); return; } @@ -97,9 +98,11 @@ void WorldBuilder::update_door(Point &at, int wall_or_space) { void WorldBuilder::stylize_room(int room, string tile_name, float size) { - Point center = $map.place_entity(room); + Point pos_out; + bool placed = $map.place_entity(room, pos_out); + dbc::check(placed, "failed to place style in room"); - for(matrix::circle it{$map.$walls, center, size}; it.next();) { + 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)) { $map.$tiles.set_tile(x, it.y, tile_name); @@ -108,22 +111,24 @@ void WorldBuilder::stylize_room(int room, string tile_name, float size) { } } -void WorldBuilder::generate_map() { - PointList holes; +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() { + generate_rooms(); + + PointList holes; for(size_t i = 0; i < $map.$rooms.size() - 1; i++) { tunnel_doors(holes, $map.$rooms[i], $map.$rooms[i+1]); } @@ -133,14 +138,21 @@ void WorldBuilder::generate_map() { // place all the holes for(auto hole : holes) { - $map.$walls[hole.y][hole.x] = INV_SPACE; - } - for(matrix::each_cell it{$map.$walls}; it.next();) { - int is_wall = !$map.$walls[it.y][it.x]; - $map.$walls[it.y][it.x] = is_wall; + if(!matrix::inbounds($map.$walls, hole.x, hole.y)) { + matrix::dump("MAP BEFORE CRASH", $map.$walls, hole.x, hole.y); + + auto err = fmt::format("invalid hold target {},{} map is only {},{}", + hole.x, hole.y, matrix::width($map.$walls), + matrix::height($map.$walls)); + + dbc::sentinel(err); + } + + $map.$walls[hole.y][hole.x] = INV_SPACE; } + $map.invert_space(); $map.expand(); $map.load_tiles(); @@ -159,9 +171,11 @@ void WorldBuilder::generate_map() { DinkyECS::Entity place_item(DinkyECS::World &world, Map &game_map, std::string name, int in_room) { auto &config = world.get_the(); auto item = world.entity(); - auto pos = game_map.place_entity(in_room); + Point pos_out; + bool placed = game_map.place_entity(in_room, pos_out); + dbc::check(placed, "failed to randomly place item in room"); json item_data = config.items[name]; - world.set(item, {pos.x+1, pos.y+1}); + world.set(item, {pos_out.x+1, pos_out.y+1}); if(item_data["inventory_count"] > 0) { world.set(item, {item_data["inventory_count"], item_data}); @@ -178,7 +192,9 @@ DinkyECS::Entity place_combatant(DinkyECS::World &world, Map &game_map, std::str auto &config = world.get_the(); auto enemy = world.entity(); auto enemy_data = config.enemies[name]; - auto pos = game_map.place_entity(in_room); + Point pos; + bool placed = game_map.place_entity(in_room, pos); + dbc::check(placed, "failed to place combatant in room"); world.set(enemy, {pos}); if(enemy_data.contains("components")) { @@ -250,11 +266,8 @@ void WorldBuilder::make_room(size_t origin_x, size_t origin_y, size_t w, size_t void WorldBuilder::place_rooms() { for(auto &cur : $map.$rooms) { - cur.x += WORLDBUILD_SHRINK; - cur.y += WORLDBUILD_SHRINK; - cur.width -= WORLDBUILD_SHRINK * 2; - cur.height -= WORLDBUILD_SHRINK * 2; - + // println("ROOM x/y={},{}; w/h={},{}; map={},{}", + // cur.x, cur.y, cur.width, cur.height, $map.$width, $map.$height); add_door(cur); make_room(cur.x, cur.y, cur.width, cur.height); } @@ -281,24 +294,34 @@ inline bool random_path(Map &map, PointList &holes, Point src, Point target) { return target_found; } -inline void straight_path(PointList &holes, Point src, Point target) { +inline void straight_path(Map &map, PointList &holes, Point src, Point target) { for(matrix::line dig{src, target}; dig.next();) { holes.push_back({size_t(dig.x), size_t(dig.y)}); - holes.push_back({size_t(dig.x+1), 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, 3); + switch(path_type) { case 0: // for now do 25% as simple straight paths - straight_path(holes, src.exit, target.entry); + straight_path($map, holes, src.exit, target.entry); + break; + case 1: + // for now do 25% as simple straight paths + straight_path($map, holes, src.exit, target.entry); break; default: // then do the rest as random with fallback if(!random_path($map, holes, src.exit, target.entry)) { - straight_path(holes, src.exit, target.entry); + straight_path($map, holes, src.exit, target.entry); } } } diff --git a/worldbuilder.hpp b/worldbuilder.hpp index a20b240..cd661fc 100644 --- a/worldbuilder.hpp +++ b/worldbuilder.hpp @@ -17,6 +17,7 @@ class WorldBuilder { void tunnel_doors(PointList &holes, Room &src, Room &target); void update_door(Point &at, int wall_or_space); void stylize_room(int room, string tile_name, float size); + void generate_rooms(); void generate_map(); void place_entities(DinkyECS::World &world); void generate(DinkyECS::World &world);