#include "map.hpp" #include "dbc.hpp" #include #include #include #include "rand.hpp" #include using std::vector, std::pair; using namespace fmt; void dump_map(const std::string &msg, Matrix &map) { println("----------------- {}", msg); for(auto row : map) { for(auto col : row) { print("{} ", col); } print("\n"); } } /** * 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), $height(height), $walls(height, MatrixRow(width, INV_WALL)), $paths(height, width, 1000) {} 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; } } } 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::partition_map(Room &cur, int depth) { if(cur.width >= 5 && cur.width <= 10 && cur.height >= 5 && cur.height <= 10) { $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 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); } } bool Map::neighbors(Point &out, bool greater) { Matrix &paths = $paths.$paths; std::array dirs{{ {out.x,out.y-1}, {out.x+1,out.y}, {out.x,out.y+1}, {out.x-1,out.y} }}; int zero_i = -1; int cur = paths[out.y][out.x]; // BUG: sometimes cur is in a wall so finding neighbors fails for(size_t i = 0; i < dirs.size(); ++i) { Point dir = dirs[i]; int target = inmap(dir.x, dir.y) ? paths[dir.y][dir.x] : $limit; if(target == $limit) continue; // skip unpathable stuff int diff = cur - target; if(diff == 1) { out = {.x=dir.x, .y=dir.y}; return true; } else if(diff == 0) { zero_i = i; } } if(zero_i != -1) { out = {.x=dirs[zero_i].x, .y=dirs[zero_i].y}; return true; } else { return false; } } 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; check($walls.size() == height(), "walls wrong height"); check($walls[0].size() == width(), "walls wrong width"); return true; }