560 lines
20 KiB
C++
560 lines
20 KiB
C++
#include "map.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <iostream>
|
|
|
|
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<std::pair<position, int>> room_dims = gen_room_dims(rng);
|
|
|
|
std::vector<std::pair<position, int>> 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_position(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_position(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<position_list> game_map::get_room_list() const {
|
|
return rooms_tile_list;
|
|
}
|
|
|
|
std::vector<std::pair<position, int>> game_map::gen_room_dims(RNG *rng) {
|
|
position_list room_dim_list;
|
|
room_dim_list.reserve(MAX_ROOM_CNT);
|
|
std::vector<std::pair<position, int>> 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::room> game_map::distr_rooms(RNG *rng,
|
|
std::vector<std::pair<position, int>> &room_dims,
|
|
std::vector<std::pair<position, int>> &layer_data) {
|
|
std::vector<room> 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<room> &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<std::pair<position, int>> &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;
|
|
}
|