From afc2b9fa12a65ee261cbf9e1ded47b97b353a85a Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 22:43:06 -0400 Subject: [PATCH] finished random map generation --- src/Makefile | 3 - src/map.cc | 259 +++++++++++++++++++++++++++++++++++++++++++-------- src/map.h | 79 ++++++++++------ src/rng.cc | 4 + src/rng.h | 2 + 5 files changed, 276 insertions(+), 71 deletions(-) diff --git a/src/Makefile b/src/Makefile index 379130a..c00e7cc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,9 +18,6 @@ EXEC = ../bin/cc3k # executable name ########## Targets ########## -test : - ${CXX} ${CXXFLAGS} position.cc display.cc curses_input.cc curses_output.cc cursor.cc rng.cc map.cc testing.cc -o testing -lncurses - .PHONY : clean # not file names ${EXEC} : ${OBJECTS} # link step diff --git a/src/map.cc b/src/map.cc index 49ec0fe..0977f92 100644 --- a/src/map.cc +++ b/src/map.cc @@ -1,7 +1,7 @@ #include "map.h" #include -#include +#include #include @@ -15,25 +15,32 @@ game_map::game_map(RNG *rng, const feature enabled_features): // 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 - std::vector room_data = distr_rooms(rng, room_dims); + 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); - for (int x = 0; x < MAP_WIDTH; ++x) { - auto tmp = remap_position({x, 0}); - map[tmp] = '-'; - map[remap_position({x, MAP_HEIGHT - 1})] = '-'; + fill_outline(); + + gen_path(layer_data, rng); + + for (std::size_t i = 0; i < map.size(); ++i) + if (map[i] >= 0 && map[i] < MAX_ROOM_CNT) + empty_spots.push_back(remap_index(i)); + + down_stairs = rng->get_rand_in_vector(empty_spots); + + if (enabled_features & FEATURE_REVISIT) { + auto tmp = remove_from_list(empty_spots, down_stairs); + up_stairs = rng->get_rand_in_vector(tmp); + + map[remap_position(up_stairs)] = '<'; + map[remap_position(down_stairs)] = '>'; + } else { + map[remap_position(down_stairs)] = '\\'; } - - - for (int y = 0; y < MAP_HEIGHT; ++y) { - map[remap_position({0, y})] = '|'; - map[remap_position({MAP_WIDTH - 1, y})] = '|'; - } - - map.shrink_to_fit(); } position game_map::random_size(int min_width, int min_height, @@ -49,21 +56,24 @@ game_map::game_map(const std::string &map_data, int map_size = MAP_HEIGHT * MAP_WIDTH; map.reserve(map_size); - for (int i = 0; i < map_size; ++i) { - if (map_data[i] >= '0' && map_data[i] <= '9') - map.push_back(map_data[i] - '0'); - else - map.push_back(map_data[i]); + int max_room_num = 0; - if ((map_data[i] >= '0' && map_data[i] <= '9') || - map_data[i] == '+' || map_data[i] == '#') + 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') empty_spots.push_back(remap_index(i)); } down_stairs = rng->get_rand_in_vector(empty_spots); - if (enabled_features | FEATURE_REVISIT) { + if (enabled_features & FEATURE_REVISIT) { auto tmp = remove_from_list(empty_spots, down_stairs); up_stairs = rng->get_rand_in_vector(tmp); @@ -72,6 +82,9 @@ game_map::game_map(const std::string &map_data, } else { map[remap_position(down_stairs)] = '\\'; } + + for (int i = 0; i <= max_room_num; ++i) + room_data.push_back({0, 0, 0, 0}); } const position_list game_map::get_available_positions() const { @@ -107,22 +120,15 @@ int game_map::get_down_stairs_room() const { return map[remap_position(down_stairs)]; } -const std::vectorgame_map::get_room_list() const { - std::vector result; - result.reserve(10); +position_list game_map::get_room_list(int idx) const { + position_list result; - for (int i = 0; i < 10; ++i) - result.push_back({}); - - for (std::size_t i = 0; i < map.size(); ++i) - if (map[i] <= 9) - result[map[i]].push_back(remap_index(i)); + for (size_t i = 0; i < map.size(); ++i) { + if (map[i] == idx) + result.push_back(remap_index(i)); + } result.shrink_to_fit(); - - for (std::size_t i = 0; i < result.size(); ++i) - result[i].shrink_to_fit(); - return result; } @@ -212,13 +218,13 @@ void game_map::fill_room(const room &r, const int num) { } std::vector game_map::distr_rooms(RNG *rng, - std::vector> &room_dims) { + 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 - std::vector> layer_data; layer_data.reserve(max_layer + 1); // distributing rooms horizontally @@ -245,7 +251,7 @@ std::vector game_map::distr_rooms(RNG *rng, 0, rng->rand_under(ACTUAL_MAP_WIDTH - room_dims[l].first.x + 1), room_dims[l].first.x, - room_dims[l].first.y}); + room_dims[l].first.y, {0, 0}, {0, 0}}); continue; } @@ -260,7 +266,7 @@ std::vector game_map::distr_rooms(RNG *rng, 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}); + room_dims[i].first.y, {0, 0}, {0, 0}}); right = offset - MIN_ROOM_SPACING; @@ -290,7 +296,7 @@ std::vector game_map::distr_rooms(RNG *rng, top -= MIN_ROOM_SPACING + layer_data[i - 1].second; } - // jitter + // add some vertical jitter jitter(rng, result); //add padding @@ -350,3 +356,176 @@ bool game_map::overlap_y(room &room1, 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; +} diff --git a/src/map.h b/src/map.h index c4e73f6..ee55ad5 100644 --- a/src/map.h +++ b/src/map.h @@ -7,6 +7,7 @@ #include "display.h" #include "position.h" #include "rng.h" +#include "fraction.h" class game_map final { private: @@ -16,12 +17,12 @@ private: static const int MAX_ROOM_WIDTH = 20; static const int MAX_ROOM_HEIGHT = 5; // setup safezones - static const int ACTUAL_MAP_WIDTH = MAP_WIDTH - 4; - static const int ACTUAL_MAP_HEIGHT = MAP_HEIGHT - 4; - static const int MAP_PADDING = 2; + static const int ACTUAL_MAP_WIDTH = MAP_WIDTH - 5; + static const int ACTUAL_MAP_HEIGHT = MAP_HEIGHT - 5; + static const int MAP_PADDING = 3; static const int MIN_ROOM_SPACING = 3; - static const int WIDTH_RESERVED = 8; - static const int HEIGHT_RESERVED = 4; + static const int WIDTH_RESERVED = 6; + static const int HEIGHT_RESERVED = 3; const feature enabled_features; std::vector map; @@ -29,33 +30,16 @@ private: position_list empty_spots; position up_stairs; position down_stairs; - int room_cnt; public: - // randomly generate a map - game_map(RNG *rng, const feature enabled_features); - // read map from a string - game_map(const std::string &map_data, RNG *rng, - const feature enabled_features); - - const position_list get_available_positions() const; - bool is_available(const position &pos) const; - - const std::vector get_room_list() const; - // IMPORTANT: always print a map before anything else - void print(display *out) const; - - position get_up_stairs() const; - position get_down_stairs() const; - - int get_up_stairs_room() const; - int get_down_stairs_room() const; -private: - struct room { int top; int left; int width; int height; + position ldoor; + position rdoor; + position tdoor; + position bdoor; bool operator!=(const room &r) { return !(*this == r); } @@ -66,7 +50,29 @@ private: height == r.height; } }; + // randomly generate a map + game_map(RNG *rng, const feature enabled_features); + // read map from a string + game_map(const std::string &map_data, RNG *rng, + const feature enabled_features); + const position_list get_available_positions() const; + bool is_available(const position &pos) const; + + position_list get_room_list(int idx) const; + // IMPORTANT: always print a map before anything else + void print(display *out) const; + + position get_up_stairs() const; + position get_down_stairs() const; + + int get_up_stairs_room() const; + int get_down_stairs_room() const; + + int get_room_cnt() const; + room get_room(std::size_t idx) const; +private: + std::vector room_data; position remap_index(const int idx) const { return {idx % MAP_WIDTH, idx / MAP_WIDTH}; } @@ -81,16 +87,33 @@ private: std::vector> gen_room_dims(RNG *rng); std::vector distr_rooms(RNG *rng, - std::vector> &room_dims); + std::vector> &room_dims, + std::vector> &layer_data); bool overlap_x(room &room1, room &room2); bool overlap_y(room &room1, room &room2); + // has anything between the two + bool between_x(room &room1, + room &room2); + bool between_y(room &room1, + room &room2); + void jitter(RNG *rng, std::vector &rooms); void fill_room(const room &r, const int num); - void gen_path(std::vector &rooms); + void fill_outline(); + + void gen_path(std::vector> &layer_data, RNG *rng); + // IMPORTANT: assumes room1 is to the left of room 2 + void connect_x(room &room1, room &room2, RNG *rng); + // IMPORTANT: assumes room1 is under room2 + void connect_y(room &room1, room &room2, RNG *rng); + + void connect(position s, position t); + room hit_room(const position &a); + bool hit_room(const position &a, const room &r); }; const std::string default_map = diff --git a/src/rng.cc b/src/rng.cc index 54aafa1..ee6e224 100644 --- a/src/rng.cc +++ b/src/rng.cc @@ -35,3 +35,7 @@ int RNG::get_curr_rand_num() const { bool RNG::trial(fraction &psuccess) { return (rand() % psuccess.denominator) < psuccess.numerator; } + +bool RNG::coin_flip() const { + return rand() % 2; +} diff --git a/src/rng.h b/src/rng.h index 896b8a7..a1fac0b 100644 --- a/src/rng.h +++ b/src/rng.h @@ -28,6 +28,8 @@ public: bool trial(fraction &psuccess); + bool coin_flip() const; + template T &get_rand_in_vector(std::vector &vec) { curr_rand_num = rand();