#include "map.h" #include #include #include "constants.h" game_map::game_map(character *player, RNG *rng, const feature enabled_features): enabled_features{enabled_features} { map.reserve(MAP_HEIGHT * MAP_WIDTH); for (int i = MAP_HEIGHT * MAP_WIDTH; i > 0; --i) map.push_back(' '); // Note: during generation, walls DO NOT count as being in the rooms std::vector> room_dims = gen_room_dims(rng); std::vector> layer_data; // width height top left room_data = distr_rooms(rng, room_dims, layer_data); for (size_t r = 0; r < room_data.size(); ++r) fill_room(room_data[r], r); fill_outline(); gen_path(layer_data, rng); rooms_tile_list.reserve(room_data.size()); for (size_t i = 0; i < room_data.size(); ++i) rooms_tile_list.push_back({}); for (size_t i = 0; i < map.size(); ++i) if (map[i] >= 0 && map[i] < MAX_ROOM_CNT) rooms_tile_list[map[i]].push_back(remap_index(i)); for (size_t i = 0; i < rooms_tile_list.size(); ++i) rooms_tile_list[i].shrink_to_fit(); rooms_tile_list.shrink_to_fit(); int player_room = rng->rand_under(room_data.size()); player->set_pos(rng->get_rand_in_vector(rooms_tile_list[player_room])); gen_stairs(player_room, rng); } void game_map::gen_stairs(int player_room, RNG *rng) { down_stairs = rng->get_rand_in_vector(rooms_tile_list[ rng->exclude_middle(0, room_data.size(), player_room)]); if (enabled_features & FEATURE_REVISIT) { up_stairs = rng->get_rand_in_vector(rooms_tile_list[player_room]); map[remap_position(up_stairs)] = '<'; map[remap_position(down_stairs)] = '>'; } else { map[remap_position(down_stairs)] = '\\'; } } position game_map::random_size(int min_width, int min_height, int max_width, int max_height, RNG *rng) { return {rng->rand_between(min_width, max_width + 1), rng->rand_between(min_height, max_height + 1)}; } game_map::game_map(character *player, const std::string &map_data, RNG *rng, const feature enabled_features): enabled_features{enabled_features} { int map_size = MAP_HEIGHT * MAP_WIDTH; map.reserve(map_size); rooms_tile_list.reserve(MAX_ROOM_CNT); for (int i = 0; i < MAX_ROOM_CNT; ++i) rooms_tile_list.push_back({}); int max_room_num = 0; for (int i = 0; i < map_size; ++i) { if (map_data[i] >= '0' && map_data[i] <= '9') { map.push_back(map_data[i] - '0'); max_room_num = std::max(max_room_num, map_data[i] - '0'); } else { map.push_back(map_data[i]); } if (map_data[i] >= '0' && map_data[i] <= '9') rooms_tile_list[map_data[i] - '0'].push_back(remap_index(i)); } for (int i = 0; i <= max_room_num; ++i) room_data.push_back({0, 0, 0, 0}); int player_room = rng->rand_under(room_data.size()); player->set_pos(rng->get_rand_in_vector(rooms_tile_list[player_room])); gen_stairs(player_room, rng); } bool game_map::is_available(const position &pos) const { if (pos.x < 0 || pos.x >= MAP_WIDTH || pos.y < 0 || pos.y >= MAP_HEIGHT) return false; int idx = remap_position(pos); return map[idx] < MAX_ROOM_CNT || map[idx] == '\\' || map[idx] == '>' || map[idx] == '<' || map[idx] == '+' || map[idx] == '#'; } position_list game_map::get_available_around(const position &pos) const { position_list result; for (int i = 0; i < DIRECTION_CNT; ++i) if (is_available(pos + MOVE[i])) result.push_back(pos + MOVE[i]); return result; } void game_map::print(display *out) const { for (std::size_t i = 0; i < map.size(); ++i) out->print_char(remap_index(i), map[i] <= 9 ? '.' : map[i], map[i] == '\\' || map[i] == '>' || map[i] == '<' ? COLOR_PAIR(COLOR_BLUE) : A_NORMAL); } int game_map::which_room(const position &pos) const { if (pos.y > MAP_HEIGHT || pos.x > MAP_WIDTH || pos.x < 0 || pos.y < 0) return -1; char tmp = map[remap_position(pos)]; return tmp < MAX_ROOM_CNT ? tmp : -1; } position game_map::get_up_stairs() const { return up_stairs; } position game_map::get_down_stairs() const { return down_stairs; } int game_map::get_up_stairs_room() const { return map[remap_position(up_stairs)]; } int game_map::get_down_stairs_room() const { return map[remap_position(down_stairs)]; } position_list game_map::get_room_list(int idx) const { return rooms_tile_list[idx]; } std::vector game_map::get_room_list() const { return rooms_tile_list; } std::vector> game_map::gen_room_dims(RNG *rng) { position_list room_dim_list; room_dim_list.reserve(MAX_ROOM_CNT); std::vector> result; result.reserve(MAX_ROOM_CNT); for (int i = 0; i < MAX_ROOM_CNT; ++i) room_dim_list.push_back(random_size(MIN_ROOM_WIDTH, MIN_ROOM_HEIGHT, MAX_ROOM_WIDTH, MAX_ROOM_HEIGHT, rng)); int curr_top = 0; int curr_left = 0; // index in result int curr_layer_idx = 0; int curr_layer_cnt = 0; for (int i = 0; i < MAX_ROOM_CNT && curr_top <= ACTUAL_MAP_HEIGHT && curr_left <= ACTUAL_MAP_WIDTH; ++i) { // DO NOT update curr_top until curr_left is exhausted if (curr_top + MIN_ROOM_SPACING + room_dim_list[i].y + HEIGHT_RESERVED <= ACTUAL_MAP_HEIGHT && curr_left + MIN_ROOM_SPACING + room_dim_list[i].x + WIDTH_RESERVED <= ACTUAL_MAP_WIDTH) { result.push_back({room_dim_list[i], curr_layer_cnt}); curr_left += MIN_ROOM_SPACING + room_dim_list[i].x; continue; } int tmp = curr_top; for (std::size_t j = curr_layer_idx; j < result.size(); ++j) tmp = std::max(curr_top + result[j].first.y + MIN_ROOM_SPACING, tmp); curr_top = tmp; curr_layer_idx = result.size(); curr_left = 0; ++curr_layer_cnt; if (curr_top + MIN_ROOM_SPACING + room_dim_list[i].y + HEIGHT_RESERVED <= ACTUAL_MAP_HEIGHT && curr_left + MIN_ROOM_SPACING + room_dim_list[i].x + WIDTH_RESERVED <= ACTUAL_MAP_WIDTH) { result.push_back({room_dim_list[i], curr_layer_cnt}); curr_left += MIN_ROOM_SPACING + room_dim_list[i].x; } } result.shrink_to_fit(); return result; } position random_size(int min_width, int min_height, int max_width, int max_height, RNG *rng) { return {rng->rand_between(min_width, max_width + 1), rng->rand_between(min_height, max_height + 1)}; } void game_map::fill_room(const room &r, const int num) { for (int x = 0; x < r.width; ++x) for (int y = 0; y < r.height; ++y) map[remap_position({r.left + x, r.top + y})] = num; for (int x = 0; x < r.width; ++x) { map[remap_position({r.left + x, r.top - 1})] = '-'; map[remap_position({r.left + x, r.top + r.height})] = '-'; } for (int y = -1; y <= r.height; ++y) { map[remap_position({r.left - 1, r.top + y})] = '|'; map[remap_position({r.left + r.width, r.top + y})] = '|'; } } std::vector game_map::distr_rooms(RNG *rng, std::vector> &room_dims, std::vector> &layer_data) { std::vector result; result.reserve(room_dims.size()); int max_layer = room_dims[room_dims.size() - 1].second; // left, right, height layer_data.reserve(max_layer + 1); // distributing rooms horizontally for (int layer = 0; layer <= max_layer; ++layer) { int l = INF; int r = 0; int layer_height = 0; // get the interval for the current layer // and the max height of the layer for (std::size_t i = 0; i < room_dims.size(); ++i) if (room_dims[i].second == layer) { l = std::min(l, (int)i); r = std::max(r, (int)i); layer_height = std::max(layer_height, room_dims[i].first.y); } layer_data.push_back({{l, r}, layer_height}); // distribute the current layer if (l == r) { result.push_back({ 0, rng->rand_under(ACTUAL_MAP_WIDTH - room_dims[l].first.x + 1), room_dims[l].first.x, room_dims[l].first.y, {0, 0}, {0, 0}}); continue; } int left = 0; int right = ACTUAL_MAP_WIDTH; // every time, distribute the last one first for (int i = l; i < r; ++i) left += MIN_ROOM_SPACING + room_dims[i].first.x; for (int i = r; i >= l; --i) { int offset = rng->rand_between(left, right - room_dims[i].first.x + 1); result.push_back({0, offset, room_dims[i].first.x, room_dims[i].first.y, {0, 0}, {0, 0}}); right = offset - MIN_ROOM_SPACING; if (i != l) left -= MIN_ROOM_SPACING + room_dims[i - 1].first.x; } } // distributing rooms vertically int top = 0; int bottom = ACTUAL_MAP_HEIGHT; for (int i = 0; i < max_layer; ++i) top += MIN_ROOM_SPACING + layer_data[i].second; for (int i = max_layer; i >= 0; --i) { int offset = rng->rand_between(top, bottom - layer_data[i].second + 1); for (int j = layer_data[i].first.x; j <= layer_data[i].first.y; ++j) result[j].top = offset; bottom = offset - MIN_ROOM_SPACING; if (i != 0) top -= MIN_ROOM_SPACING + layer_data[i - 1].second; } // add some vertical jitter jitter(rng, result); //add padding for (std::size_t i = 0; i < result.size(); ++i) { result[i].top += MAP_PADDING; result[i].left += MAP_PADDING; } return result; } void game_map::jitter(RNG *rng, std::vector &rooms) { for (int n = rooms.size() - 1; n >= 0; --n) { int t = 0; int b = ACTUAL_MAP_HEIGHT - 1; for (std::size_t i = 0; i < rooms.size(); ++i) { if (rooms[i] == rooms[n]) continue; if (overlap_x(rooms[n], rooms[i])) { if (rooms[n].top > rooms[i].top) t = std::max(t, rooms[i].top + rooms[i].height + MIN_ROOM_SPACING); else b = std::min(b, rooms[i].top - MIN_ROOM_SPACING); } } if (t < b - rooms[n].height + 1) rooms[n].top = rng->rand_between(t, b - rooms[n].height + 1); } } bool game_map::overlap_x(room &room1, room &room2) { return (room1.left >= room2.left && room1.left - MIN_ROOM_SPACING <= room2.left + room2.width) || (room2.left >= room1.left && room2.left - MIN_ROOM_SPACING <= room1.left + room1.width); } bool game_map::overlap_y(room &room1, room &room2) { return (room1.top >= room2.top && room1.top - MIN_ROOM_SPACING <= room2.top + room2.height) || (room2.top >= room1.top && room2.top - MIN_ROOM_SPACING <= room1.top + room1.height); } void game_map::fill_outline() { for (int x = 0; x < MAP_WIDTH; ++x) { map[remap_position({x, 0})] = '-'; map[remap_position({x, MAP_HEIGHT - 1})] = '-'; } for (int y = 0; y < MAP_HEIGHT; ++y) { map[remap_position({0, y})] = '|'; map[remap_position({MAP_WIDTH - 1, y})] = '|'; } } int game_map::get_room_cnt() const { return room_data.size(); } game_map::room game_map::get_room(std::size_t idx) const { if (idx < room_data.size()) return room_data[idx]; return {0, 0, 0, 0, {0, 0}, {0, 0}}; } void game_map::gen_path(std::vector> &layer_data, RNG *rng) { for (std::size_t l = 0; l < layer_data.size(); ++l) { for (int i = layer_data[l].first.x; i < layer_data[l].first.y; ++i) connect_x(room_data[i + 1], room_data[i], rng); if (l < layer_data.size() - 1) { connect_y(room_data[layer_data[l].first.x], room_data[layer_data[l + 1].first.x], rng); connect_y(room_data[layer_data[l].first.y], room_data[layer_data[l + 1].first.y], rng); connect_y(room_data[(layer_data[l].first.x + layer_data[l].first.y) / 2], room_data[(1 + layer_data[l + 1].first.x + layer_data[l + 1].first.y) / 2], rng); } } } void game_map::connect_x(room &room1, room &room2, RNG *rng) { room1.rdoor = {room1.left + room1.width, rng->rand_between(room1.top, room1.top + room1.height) }; room2.ldoor = {room2.left - 1, rng->rand_between(room2.top, room2.top + room2.height) }; map[remap_position(room1.rdoor)] = '+'; map[remap_position(room2.ldoor)] = '+'; connect({room1.rdoor.x + 1, room1.rdoor.y}, {room2.ldoor.x - 1, room2.ldoor.y}); } void game_map::connect_y(room &room1, room &room2, RNG *rng) { room1.bdoor = {rng->rand_between(room1.left, room1.left + room1.width), room1.top + room1.height }; room2.tdoor = {rng->rand_between(room2.left, room2.left + room2.width), room2.top - 1 }; map[remap_position(room1.bdoor)] = '+'; map[remap_position(room2.tdoor)] = '+'; connect({room1.bdoor.x, room1.bdoor.y + 1}, {room2.tdoor.x, room2.tdoor.y - 1}); } // a is to the left of b void game_map::connect(position s, position t) { if (s.x > t.x) { connect(t, s); return; } map[remap_position(s)] = '#'; map[remap_position(t)] = '#'; const room nil{0, 0, 0, 0}; while (s.x < t.x) { auto r = hit_room({s.x + 1, s.y}); if (r == nil) { ++s.x; map[remap_position(s)] = '#'; continue; } if (r.top + r.height - s.y < r.top - 1) { while (hit_room({s.x + 1, s.y}, r)) { ++s.y; map[remap_position(s)] = '#'; } } else { while (hit_room({s.x + 1, s.y}, r)) { --s.y; map[remap_position(s)] = '#'; } } } if (s.y < t.y) { while (s.y < t.y) { auto r = hit_room({s.x, s.y + 1}); if (r == nil) { ++s.y; map[remap_position(s)] = '#'; continue; } while (hit_room({s.x, s.y + 1}, r)) { ++s.x; map[remap_position(s)] = '#'; } while (hit_room({s.x - 1, s.y + 1}, r)) { ++s.y; map[remap_position(s)] = '#'; } while (hit_room({s.x, s.y - 1}, r) && s.x > t.x) { --s.x; map[remap_position(s)] = '#'; } ++s.y; map[remap_position(s)] = '#'; } } else { while (s.y > t.y) { auto r = hit_room({s.x, s.y - 1}); if (r == nil) { --s.y; map[remap_position(s)] = '#'; continue; } while (hit_room({s.x, s.y - 1}, r)) { ++s.x; map[remap_position(s)] = '#'; } while (hit_room({s.x - 1, s.y - 1}, r)) { --s.y; map[remap_position(s)] = '#'; } while (hit_room({s.x, s.y + 1}, r) && s.x > t.x) { --s.x; map[remap_position(s)] = '#'; } } } } game_map::room game_map::hit_room(const position &a) { for (auto r : room_data) { if (hit_room(a, r)) return r; } const room nil{0, 0, 0, 0}; return nil; } bool game_map::hit_room(const position &a, const room &r) { return a.y >= r.top - 1 && a.y <= r.top + r.height && a.x >= r.left - 1 && a.x <= r.left + r.width; } position game_map::remap_index(const int idx) const { return {idx % MAP_WIDTH, idx / MAP_WIDTH}; } int game_map::remap_position(const position &pos) const { return pos.y * MAP_WIDTH + pos.x; }