From b3300b8e7c5bc96dda3ab19179b6aa4658d40eb3 Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 00:16:08 -0400 Subject: [PATCH 1/9] WORK IN PROGRESS TODO: implement random level generation moved exclude from list to position.h distinguished level and map --- src/cc3k.h | 6 +++ src/characters.cc | 18 ------- src/characters.h | 6 +-- src/constants.h | 1 - src/game.h | 2 + src/gold.h | 14 ++++++ src/level.h | 27 ++++++++++- src/main.cc | 8 ++-- src/map.cc | 120 ++++++++++++++++++++++++++-------------------- src/map.cc.bak | 68 ++++++++++++++++++++++++++ src/map.h | 85 +++++++++++++++++++------------- src/position.cc | 21 ++++++++ src/position.h | 5 ++ 13 files changed, 268 insertions(+), 113 deletions(-) create mode 100644 src/cc3k.h create mode 100644 src/gold.h create mode 100644 src/map.cc.bak diff --git a/src/cc3k.h b/src/cc3k.h new file mode 100644 index 0000000..e702d1a --- /dev/null +++ b/src/cc3k.h @@ -0,0 +1,6 @@ +#ifndef __CC3K_H__ +#define __CC3K_H__ + +class CC3K; + +#endif diff --git a/src/characters.cc b/src/characters.cc index 48a0931..13602db 100644 --- a/src/characters.cc +++ b/src/characters.cc @@ -110,24 +110,6 @@ result character::apply(direction &dir, const potion_list &potions) { return result::fine; } -position_list remove_from_list(const position_list &sorted_positions, - position_list excluded) { - std::sort(excluded.begin(), excluded.end()); - - position_list result{sorted_positions.size() - excluded.size()}; - - auto exc = excluded.begin(); - - for (auto src : sorted_positions) { - if (exc != excluded.end() && src == *exc) - ++exc; - else - result.push_back(src); - } - - return result; -} - // IMPORTANT: remember to check if player is on the stairs result character::move(const direction dir, const position_list &available_positions) { diff --git a/src/characters.h b/src/characters.h index 4c5a04c..d4f6bb3 100644 --- a/src/characters.h +++ b/src/characters.h @@ -14,6 +14,7 @@ #include "constants.h" #include "position.h" #include "potion.h" +#include "gold.h" #include "rng.h" // #include "inventory.h" // Reserved for later @@ -85,11 +86,6 @@ protected: bool hostile; }; -// requires: all elements of excluded are in sorted_positions -position_list remove_from_list(const position_list &sorted_positions, - position_list excluded); - - int calc_dmg(const int ATK, const int DEF); #endif diff --git a/src/constants.h b/src/constants.h index f4316de..d9c7962 100644 --- a/src/constants.h +++ b/src/constants.h @@ -106,6 +106,5 @@ const int COLOR_BLACK_ON_WHITE = 8; typedef std::vector position_list; typedef std::vector direction_list; -typedef std::vector gold_list; #endif diff --git a/src/game.h b/src/game.h index e7318ec..f79aa5d 100644 --- a/src/game.h +++ b/src/game.h @@ -17,6 +17,8 @@ private: display &out; logger &log; RNG &rng; + + // IMPORTANT: during player generation, std::unique_ptr player; public: game(const feature enabled_features, diff --git a/src/gold.h b/src/gold.h new file mode 100644 index 0000000..738f066 --- /dev/null +++ b/src/gold.h @@ -0,0 +1,14 @@ +#ifndef __GOLD_H__ +#define __GOLD_H__ + +#include +#include "position.h" + +struct gold { + int amount; + position pos; +}; + +typedef std::vector gold_list; + +#endif diff --git a/src/level.h b/src/level.h index dff3881..4488d5f 100644 --- a/src/level.h +++ b/src/level.h @@ -1,6 +1,31 @@ #ifndef __LEVEL_H__ #define __LEVEL_H__ -class level; +#include +#include "display.h" +#include "characters.h" +#include "potion.h" +#include "constants.h" +#include "gold.h" +#include "map.h" + +class level { +private: + game_map map; + const character &player; + character_list chlist; + potion_list plist; + gold_list glist; +public: + // randomly generate a map + level(const character &player); + // read map from a string + level(const std::string &map_data, const character &player); + void print(display &out) const; + void run(); + position_list get_available_positions() const; + position get_up_stairs() const; + position get_down_stairs() const; +}; #endif diff --git a/src/main.cc b/src/main.cc index dec8057..30069a2 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,4 +1,4 @@ -#include "game.h" +#include "cc3k.h" #include "arguments.h" int main(int argc, char **argv) { @@ -19,10 +19,12 @@ int main(int argc, char **argv) { return RETURN_PANICKED; } - game game_proc(enabled_features, *in, *out, *log, *rng); + CC3K game_proc(enabled_features, *in, *out, *log, *rng); - while (game_proc.run() != game_status::terminated) + while (game_proc.run() != game_status::terminated) { out->render(); + out->clear(); + } return RETURN_FINE; } diff --git a/src/map.cc b/src/map.cc index d9827cd..5a9a787 100644 --- a/src/map.cc +++ b/src/map.cc @@ -1,68 +1,82 @@ #include "map.h" -#include -#include -#include -#include +game_map::game_map(RNG &rng, const feature enabled_features): + enabled_features{enabled_features} { -game_map::game_map(int lvl) { - level = lvl; - // TODO: randomly generate a map } -game_map::game_map(const std::string &map_data, int lvl) { - level = lvl; - std::istringstream iss{map_data}; +game_map::game_map(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); - std::string line; + 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]); - for (int i = 0; i < MAP_HEIGHT; ++i) { - getline(iss, line); - map[i] = line; + if ((map_data[i] >= '0' && map_data[i] <= '9') || + map_data[i] == '+' || map_data[i] == '#') + empty_spots.push_back(remap_index(i)); + } + + down_stairs = rng.get_rand_in_vector(empty_spots); + + + if (enabled_features | FEATURE_REVISIT) { + up_stairs = rng.get_rand_in_vector( + remove_from_list(empty_spots, {down_stairs})); + map[remap_position(up_stairs)] = '<'; + map[remap_position(down_stairs)] = '>'; + } else { + map[remap_position(down_stairs)] = '\\'; } } -game_map::~game_map() {} - -int game_map::get_level() const { - return this->level; +const position_list game_map::get_available_positions() const { + return empty_spots; } -std::vector game_map::get_available_positions() const { - std::vector 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); +} + +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)]; +} + +const std::vectorgame_map::get_room_list() const { + std::vector result; + result.reserve(10); + + 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)); + + result.shrink_to_fit(); + + for (std::size_t i = 0; i < result.size(); ++i) + result[i].shrink_to_fit(); - for (int line = 0; line < MAP_HEIGHT; ++line) - for (int x = 0; x < MAP_WIDTH; ++x) - if (map[line][x] == '.' || - map[line][x] == '#' || - map[line][x] == '+') - result.push_back(position{x, line}); return result; } - -void game_map::print() const { - - // TODO: write a print function using ncurses -} - -void game_map::print(display &display) const { - for (int i = 0; i < MAP_HEIGHT; ++i) - display.print_str(position{0, i}, map[i]); -} - -void game_map::apply_potion(character *who, const stat_name statn, - const int amount) { - effects.push_back(effect{who, statn, amount}); - who->apply_buff(statn, amount); -} - -void game_map::enter_level(character *who) { - for (auto eff : effects) - if (eff.who == who) - who->apply_buff(eff.statn, eff.amount); -} - -void game_map::leave_level(character *who) { - who->set_ATK(STARTING_ATK[who->get_race()]); - who->set_DEF(STARTING_DEF[who->get_race()]); -} diff --git a/src/map.cc.bak b/src/map.cc.bak new file mode 100644 index 0000000..d9827cd --- /dev/null +++ b/src/map.cc.bak @@ -0,0 +1,68 @@ +#include "map.h" + +#include +#include +#include +#include + +game_map::game_map(int lvl) { + level = lvl; + // TODO: randomly generate a map +} + +game_map::game_map(const std::string &map_data, int lvl) { + level = lvl; + std::istringstream iss{map_data}; + + std::string line; + + for (int i = 0; i < MAP_HEIGHT; ++i) { + getline(iss, line); + map[i] = line; + } +} + +game_map::~game_map() {} + +int game_map::get_level() const { + return this->level; +} + +std::vector game_map::get_available_positions() const { + std::vector result; + + for (int line = 0; line < MAP_HEIGHT; ++line) + for (int x = 0; x < MAP_WIDTH; ++x) + if (map[line][x] == '.' || + map[line][x] == '#' || + map[line][x] == '+') + result.push_back(position{x, line}); + return result; +} + +void game_map::print() const { + + // TODO: write a print function using ncurses +} + +void game_map::print(display &display) const { + for (int i = 0; i < MAP_HEIGHT; ++i) + display.print_str(position{0, i}, map[i]); +} + +void game_map::apply_potion(character *who, const stat_name statn, + const int amount) { + effects.push_back(effect{who, statn, amount}); + who->apply_buff(statn, amount); +} + +void game_map::enter_level(character *who) { + for (auto eff : effects) + if (eff.who == who) + who->apply_buff(eff.statn, eff.amount); +} + +void game_map::leave_level(character *who) { + who->set_ATK(STARTING_ATK[who->get_race()]); + who->set_DEF(STARTING_DEF[who->get_race()]); +} diff --git a/src/map.h b/src/map.h index 58fe7c2..2255d64 100644 --- a/src/map.h +++ b/src/map.h @@ -12,49 +12,70 @@ #include "display.h" #include "position.h" #include "characters.h" +#include "rng.h" class game_map final { +private: + const feature enabled_features; + std::vector map; + // IMPORTANT: keep empty_spots ordered + position_list empty_spots; + position up_stairs; + position down_stairs; + int room_cnt; public: - game_map(int lvl = 0); // randomly generate one - // initialize using stored data - game_map(const std::string &map_data, int lvl); - ~game_map(); // placeholder + // 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); - int get_level() const; - - position_list get_available_positions() const; + const position_list get_available_positions() const; + const std::vector get_room_list() const; // IMPORTANT: always print a map before anything else + void print(display &out) const; - // prints using ncurses - void print() const; + position get_up_stairs() const; + position get_down_stairs() const; - // prints to a string - void print(display &display) const; + int get_up_stairs_room() const; + int get_down_stairs_room() const; +private: + position remap_index(const int idx) const { + return {idx % MAP_WIDTH, idx / MAP_HEIGHT}; + } - // This is implemented this way to do two things: - // 1. avoid directly accessing heap memory (bonus points) - // 2. make a level revisitable - void apply_potion(character *who, const stat_name statn, const int amount); - - // just in case you want to retain the potion effects - void enter_level(character *who); - void leave_level(character *who); -public: - struct effect { - character *who; - stat_name statn; - int amount; - }; - - std::string map[MAP_HEIGHT]; - int level; - - // use this to remember every applied potion - std::vector effects; + int remap_position(const position &pos) const { + return pos.y * MAP_WIDTH + pos.x; + } }; const std::string default_map = - "|-----------------------------------------------------------------------------|\n| |\n| |--------------------------| |-----------------------| |\n| |..........................| |.......................| |\n| |..........................+########+.......................|-------| |\n| |..........................| # |...............................|--| |\n| |..........................| # |..................................|--| |\n| |----------+---------------| # |----+----------------|...............| |\n| # ############# |...............| |\n| # # |-----+------| |...............| |\n| # # |............| |...............| |\n| ################### |............| ######+...............| |\n| # # |............| # |...............| |\n| # # |-----+------| # |--------+------| |\n| |---------+-----------| # # # # |\n| |.....................| # # # |----+------| |\n| |.....................| ######################## |...........| |\n| |.....................| # # |...........| |\n| |.....................| # |------+--------------------|...........| |\n| |.....................| # |.......................................| |\n| |.....................+##########+.......................................| |\n| |.....................| |.......................................| |\n| |---------------------| |---------------------------------------| |\n| |\n|-----------------------------------------------------------------------------|"; + "|-----------------------------------------------------------------------------|\ +| |\ +| |--------------------------| |-----------------------| |\ +| |11111111111111111111111111| |33333333333333333333333| |\ +| |11111111111111111111111111+########+33333333333333333333333|-------| |\ +| |11111111111111111111111111| # |3333333333333333333333333333333|--| |\ +| |11111111111111111111111111| # |3333333333333333333333333333333333|--| |\ +| |----------+---------------| # |----+----------------|333333333333333| |\ +| # ############# |333333333333333| |\ +| # # |-----+------| |333333333333333| |\ +| # # |444444444444| |333333333333333| |\ +| ################### |444444444444| ######+333333333333333| |\ +| # # |444444444444| # |333333333333333| |\ +| # # |-----+------| # |--------+------| |\ +| |---------+-----------| # # # # |\ +| |222222222222222222222| # # # |----+------| |\ +| |222222222222222222222| ######################## |00000000000| |\ +| |222222222222222222222| # # |00000000000| |\ +| |222222222222222222222| # |------+--------------------|00000000000| |\ +| |222222222222222222222| # |000000000000000000000000000000000000000| |\ +| |222222222222222222222+##########+000000000000000000000000000000000000000| |\ +| |222222222222222222222| |000000000000000000000000000000000000000| |\ +| |---------------------| |---------------------------------------| |\ +| |\ +|-----------------------------------------------------------------------------|"; #endif diff --git a/src/position.cc b/src/position.cc index 63609e5..478b57d 100644 --- a/src/position.cc +++ b/src/position.cc @@ -43,3 +43,24 @@ size_t find(const std::vector &sorted_list, return sorted_list.size(); } + +#include + +std::vector remove_from_list(const std::vector + &sorted_positions, + std::vector excluded) { + std::sort(excluded.begin(), excluded.end()); + + std::vector result{sorted_positions.size() - excluded.size()}; + + auto exc = excluded.begin(); + + for (auto src : sorted_positions) { + if (exc != excluded.end() && src == *exc) + ++exc; + else + result.push_back(src); + } + + return result; +} diff --git a/src/position.h b/src/position.h index 6d794a5..ea9535e 100644 --- a/src/position.h +++ b/src/position.h @@ -26,4 +26,9 @@ typedef struct position { std::size_t find(const std::vector &sorted_list, const position &target); +// requires: all elements of excluded are in sorted_positions +std::vector remove_from_list(const std::vector + &sorted_positions, + std::vector excluded); + #endif From c9e96b5e6e10b4f4a4c6d72dc805fe575441d5cc Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 09:58:30 -0400 Subject: [PATCH 2/9] changed unique_ptr access to using raw pointers to denote non-ownership added new flag -e for extra stuff (potions/races) IMPORTANT: main.cc is undergoing rework --- src/arguments.cc | 6 ++++-- src/arguments.h | 1 + src/constants.h | 1 + src/curses_input.cc | 6 +++--- src/curses_input.h | 4 ++-- src/curses_output.cc | 10 +++++----- src/curses_output.h | 4 ++-- src/game.h | 16 ++++++++-------- 8 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/arguments.cc b/src/arguments.cc index 1b2462f..f9d27f1 100644 --- a/src/arguments.cc +++ b/src/arguments.cc @@ -47,6 +47,8 @@ feature proc_args(int argc, char **argv, result |= FEATURE_THROW; } else if (str == "-R") { result |= FEATURE_REVISIT; + } else if (str == "-e") { + result |= FEATURE_EXTRA_STUFF; } else if (str == "-s") { ++i; str = argv[i]; @@ -121,8 +123,8 @@ feature proc_args(int argc, char **argv, if (result & FEATURE_NCURSES) { curse = std::make_unique(); - in = std::make_unique(*curse); - out = std::make_unique(*curse); + in = std::make_unique(curse.get()); + out = std::make_unique(curse.get()); } if (result & FEATURE_SEED) { diff --git a/src/arguments.h b/src/arguments.h index 9af55de..0c0d088 100644 --- a/src/arguments.h +++ b/src/arguments.h @@ -18,6 +18,7 @@ -i : Enable inventory -t : Enable throw -R : Enable revisiting levels +-e : Enable extra potions and races -s [seed] : Sets initial seed to seed -L [file] : Enable logging to file -I [file] : Reads commands from file. CANNOT BE USED WITH -n. diff --git a/src/constants.h b/src/constants.h index d9c7962..7161acb 100644 --- a/src/constants.h +++ b/src/constants.h @@ -92,6 +92,7 @@ const feature FEATURE_SEED = 1 << 7; const feature FEATURE_MENU = 1 << 8; const feature FEATURE_IN_FILE = 1 << 9; const feature FEATURE_OUT_FILE = 1 << 10; +const feature FEATURE_EXTRA_STUFF = 1 << 11; const feature FEATURE_PANIC_SEED = 1 << 27; const feature FEATURE_PANIC_FILE = 1 << 28; diff --git a/src/curses_input.cc b/src/curses_input.cc index 44319c3..dfcab87 100644 --- a/src/curses_input.cc +++ b/src/curses_input.cc @@ -1,10 +1,10 @@ #include "curses_input.h" -curses_input::curses_input(cursor &new_curse): +curses_input::curses_input(cursor *new_curse): curse{new_curse} {} game_command curses_input::get_command() { - switch (curse.getcmd()) { + switch (curse->getcmd()) { case 'h': return game_command::move_west; @@ -51,7 +51,7 @@ game_command curses_input::get_command() { return game_command_pass; } - switch (curse.getcmd()) { + switch (curse->getcmd()) { case 'h': return game_command::apply_west; diff --git a/src/curses_input.h b/src/curses_input.h index 0f7808a..3d34d5c 100644 --- a/src/curses_input.h +++ b/src/curses_input.h @@ -8,9 +8,9 @@ class curses_input final: public input { private: - cursor &curse; + cursor *curse; public: - curses_input(cursor &new_curse); + curses_input(cursor *new_curse); game_command get_command() override; }; diff --git a/src/curses_output.cc b/src/curses_output.cc index ac4d8e8..e85a278 100644 --- a/src/curses_output.cc +++ b/src/curses_output.cc @@ -1,14 +1,14 @@ #include "curses_output.h" -curses_output::curses_output(cursor &new_curse): +curses_output::curses_output(cursor *new_curse): curse{new_curse} {} void curses_output::render() { - curse.show(); + curse->show(); } void curses_output::clear() { - curse.clear(); + curse->clear(); } void curses_output::print_char(const position &pos, const char ch, @@ -16,7 +16,7 @@ void curses_output::print_char(const position &pos, const char ch, if (pos.x >= DISPLAY_WIDTH || pos.y >= DISPLAY_HEIGHT) return; - curse.print_char(pos, ch, attrs); + curse->print_char(pos, ch, attrs); } void curses_output::print_str(const position &pos, const std::string &str, @@ -27,7 +27,7 @@ void curses_output::print_str(const position &pos, const std::string &str, position tmp = pos; for (std::size_t i = 0; i < str.length(); ++i) { - curse.print_char(tmp, str[i], attrs); + curse->print_char(tmp, str[i], attrs); tmp += {1, 0}; if (tmp.x >= DISPLAY_WIDTH) diff --git a/src/curses_output.h b/src/curses_output.h index 7024ec6..123bd67 100644 --- a/src/curses_output.h +++ b/src/curses_output.h @@ -8,9 +8,9 @@ class curses_output final : public display { private: - cursor &curse; + cursor *curse; public: - curses_output(cursor &new_curse); + curses_output(cursor *new_curse); void render() override; void clear() override; diff --git a/src/game.h b/src/game.h index f79aa5d..27198ab 100644 --- a/src/game.h +++ b/src/game.h @@ -13,19 +13,19 @@ class game final { private: feature features; - input ∈ - display &out; - logger &log; - RNG &rng; + input *in; + display *out; + logger *log; + RNG *rng; // IMPORTANT: during player generation, std::unique_ptr player; public: game(const feature enabled_features, - input &new_in, - display &new_out, - logger &new_log, - RNG &new_rng); + input *new_in, + display *new_out, + logger *new_log, + RNG *new_rng); game_status run(); private: int getcmd() const; From 922b88a4a257f69fd27ae74d5db310be5d78c34a Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 10:00:48 -0400 Subject: [PATCH 3/9] removed some unnecessary comments --- src/characters.h | 6 ------ src/map.h | 6 ------ src/position.h | 6 ------ src/races.h | 6 ------ 4 files changed, 24 deletions(-) diff --git a/src/characters.h b/src/characters.h index d4f6bb3..9d8ac68 100644 --- a/src/characters.h +++ b/src/characters.h @@ -1,9 +1,3 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles all characters - */ - #ifndef __CHARACTERS_H__ #define __CHARACTERS_H__ #include diff --git a/src/map.h b/src/map.h index 2255d64..2585cbf 100644 --- a/src/map.h +++ b/src/map.h @@ -1,9 +1,3 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles map functionality - */ - #ifndef __MAP_H__ #define __MAP_H__ #include diff --git a/src/position.h b/src/position.h index ea9535e..214b8f9 100644 --- a/src/position.h +++ b/src/position.h @@ -1,9 +1,3 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles map functionality - */ - #ifndef __POSITION_H__ #define __POSITION_H__ #include diff --git a/src/races.h b/src/races.h index 73b9cd4..dd3862e 100644 --- a/src/races.h +++ b/src/races.h @@ -1,9 +1,3 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles map functionality - */ - #ifndef __RACES_H__ #define __RACES_H__ From 83b05b0f89c80030602548b287de0fd0a0aada7f Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 12:07:38 -0400 Subject: [PATCH 4/9] Unfinished: rand map gen --- src/main.cc | 10 ++--- src/map.cc | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/map.h | 25 ++++++++++- 3 files changed, 152 insertions(+), 6 deletions(-) diff --git a/src/main.cc b/src/main.cc index 30069a2..ab2e35a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -19,12 +19,12 @@ int main(int argc, char **argv) { return RETURN_PANICKED; } - CC3K game_proc(enabled_features, *in, *out, *log, *rng); + // CC3K game_proc(enabled_features, *in, *out, *log, *rng); - while (game_proc.run() != game_status::terminated) { - out->render(); - out->clear(); - } + // while (game_proc.run() != game_status::terminated) { + // out->render(); + // out->clear(); + // } return RETURN_FINE; } diff --git a/src/map.cc b/src/map.cc index 5a9a787..12af10c 100644 --- a/src/map.cc +++ b/src/map.cc @@ -1,8 +1,68 @@ #include "map.h" +#include +#include + game_map::game_map(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(-1); + + // Note: during generation, walls DO NOT count as being in the rooms + std::vector> room_dims = gen_room_dims(rng); + + // width height top left + std::vector> room_data = distr_rooms(rng, + room_dims); + +} + +std::vector> game_map::distr_rooms(RNG &rng, +std::vector> &room_dims) { + std::vector> result{room_dims.size()}; + + int max_layer = room_dims[room_dims.size() - 1].second; + std::vector layer_heights{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_heights.push_back(layer_height); + + // distribute the current layer + if (l == r) { + result.push_back({room_dims[l].first, + {rng.rand_under(ACTUAL_MAP_WIDTH - room_dims[l].first.x + 1), 0}}); + continue; + } + + int left = room_dims[l].first.x; + int right = ACTUAL_MAP_WIDTH; + + // every time, distribute the last one first + for (int i = l + 1; i < r; ++i) + left += MIN_ROOM_SPACING + room_dims[i].first.x; + + for (int i = r; i > l; --i) { + } + } + + return result; } game_map::game_map(const std::string &map_data, @@ -39,6 +99,12 @@ const position_list game_map::get_available_positions() const { return empty_spots; } +bool game_map::is_available(const position &pos) const { + int idx = remap_position(pos); + return map[idx] <= 9 || map[idx] == '\\' || map[idx] == '>' || + map[idx] == '<' || map[idx] == '+' || map[idx] == '#'; +} + 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], @@ -80,3 +146,60 @@ const std::vectorgame_map::get_room_list() const { return result; } + +std::vector> game_map::gen_room_dims(RNG &rng) { + position_list room_dim_list{MAX_ROOM_CNT}; + std::vector> result{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}); + 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}); + } + + 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)}; +} diff --git a/src/map.h b/src/map.h index 2585cbf..101de03 100644 --- a/src/map.h +++ b/src/map.h @@ -2,14 +2,27 @@ #define __MAP_H__ #include #include +#include #include "constants.h" #include "display.h" #include "position.h" -#include "characters.h" #include "rng.h" class game_map final { private: + static const int MAX_ROOM_CNT = 20; + static const int MIN_ROOM_WIDTH = 3; + static const int MIN_ROOM_HEIGHT = 3; + static const int MAX_ROOM_WIDTH = 20; + static const int MAX_ROOM_HEIGHT = 13; + // 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 = 1; + static const int MIN_ROOM_SPACING = 4; + static const int WIDTH_RESERVED = 8; + static const int HEIGHT_RESERVED = 4; + const feature enabled_features; std::vector map; // IMPORTANT: keep empty_spots ordered @@ -25,6 +38,7 @@ public: 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 @@ -43,6 +57,15 @@ private: int remap_position(const position &pos) const { return pos.y * MAP_WIDTH + pos.x; } + + position random_size(int min_width, int min_height, + int max_width, int max_height, + RNG &rng); + + std::vector> gen_room_dims(RNG &rng); + std::vector> distr_rooms(RNG &rng, + std::vector> + &room_dims); }; const std::string default_map = From f5daea0b18ee907e63352c54fedccc1efce5feb2 Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 17:10:16 -0400 Subject: [PATCH 5/9] unfinished work on random map gen --- src/map.cc | 174 ++++++++++++++++++++++++++++++++++++++++++------ src/map.h | 22 +++--- src/position.cc | 14 ++++ src/position.h | 4 +- src/rng.cc | 6 -- src/rng.h | 6 +- 6 files changed, 188 insertions(+), 38 deletions(-) diff --git a/src/map.cc b/src/map.cc index 12af10c..6d531f8 100644 --- a/src/map.cc +++ b/src/map.cc @@ -3,12 +3,12 @@ #include #include -game_map::game_map(RNG &rng, const feature enabled_features): +game_map::game_map(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(-1); + map.push_back(' '); // Note: during generation, walls DO NOT count as being in the rooms std::vector> room_dims = gen_room_dims(rng); @@ -17,14 +17,41 @@ game_map::game_map(RNG &rng, const feature enabled_features): std::vector> room_data = distr_rooms(rng, room_dims); + for (size_t r = 0; r < room_data.size(); ++r) { + for (int x = 0; x < room_data[r].first.x; ++x) + for (int y = 0; y < room_data[r].first.y; ++y) + map[remap_position({room_data[r].second.x + x, + room_data[r].second.y + y})] = r; + + for (int x = 0; x < room_data[r].first.x; ++x) { + map[remap_position({room_data[r].second.x + x, + room_data[r].second.y - 1})] = '-'; + map[remap_position({room_data[r].second.x + x, + room_data[r].second.y + + room_data[r].first.y + 1})] = '-'; + } + + for (int y = -1; y <= room_data[r].first.y; ++y) { + map[remap_position({room_data[r].second.x - 1, + room_data[r].second.y + y})] = '|'; + map[remap_position({room_data[r].second.x + + room_data[r].first.x + 1, + room_data[r].second.y + y})] = '|'; + } + } + + map.shrink_to_fit(); } -std::vector> game_map::distr_rooms(RNG &rng, +std::vector> game_map::distr_rooms(RNG *rng, std::vector> &room_dims) { - std::vector> result{room_dims.size()}; + std::vector> result; + result.reserve(room_dims.size()); int max_layer = room_dims[room_dims.size() - 1].second; - std::vector layer_heights{max_layer + 1}; + // left, right, height + std::vector> layer_data; + layer_data.reserve(max_layer + 1); // distributing rooms horizontally for (int layer = 0; layer <= max_layer; ++layer) { @@ -42,31 +69,128 @@ std::vector> &room_dims) { room_dims[i].first.y); } - layer_heights.push_back(layer_height); + layer_data.push_back({{l, r}, layer_height}); // distribute the current layer if (l == r) { - result.push_back({room_dims[l].first, - {rng.rand_under(ACTUAL_MAP_WIDTH - room_dims[l].first.x + 1), 0}}); + result.push_back({{ + 0, rng->rand_under(ACTUAL_MAP_WIDTH - + room_dims[l].first.x + 1) + }, + room_dims[l].first}); continue; } - int left = room_dims[l].first.x; + int left = 0; int right = ACTUAL_MAP_WIDTH; // every time, distribute the last one first - for (int i = l + 1; i < r; ++i) + for (int i = l; i < r; ++i) left += MIN_ROOM_SPACING + room_dims[i].first.x; - for (int i = r; i > l; --i) { + 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}); + + 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].second.y = offset; + + bottom = offset - MIN_ROOM_SPACING; + + if (i != 0) + top -= MIN_ROOM_SPACING + layer_data[i - 1].second; + } + + // jitter + //jitter(rng, result); + + //add padding + for (auto room : result) + room.first += {MAP_PADDING, MAP_PADDING}; + return result; } +void game_map::jitter(RNG *rng, + std::vector> &rooms) { + for (auto target = rooms.rbegin(); target != rooms.rend(); ++target) { + int t = -INF; + int b = INF; + int l = -INF; + int r = INF; + + for (auto i : rooms) + if (i != *target) { + if (overlap_x(*target, i)) { + if (target->second.y > i.second.y) + t = std::max(t, + i.second.y + i.first.y + + MIN_ROOM_SPACING); + else + b = std::min(b, + i.second.y + - MIN_ROOM_SPACING); + } + + if (overlap_y(*target, i)) { + if (target->second.x > i.second.x) + l = std::max(l, + i.second.x + i.first.x + + MIN_ROOM_SPACING); + else + r = std::min(r, + i.second.x + - MIN_ROOM_SPACING); + } + } + + target->second.x = rng->rand_between(l, r - target->first.x + 1); + target->second.y = rng->rand_between(t, b - target->first.y + 1); + } +} + +bool game_map::overlap_x(std::pair &room1, + std::pair &room2) { + return (room1.second.x >= room2.second.x && + room1.second.x - 1 <= room2.second.x + room2.first.x + 1) || + (room2.second.x >= room1.second.x && + room2.second.x - 1 <= room1.second.x + room1.first.x + 1); +} + +bool game_map::overlap_y(std::pair &room1, + std::pair &room2) { + return (room1.second.y >= room2.second.y && + room1.second.y - 1 <= room2.second.y + room2.first.y + 1) || + (room2.second.y >= room1.second.y && + room2.second.y - 1 <= room1.second.y + room1.first.y + 1); +} + +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(const std::string &map_data, - RNG &rng, const feature enabled_features): + RNG *rng, const feature enabled_features): enabled_features{enabled_features} { int map_size = MAP_HEIGHT * MAP_WIDTH; map.reserve(map_size); @@ -82,12 +206,13 @@ game_map::game_map(const std::string &map_data, empty_spots.push_back(remap_index(i)); } - down_stairs = rng.get_rand_in_vector(empty_spots); + down_stairs = rng->get_rand_in_vector(empty_spots); if (enabled_features | FEATURE_REVISIT) { - up_stairs = rng.get_rand_in_vector( - remove_from_list(empty_spots, {down_stairs})); + 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 { @@ -147,9 +272,11 @@ const std::vectorgame_map::get_room_list() const { return result; } -std::vector> game_map::gen_room_dims(RNG &rng) { - position_list room_dim_list{MAX_ROOM_CNT}; - std::vector> result{MAX_ROOM_CNT}; +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, @@ -171,6 +298,7 @@ std::vector> game_map::gen_room_dims(RNG &rng) { 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; } @@ -188,8 +316,10 @@ std::vector> game_map::gen_room_dims(RNG &rng) { 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) + 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(); @@ -199,7 +329,7 @@ std::vector> game_map::gen_room_dims(RNG &rng) { 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)}; + RNG *rng) { + return {rng->rand_between(min_width, max_width + 1), + rng->rand_between(min_height, max_height + 1)}; } diff --git a/src/map.h b/src/map.h index 101de03..a349833 100644 --- a/src/map.h +++ b/src/map.h @@ -14,12 +14,12 @@ private: static const int MIN_ROOM_WIDTH = 3; static const int MIN_ROOM_HEIGHT = 3; static const int MAX_ROOM_WIDTH = 20; - static const int MAX_ROOM_HEIGHT = 13; + 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 = 1; - static const int MIN_ROOM_SPACING = 4; + static const int MAP_PADDING = 2; + static const int MIN_ROOM_SPACING = 3; static const int WIDTH_RESERVED = 8; static const int HEIGHT_RESERVED = 4; @@ -32,9 +32,9 @@ private: int room_cnt; public: // randomly generate a map - game_map(RNG &rng, const feature enabled_features); + game_map(RNG *rng, const feature enabled_features); // read map from a string - game_map(const std::string &map_data, RNG &rng, + game_map(const std::string &map_data, RNG *rng, const feature enabled_features); const position_list get_available_positions() const; @@ -60,12 +60,18 @@ private: position random_size(int min_width, int min_height, int max_width, int max_height, - RNG &rng); + RNG *rng); - std::vector> gen_room_dims(RNG &rng); - std::vector> distr_rooms(RNG &rng, + std::vector> gen_room_dims(RNG *rng); + std::vector> distr_rooms(RNG *rng, std::vector> &room_dims); + + bool overlap_x(std::pair &room1, + std::pair &room2); + bool overlap_y(std::pair &room1, + std::pair &room2); + void jitter(RNG *rng, std::vector> &rooms); }; const std::string default_map = diff --git a/src/position.cc b/src/position.cc index 478b57d..b30636d 100644 --- a/src/position.cc +++ b/src/position.cc @@ -64,3 +64,17 @@ std::vector remove_from_list(const std::vector return result; } + +std::vector remove_from_list(const std::vector + &sorted_positions, + position &excluded) { + + std::vector result{sorted_positions.size() - 1}; + + for (auto src : sorted_positions) { + if (src != excluded) + result.push_back(src); + } + + return result; +} diff --git a/src/position.h b/src/position.h index 214b8f9..b7a6d55 100644 --- a/src/position.h +++ b/src/position.h @@ -24,5 +24,7 @@ std::size_t find(const std::vector &sorted_list, std::vector remove_from_list(const std::vector &sorted_positions, std::vector excluded); - +std::vector remove_from_list(const std::vector + &sorted_positions, + position &excluded); #endif diff --git a/src/rng.cc b/src/rng.cc index 2cd54bb..54aafa1 100644 --- a/src/rng.cc +++ b/src/rng.cc @@ -35,9 +35,3 @@ int RNG::get_curr_rand_num() const { bool RNG::trial(fraction &psuccess) { return (rand() % psuccess.denominator) < psuccess.numerator; } - -template T &RNG::get_rand_in_vector(const std::vector &vec) { - curr_rand_num = rand(); - - return vec[curr_rand_num % vec.size()]; -} diff --git a/src/rng.h b/src/rng.h index 037d6bb..896b8a7 100644 --- a/src/rng.h +++ b/src/rng.h @@ -28,7 +28,11 @@ public: bool trial(fraction &psuccess); - template T &get_rand_in_vector(const std::vector &vec); + template T &get_rand_in_vector(std::vector &vec) { + curr_rand_num = rand(); + + return vec[curr_rand_num % vec.size()]; + } }; #endif From 606429a6ed4fd68778023b3f0ca1f2daec2f7e0f Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 20:11:56 -0400 Subject: [PATCH 6/9] finished random room generation --- src/Makefile | 5 +- src/map.cc | 347 +++++++++++++++++++++++++++------------------------ src/map.h | 39 ++++-- 3 files changed, 215 insertions(+), 176 deletions(-) diff --git a/src/Makefile b/src/Makefile index 68df479..379130a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,10 +18,13 @@ 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 - ${CXX} ${CXXFLAGS} -O0 $^ -o $@ -lncurses # additional object files before $^ + ${CXX} ${CXXFLAGS} $^ -o $@ -lncurses # additional object files before $^ ${OBJECTS} : ${MAKEFILE_NAME} # OPTIONAL : changes to this file => recompile diff --git a/src/map.cc b/src/map.cc index 6d531f8..49ec0fe 100644 --- a/src/map.cc +++ b/src/map.cc @@ -3,6 +3,8 @@ #include #include +#include + game_map::game_map(RNG *rng, const feature enabled_features): enabled_features{enabled_features} { map.reserve(MAP_HEIGHT * MAP_WIDTH); @@ -14,174 +16,26 @@ game_map::game_map(RNG *rng, const feature enabled_features): std::vector> room_dims = gen_room_dims(rng); // width height top left - std::vector> room_data = distr_rooms(rng, - room_dims); + std::vector room_data = distr_rooms(rng, room_dims); - for (size_t r = 0; r < room_data.size(); ++r) { - for (int x = 0; x < room_data[r].first.x; ++x) - for (int y = 0; y < room_data[r].first.y; ++y) - map[remap_position({room_data[r].second.x + x, - room_data[r].second.y + y})] = r; + for (size_t r = 0; r < room_data.size(); ++r) + fill_room(room_data[r], r); - for (int x = 0; x < room_data[r].first.x; ++x) { - map[remap_position({room_data[r].second.x + x, - room_data[r].second.y - 1})] = '-'; - map[remap_position({room_data[r].second.x + x, - room_data[r].second.y + - room_data[r].first.y + 1})] = '-'; - } + for (int x = 0; x < MAP_WIDTH; ++x) { + auto tmp = remap_position({x, 0}); + map[tmp] = '-'; + map[remap_position({x, MAP_HEIGHT - 1})] = '-'; + } - for (int y = -1; y <= room_data[r].first.y; ++y) { - map[remap_position({room_data[r].second.x - 1, - room_data[r].second.y + y})] = '|'; - map[remap_position({room_data[r].second.x + - room_data[r].first.x + 1, - room_data[r].second.y + y})] = '|'; - } + + for (int y = 0; y < MAP_HEIGHT; ++y) { + map[remap_position({0, y})] = '|'; + map[remap_position({MAP_WIDTH - 1, y})] = '|'; } map.shrink_to_fit(); } -std::vector> game_map::distr_rooms(RNG *rng, -std::vector> &room_dims) { - 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 - 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}); - 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}); - - 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].second.y = offset; - - bottom = offset - MIN_ROOM_SPACING; - - if (i != 0) - top -= MIN_ROOM_SPACING + layer_data[i - 1].second; - } - - // jitter - //jitter(rng, result); - - //add padding - for (auto room : result) - room.first += {MAP_PADDING, MAP_PADDING}; - - return result; -} - -void game_map::jitter(RNG *rng, - std::vector> &rooms) { - for (auto target = rooms.rbegin(); target != rooms.rend(); ++target) { - int t = -INF; - int b = INF; - int l = -INF; - int r = INF; - - for (auto i : rooms) - if (i != *target) { - if (overlap_x(*target, i)) { - if (target->second.y > i.second.y) - t = std::max(t, - i.second.y + i.first.y - + MIN_ROOM_SPACING); - else - b = std::min(b, - i.second.y - - MIN_ROOM_SPACING); - } - - if (overlap_y(*target, i)) { - if (target->second.x > i.second.x) - l = std::max(l, - i.second.x + i.first.x - + MIN_ROOM_SPACING); - else - r = std::min(r, - i.second.x - - MIN_ROOM_SPACING); - } - } - - target->second.x = rng->rand_between(l, r - target->first.x + 1); - target->second.y = rng->rand_between(t, b - target->first.y + 1); - } -} - -bool game_map::overlap_x(std::pair &room1, - std::pair &room2) { - return (room1.second.x >= room2.second.x && - room1.second.x - 1 <= room2.second.x + room2.first.x + 1) || - (room2.second.x >= room1.second.x && - room2.second.x - 1 <= room1.second.x + room1.first.x + 1); -} - -bool game_map::overlap_y(std::pair &room1, - std::pair &room2) { - return (room1.second.y >= room2.second.y && - room1.second.y - 1 <= room2.second.y + room2.first.y + 1) || - (room2.second.y >= room1.second.y && - room2.second.y - 1 <= room1.second.y + room1.first.y + 1); -} - position game_map::random_size(int min_width, int min_height, int max_width, int max_height, RNG *rng) { @@ -226,15 +80,15 @@ const position_list game_map::get_available_positions() const { bool game_map::is_available(const position &pos) const { int idx = remap_position(pos); - return map[idx] <= 9 || map[idx] == '\\' || map[idx] == '>' || + return map[idx] < MAX_ROOM_CNT || map[idx] == '\\' || map[idx] == '>' || map[idx] == '<' || map[idx] == '+' || map[idx] == '#'; } -void game_map::print(display &out) const { +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); + out->print_char(remap_index(i), map[i] <= 9 ? '.' : map[i], + map[i] == '\\' || map[i] == '>' || + map[i] == '<' ? COLOR_PAIR(COLOR_BLUE) : A_NORMAL); } position game_map::get_up_stairs() const { @@ -333,3 +187,166 @@ position random_size(int min_width, int min_height, 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 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 + 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}); + 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}); + + 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; + } + + // 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); +} diff --git a/src/map.h b/src/map.h index a349833..c4e73f6 100644 --- a/src/map.h +++ b/src/map.h @@ -42,7 +42,7 @@ public: const std::vector get_room_list() const; // IMPORTANT: always print a map before anything else - void print(display &out) const; + void print(display *out) const; position get_up_stairs() const; position get_down_stairs() const; @@ -50,8 +50,25 @@ public: int get_up_stairs_room() const; int get_down_stairs_room() const; private: + + struct room { + int top; + int left; + int width; + int height; + bool operator!=(const room &r) { + return !(*this == r); + } + bool operator==(const room &r) { + return top == r.top && + left == r.left && + width == r.width && + height == r.height; + } + }; + position remap_index(const int idx) const { - return {idx % MAP_WIDTH, idx / MAP_HEIGHT}; + return {idx % MAP_WIDTH, idx / MAP_WIDTH}; } int remap_position(const position &pos) const { @@ -63,15 +80,17 @@ private: RNG *rng); std::vector> gen_room_dims(RNG *rng); - std::vector> distr_rooms(RNG *rng, - std::vector> - &room_dims); + std::vector distr_rooms(RNG *rng, + std::vector> &room_dims); - bool overlap_x(std::pair &room1, - std::pair &room2); - bool overlap_y(std::pair &room1, - std::pair &room2); - void jitter(RNG *rng, std::vector> &rooms); + bool overlap_x(room &room1, + room &room2); + bool overlap_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); }; const std::string default_map = From afc2b9fa12a65ee261cbf9e1ded47b97b353a85a Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Fri, 12 Jul 2024 22:43:06 -0400 Subject: [PATCH 7/9] 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(); From f2d2f6f72d524b59e3b9c09de31431de4f6293ed Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Sat, 13 Jul 2024 00:59:52 -0400 Subject: [PATCH 8/9] changes to characters and potions, work in progress --- src/arguments.cc | 23 +++++++++++++++ src/arguments.h | 18 ++--------- src/characters.cc | 69 +++++++++++++++++++++++++++++++++++++++---- src/characters.h | 31 +++++++++++-------- src/constants.h | 1 + src/fraction.cc | 2 +- src/fraction.h | 2 +- src/goblin.cc | 8 ++--- src/goblin.h | 4 +-- src/main.cc | 5 ++++ src/map.cc | 31 ++++++++++++++++--- src/map.h | 3 +- src/potion.cc | 34 +++++++++++++++++++-- src/potion.h | 22 +++++++++----- src/restore_health.cc | 6 ++-- src/restore_health.h | 4 +-- src/rng.cc | 2 +- src/rng.h | 2 +- src/shade.cc | 8 ++--- src/shade.h | 4 +-- src/vampire.cc | 8 ++--- src/vampire.h | 4 +-- 22 files changed, 215 insertions(+), 76 deletions(-) diff --git a/src/arguments.cc b/src/arguments.cc index f9d27f1..bcfe2f2 100644 --- a/src/arguments.cc +++ b/src/arguments.cc @@ -49,6 +49,8 @@ feature proc_args(int argc, char **argv, result |= FEATURE_REVISIT; } else if (str == "-e") { result |= FEATURE_EXTRA_STUFF; + } else if (str == "-E") { + result |= FEATURE_EXTRA_LEVELS; } else if (str == "-s") { ++i; str = argv[i]; @@ -168,6 +170,7 @@ void panic_args(feature panic) { default: cerr << "Something must have went really, really, wrong..." << endl; + break; } } else if (panic & FEATURE_PANIC_SEED) { cerr << "Invalid seed" << endl; @@ -175,3 +178,23 @@ void panic_args(feature panic) { cerr << "Something must have went really, really, wrong..." << endl; } + +void print_args_list() { + + + static const char *ARGS_LIST = "-n : Use ncurses for I/O\n\ +-r : Randomly generate maps\n\ +-m : Enabled a main menu + options\n\ +-c : Enemies chase the player (through doors and up stairs)\n\ +-i : Enable inventory\n\ +-t : Enable throw\n\ +-R : Enable revisiting levels\n\ +-e : Enable extra potions and races\n\ +-E : Enable extra levels\n\ +-s [seed] : Sets initial seed to seed\n\ +-L [file] : Enable logging to file\n\ +-I [file] : Reads commands from file. CANNOT BE USED WITH -n.\n\ +-O [file] : Outputs to file. CANNOT BE USED WITH -n.\n\ +-h/--help : Displays options list (doesn't start a game)"; + std::cout << ARGS_LIST << std::endl; +} diff --git a/src/arguments.h b/src/arguments.h index 0c0d088..76ef406 100644 --- a/src/arguments.h +++ b/src/arguments.h @@ -10,22 +10,6 @@ #include "constants.h" -/* Arguments --n : Use ncurses for I/O --r : Randomly generate maps --m : Enabled a main menu + options --c : Enemies chase the player (through doors and up stairs) --i : Enable inventory --t : Enable throw --R : Enable revisiting levels --e : Enable extra potions and races --s [seed] : Sets initial seed to seed --L [file] : Enable logging to file --I [file] : Reads commands from file. CANNOT BE USED WITH -n. --O [file] : Outputs to file. CANNOT BE USED WITH -n. --h/--help : Displays options list (doesn't start a game) - */ - // IMPORTANT: Errors include the index that caused them (or'ed into them) feature proc_args(int argc, char **argv, std::unique_ptr &curse, @@ -36,4 +20,6 @@ feature proc_args(int argc, char **argv, void panic_args(feature panic); +void print_args_list(); + #endif diff --git a/src/characters.cc b/src/characters.cc index 13602db..704a058 100644 --- a/src/characters.cc +++ b/src/characters.cc @@ -2,10 +2,17 @@ #include -character::character(RNG &rng, const enum race &nrace): +character::character(RNG *rng, const enum race &nrace): rng{rng}, race{nrace}, HP{STARTING_HP[race]}, - ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[race]} {} + ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[race]}, + base_hit_rate{1, 1} {} + +void character::start_turn() { + ATK = STARTING_ATK[race]; + DEF = STARTING_DEF[race]; + base_hit_rate = {1, 1}; +} enum race character::get_race() const { return race; @@ -31,7 +38,11 @@ int character::get_gold() const { return gold; } -float character::get_hitrate() const { +float character::get_hitrate_real() const { + return base_hit_rate.real(); +} + +fraction character::get_hitrate() const { return base_hit_rate; } @@ -59,7 +70,7 @@ void character::set_gold(const int ngold) { gold = ngold; } -void character::set_hitrate(const float nhitrate) { +void character::set_hitrate(const fraction nhitrate) { base_hit_rate = nhitrate; } @@ -105,11 +116,57 @@ const { return result; } -result character::apply(direction &dir, const potion_list &potions) { - // TODO: implement this after implementing potions +void character::apply(direction &dir, potion_list &plist) { + for (size_t i = 0; i < plist.size(); ++i) + if (pos + MOVE[dir] == plist[i]->get_pos()) { + insert_potion(plist[i]); + plist.erase(plist.begin() + i); + return; + } +} + +void character::insert_potion(potion *p) { + effects.push_back(p); + + for (int i = effects.size() - 1; i > 0; --i) + if (p->get_priority() < effects[i - 1]->get_priority()) + std::swap(effects[i], effects[i - 1]); +} + +result character::apply_effects() { + potion_list tmp; + tmp.reserve(effects.size()); + + for (auto p : effects) { + p->apply(this->race, HP, ATK, DEF, base_hit_rate); + + if (HP <= 0) + return result::died; + + if (p->get_duration() != 0) + tmp.push_back(p); + } + + tmp.shrink_to_fit(); + + std::swap(tmp, effects); + return result::fine; } +void character::discard_level_effects() { + potion_list tmp; + tmp.reserve(effects.size()); + + for (auto p : effects) + if (p->get_duration() != -1) + tmp.push_back(p); + + tmp.shrink_to_fit(); + + std::swap(tmp, effects); +} + // IMPORTANT: remember to check if player is on the stairs result character::move(const direction dir, const position_list &available_positions) { diff --git a/src/characters.h b/src/characters.h index 9d8ac68..e23719f 100644 --- a/src/characters.h +++ b/src/characters.h @@ -6,13 +6,12 @@ #include #include "constants.h" +#include "fraction.h" #include "position.h" -#include "potion.h" +#include "potions.h" #include "gold.h" #include "rng.h" -// #include "inventory.h" // Reserved for later - class character; // forward declaration // Note: player should not be in the character list @@ -20,7 +19,7 @@ typedef std::vector character_list; class character { public: - character(RNG &rng, const race &nrace); // fills out the starting stats + character(RNG *rng, const race &nrace); // fills out the starting stats // usually I wouldn't do this but considering that the map has // a super small size an O(n) solution is acceptable @@ -33,10 +32,13 @@ public: virtual result move_or_attack(const direction dir, const position_list &available_positions, character_list &chlist); - virtual result apply(direction &dir, - const potion_list &potions); + virtual void apply(direction &dir, + potion_list &potions); virtual result get_hit(const enum race &race, const int atk, - const float hitrate) = 0; + const fraction hitrate) = 0; + result apply_effects(); + void discard_level_effects(); + void start_turn(); enum race get_race() const; position get_position() const; @@ -44,7 +46,8 @@ public: int get_ATK() const; int get_DEF() const; int get_gold() const; - float get_hitrate() const; + float get_hitrate_real() const; + fraction get_hitrate() const; bool is_hostile() const; void set_position(const position &npos); @@ -52,7 +55,7 @@ public: void set_ATK(const int nATK); void set_DEF(const int nDEF); void set_gold(const int ngold); - void set_hitrate(const float nhitrate); + void set_hitrate(const fraction nhitrate); void set_hostile(const bool is_hostile); // if stat is hostile, positive to set it to hostile, @@ -61,7 +64,7 @@ public: // void apply_buff(const stat_name statn, const float mul); // reserved for later protected: - RNG &rng; + RNG *rng; const enum race race; int HP; @@ -69,15 +72,17 @@ protected: // IMPORTANT: keep track of ATK and DEF in game at turn time int ATK; int DEF; + fraction base_hit_rate; position pos; int gold; // characters spawn with gold - potion_list potions;// inventory inventory; // Reserved - - float base_hit_rate; // requires: between [0,1] + potion_list potions; // inventory + potion_list effects; // applied potions bool hostile; +private: + void insert_potion(potion *p); }; int calc_dmg(const int ATK, const int DEF); diff --git a/src/constants.h b/src/constants.h index 7161acb..25590e8 100644 --- a/src/constants.h +++ b/src/constants.h @@ -93,6 +93,7 @@ const feature FEATURE_MENU = 1 << 8; const feature FEATURE_IN_FILE = 1 << 9; const feature FEATURE_OUT_FILE = 1 << 10; const feature FEATURE_EXTRA_STUFF = 1 << 11; +const feature FEATURE_EXTRA_LEVELS = 1 << 12; const feature FEATURE_PANIC_SEED = 1 << 27; const feature FEATURE_PANIC_FILE = 1 << 28; diff --git a/src/fraction.cc b/src/fraction.cc index e7c1b57..c31f061 100644 --- a/src/fraction.cc +++ b/src/fraction.cc @@ -32,7 +32,7 @@ fraction &fraction::simplify() { return *this; } -float fraction::real() { +float fraction::real() const { return (float)numerator / denominator; } diff --git a/src/fraction.h b/src/fraction.h index 59cd82c..5c5a61e 100644 --- a/src/fraction.h +++ b/src/fraction.h @@ -10,7 +10,7 @@ struct fraction final { bool operator==(const fraction &frac); bool operator!=(const fraction &frac); fraction &simplify(); - float real(); + float real() const; private: int gcd(int a, int b); }; diff --git a/src/goblin.cc b/src/goblin.cc index 5431227..3155232 100644 --- a/src/goblin.cc +++ b/src/goblin.cc @@ -2,9 +2,9 @@ #include #include -goblin::goblin(RNG &rng, const position_list &available_positions): +goblin::goblin(RNG *rng, const position_list &available_positions): character{rng, race::rshade} { - pos = available_positions[rng.rand_under(available_positions.size())]; + pos = available_positions[rng->rand_under(available_positions.size())]; gold = 0; hostile = true; } @@ -27,8 +27,8 @@ result goblin::attack(const direction dir, character_list &chlist) { } result goblin::get_hit(const enum race &race, const int atk, - const float hitrate) { - if (rng.rand_num() <= hitrate * (float)RAND_MAX) + const fraction hitrate) { + if (rng->trial(hitrate)) HP = std::max(HP - calc_dmg(atk, DEF), 0); if (HP == 0) diff --git a/src/goblin.h b/src/goblin.h index 9f85e84..c416a4f 100644 --- a/src/goblin.h +++ b/src/goblin.h @@ -6,12 +6,12 @@ const int GAIN_GOLD = 5; class goblin final: public character { public: - goblin(RNG &rng, + goblin(RNG *rng, const position_list &available_positions); virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk, - const float hit_rate) override; + const fraction hit_rate) override; }; #endif diff --git a/src/main.cc b/src/main.cc index ab2e35a..f33033e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,3 +1,5 @@ +#include + #include "cc3k.h" #include "arguments.h" @@ -17,6 +19,9 @@ int main(int argc, char **argv) { FEATURE_CONFLICT | FEATURE_PANIC_SEED)) { panic_args(enabled_features); return RETURN_PANICKED; + } else if (enabled_features & FEATURE_LIST_ARGS) { + print_args_list(); + return RETURN_FINE; } // CC3K game_proc(enabled_features, *in, *out, *log, *rng); diff --git a/src/map.cc b/src/map.cc index 0977f92..6401513 100644 --- a/src/map.cc +++ b/src/map.cc @@ -87,16 +87,25 @@ game_map::game_map(const std::string &map_data, room_data.push_back({0, 0, 0, 0}); } -const position_list game_map::get_available_positions() const { - return empty_spots; -} - 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], @@ -132,6 +141,20 @@ position_list game_map::get_room_list(int idx) const { return result; } +std::vector game_map::get_room_list() const { + std::vector result; + result.reserve(room_data.size()); + + for (size_t i = 0; i < room_data.size(); ++i) + result.push_back({}); + + for (size_t i = 0; i < map.size(); ++i) + if (map[i] >= 0 && map[i] < MAX_ROOM_CNT) + result[map[i]].push_back(remap_index(i)); + + return result; +} + std::vector> game_map::gen_room_dims(RNG *rng) { position_list room_dim_list; room_dim_list.reserve(MAX_ROOM_CNT); diff --git a/src/map.h b/src/map.h index ee55ad5..f96b529 100644 --- a/src/map.h +++ b/src/map.h @@ -56,10 +56,11 @@ public: 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_available_around(const position &pos) const; position_list get_room_list(int idx) const; + std::vector get_room_list() const; // IMPORTANT: always print a map before anything else void print(display *out) const; diff --git a/src/potion.cc b/src/potion.cc index 2f9fab6..b2e5e36 100644 --- a/src/potion.cc +++ b/src/potion.cc @@ -1,7 +1,7 @@ #include "potion.h" -potion::potion(const potion_type type, const int duration): - type{type}, remaining_duration{duration} {} +potion::potion(const potion_type type, const int duration, const position &pos): + type{type}, remaining_duration{duration}, pos{pos} {} potion_type potion::get_type() const { return type; @@ -10,3 +10,33 @@ potion_type potion::get_type() const { int potion::get_duration() const { return remaining_duration; } + +position potion::get_pos() const { + return pos; +} + +void potion::set_pos(const position &npos) { + pos = npos; +} + +potion::potion(const potion &p): + type(p.type), remaining_duration(p.remaining_duration), + pos{p.pos} {} + +potion::potion(potion &&p): + type(p.type), remaining_duration(p.remaining_duration), + pos{p.pos} {} + +potion &potion::operator=(const potion &p) { + type = p.type; + remaining_duration = p.remaining_duration; + pos = p.pos; + return *this; +} + +potion &potion::operator=(potion &&p) { + type = p.type; + remaining_duration = p.remaining_duration; + pos = p.pos; + return *this; +} diff --git a/src/potion.h b/src/potion.h index 00396bb..64d6786 100644 --- a/src/potion.h +++ b/src/potion.h @@ -1,8 +1,9 @@ -#ifndef __POTIONS_H__ -#define __POTIONS_H__ +#ifndef __POTION_H__ +#define __POTION_H__ #include #include "constants.h" +#include "fraction.h" // IMPORTANT: pop all potions of duration == 0, and when entering a // new level, pop all potions of duration == -1 @@ -12,19 +13,26 @@ protected: // Use -1 to denote that the effect will last until leaving the level // Otherwise, use a positive number // Single use potions have a starting duration of 1 - const potion_type type; + potion_type type; int remaining_duration; + position pos; public: - potion(const potion_type type, const int duration); + potion(const potion &p); + potion(potion &&p); + potion &operator=(const potion &p); + potion &operator=(potion &&p); + potion(const potion_type type, const int duration, const position &pos); // apply decrements remaining_duration if it's positive, and // won't do anything if it's non-negative - virtual void apply(enum race &race, int &HP, int &ATK, int &DEF, - float &base_hit_rate) = 0; + virtual void apply(const enum race &race, int &HP, int &ATK, int &DEF, + fraction &base_hit_rate) = 0; virtual int get_priority() const = 0; potion_type get_type() const; int get_duration() const; + position get_pos() const; + void set_pos(const position &npos); }; -typedef std::vector potion_list; +typedef std::vector potion_list; #endif diff --git a/src/restore_health.cc b/src/restore_health.cc index 6d725c0..b1fa352 100644 --- a/src/restore_health.cc +++ b/src/restore_health.cc @@ -3,10 +3,10 @@ #include restore_health::restore_health(): - potion{potion_type::restore_health, -1} {} + potion{potion_type::restore_health, -1, {0, 0}} {} -void restore_health::apply(enum race &race, int &HP, int &ATK, int &DEF, - float &base_hit_rate) { +void restore_health::apply(const enum race &race, int &HP, int &ATK, int &DEF, + fraction &base_hit_rate) { if (remaining_duration > 0) { if (race == rdrow) HP = std::min(HP + 7, MAX_HP[race]); diff --git a/src/restore_health.h b/src/restore_health.h index 1e7a594..30916af 100644 --- a/src/restore_health.h +++ b/src/restore_health.h @@ -6,8 +6,8 @@ class restore_health final: public potion { public: restore_health(); - void apply(enum race &race, int &HP, int &ATK, int &DEF, - float &base_hit_rate) override; + void apply(const enum race &race, int &HP, int &ATK, int &DEF, + fraction &base_hit_rate) override; int get_priority() const override; }; diff --git a/src/rng.cc b/src/rng.cc index ee6e224..07a0105 100644 --- a/src/rng.cc +++ b/src/rng.cc @@ -32,7 +32,7 @@ int RNG::get_curr_rand_num() const { return curr_rand_num; } -bool RNG::trial(fraction &psuccess) { +bool RNG::trial(const fraction &psuccess) { return (rand() % psuccess.denominator) < psuccess.numerator; } diff --git a/src/rng.h b/src/rng.h index a1fac0b..4283770 100644 --- a/src/rng.h +++ b/src/rng.h @@ -26,7 +26,7 @@ public: unsigned int get_init_seed() const; int get_curr_rand_num() const; - bool trial(fraction &psuccess); + bool trial(const fraction &psuccess); bool coin_flip() const; diff --git a/src/shade.cc b/src/shade.cc index 1e915fe..38bbe96 100644 --- a/src/shade.cc +++ b/src/shade.cc @@ -3,9 +3,9 @@ #include #include -shade::shade(RNG &rng, const position_list &available_positions): +shade::shade(RNG *rng, const position_list &available_positions): character{rng, race::rshade} { - pos = available_positions[rng.rand_under(available_positions.size())]; + pos = available_positions[rng->rand_under(available_positions.size())]; gold = 0; hostile = true; } @@ -22,8 +22,8 @@ result shade::attack(const direction dir, character_list &chlist) { } result shade::get_hit(const enum race &race, const int atk, - const float hitrate) { - if (rng.rand_num() <= hitrate * (float)RAND_MAX) // This is a hit! + const fraction hitrate) { + if (rng->trial(hitrate)) // This is a hit! HP = std::max(HP - calc_dmg(atk, DEF), 0); if (HP == 0) diff --git a/src/shade.h b/src/shade.h index 838d437..9848045 100644 --- a/src/shade.h +++ b/src/shade.h @@ -5,12 +5,12 @@ class shade final: public character { public: - shade(RNG &rng, const position_list + shade(RNG *rng, const position_list &available_positions); // spawn at a random place virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk, - const float hit_rate) override; + const fraction hit_rate) override; }; #endif diff --git a/src/vampire.cc b/src/vampire.cc index 2d5657f..2528977 100644 --- a/src/vampire.cc +++ b/src/vampire.cc @@ -2,9 +2,9 @@ #include #include -vampire::vampire(RNG &rng, const position_list &available_positions): +vampire::vampire(RNG *rng, const position_list &available_positions): character{rng, race::rshade} { - pos = available_positions[rng.rand_under(available_positions.size())]; + pos = available_positions[rng->rand_under(available_positions.size())]; gold = 0; hostile = true; } @@ -27,8 +27,8 @@ result vampire::attack(const direction dir, character_list &chlist) { } result vampire::get_hit(const enum race &race, const int atk, - const float hitrate) { - if (rng.rand_num() <= hitrate * (float)RAND_MAX) + const fraction hitrate) { + if (rng->trial(hitrate)) HP = std::max(HP - calc_dmg(atk, DEF), 0); if (HP == 0) diff --git a/src/vampire.h b/src/vampire.h index 04a89de..b74900c 100644 --- a/src/vampire.h +++ b/src/vampire.h @@ -6,12 +6,12 @@ const int GAIN_HP = 5; class vampire final: public character { public: - vampire(RNG &rng, + vampire(RNG *rng, const position_list &available_positions); virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk, - const float hit_rate) override; + const fraction hit_rate) override; }; #endif From 9fdbf141d3dc96884d8ed1b25b23fbd239c7c46b Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Sat, 13 Jul 2024 14:02:30 -0400 Subject: [PATCH 9/9] TODO: modify other races constructors did some reworking --- src/arguments.cc | 3 ++ src/characters.cc | 20 ++++++++-- src/characters.h | 15 +++++-- src/constants.h | 22 +++++++---- src/goblin.cc | 9 ++--- src/goblin.h | 6 +-- src/gold.h | 19 +++++++++ src/level.h | 42 ++++++++++++++++---- src/map.cc | 91 +++++++++++++++++++++++-------------------- src/map.cc.bak | 68 -------------------------------- src/map.h | 11 ++++-- src/position.cc | 20 ++++------ src/position.h | 5 +-- src/potion.cc | 4 ++ src/potion.h | 3 ++ src/races.h | 1 + src/restore_health.cc | 4 +- src/restore_health.h | 2 +- src/rng.cc | 33 ++++++++++++++-- src/rng.h | 11 +++--- src/shade.cc | 9 ++--- src/shade.h | 3 +- src/vampire.cc | 9 ++--- src/vampire.h | 3 +- 24 files changed, 227 insertions(+), 186 deletions(-) delete mode 100644 src/map.cc.bak diff --git a/src/arguments.cc b/src/arguments.cc index bcfe2f2..f51c4fd 100644 --- a/src/arguments.cc +++ b/src/arguments.cc @@ -41,6 +41,8 @@ feature proc_args(int argc, char **argv, result |= FEATURE_MENU; } else if (str == "-c") { result |= FEATURE_ENEMIES_CHASE; + } else if (str == "-C") { + result |= FEATURE_COLORFUL; } else if (str == "-i") { result |= FEATURE_INVENTORY; } else if (str == "-t") { @@ -186,6 +188,7 @@ void print_args_list() { -r : Randomly generate maps\n\ -m : Enabled a main menu + options\n\ -c : Enemies chase the player (through doors and up stairs)\n\ +-C : Give things better coloring\n\ -i : Enable inventory\n\ -t : Enable throw\n\ -R : Enable revisiting levels\n\ diff --git a/src/characters.cc b/src/characters.cc index 704a058..b3473b6 100644 --- a/src/characters.cc +++ b/src/characters.cc @@ -2,11 +2,10 @@ #include -character::character(RNG *rng, const enum race &nrace): - rng{rng}, - race{nrace}, HP{STARTING_HP[race]}, +character::character(RNG *rng, const enum race &nrace, const position &pos): + rng{rng}, race{nrace}, HP{STARTING_HP[race]}, ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[race]}, - base_hit_rate{1, 1} {} + base_hit_rate{1, 1}, pos{pos} {} void character::start_turn() { ATK = STARTING_ATK[race]; @@ -14,6 +13,11 @@ void character::start_turn() { base_hit_rate = {1, 1}; } +void character::print(display *out, bool color, bool player) { + out->print_char(pos, player ? '@' : CHARACTER_REP[race], + COLOR_PAIR(COLOR_BLACK_ON_WHITE)); +} + enum race character::get_race() const { return race; } @@ -46,10 +50,18 @@ fraction character::get_hitrate() const { return base_hit_rate; } +int character::get_room_num() const { + return room_num; +} + bool character::is_hostile() const { return hostile; } +void character::set_room_num(const int room) { + room_num = room; +} + void character::set_position(const position &npos) { pos = npos; } diff --git a/src/characters.h b/src/characters.h index e23719f..5393126 100644 --- a/src/characters.h +++ b/src/characters.h @@ -6,6 +6,7 @@ #include #include "constants.h" +#include "display.h" #include "fraction.h" #include "position.h" #include "potions.h" @@ -15,11 +16,12 @@ class character; // forward declaration // Note: player should not be in the character list -typedef std::vector character_list; +typedef std::vector character_list; class character { public: - character(RNG *rng, const race &nrace); // fills out the starting stats + character(RNG *rng, const race &nrace, + const position &pos); // fills out the starting stats // usually I wouldn't do this but considering that the map has // a super small size an O(n) solution is acceptable @@ -36,6 +38,10 @@ public: potion_list &potions); virtual result get_hit(const enum race &race, const int atk, const fraction hitrate) = 0; + + // overload for different races + virtual void print(display *out, bool color = false, bool player = false); + result apply_effects(); void discard_level_effects(); void start_turn(); @@ -49,6 +55,7 @@ public: float get_hitrate_real() const; fraction get_hitrate() const; bool is_hostile() const; + int get_room_num() const; void set_position(const position &npos); void set_HP(const int nHP); @@ -56,7 +63,8 @@ public: void set_DEF(const int nDEF); void set_gold(const int ngold); void set_hitrate(const fraction nhitrate); - void set_hostile(const bool is_hostile); + virtual void set_hostile(const bool is_hostile); + void set_room_num(const int room); // if stat is hostile, positive to set it to hostile, // negative to set it to peaceful @@ -81,6 +89,7 @@ protected: potion_list effects; // applied potions bool hostile; + int room_num; private: void insert_potion(potion *p); }; diff --git a/src/constants.h b/src/constants.h index 25590e8..2e696bd 100644 --- a/src/constants.h +++ b/src/constants.h @@ -31,17 +31,19 @@ enum game_command {game_command_terminate = 0, enum stat_name {HP, ATK, DEF, hostile}; -const int RACE_CNT = 4; // TODO: update as you go +const int RACE_CNT = 5; // TODO: update as you go -enum race {rshade = 0, rvampire, rgoblin, rdrow /* TODO: fill out the other races (including enemies) */}; +enum race {rshade = 0, rvampire, rgoblin, rdrow, rdragon /* TODO: fill out the other races (including enemies) */}; // TODO: fill out the other races (including enemies) -const int MAX_HP[RACE_CNT] = {125, INF, 110, 150}; -const int STARTING_HP[RACE_CNT] = {125, 50, 110, 150}; -const int STARTING_ATK[RACE_CNT] = {25, 25, 15, 25}; -const int STARTING_DEF[RACE_CNT] = {25, 25, 20, 15}; -const char CHARACTER_REP[RACE_CNT] = {'S', 'V', 'G', 'D'}; +const int MAX_HP[RACE_CNT] = {125, INF, 110, 150, 150}; +const int STARTING_HP[RACE_CNT] = {125, 50, 110, 150, 150}; +const int STARTING_ATK[RACE_CNT] = {25, 25, 15, 25, 20}; +const int STARTING_DEF[RACE_CNT] = {25, 25, 20, 15, 20}; +const char CHARACTER_REP[RACE_CNT] = {'S', 'V', 'G', 'd', 'D'}; +const int POTION_TYPE_CNT = 6; +const int DEFAULT_POTION_TYPE_CNT = 6; enum potion_type {restore_health = 0, boost_atk, boost_def, poison_health, wound_atk, wound_def }; @@ -94,6 +96,7 @@ const feature FEATURE_IN_FILE = 1 << 9; const feature FEATURE_OUT_FILE = 1 << 10; const feature FEATURE_EXTRA_STUFF = 1 << 11; const feature FEATURE_EXTRA_LEVELS = 1 << 12; +const feature FEATURE_COLORFUL = 1 << 13; const feature FEATURE_PANIC_SEED = 1 << 27; const feature FEATURE_PANIC_FILE = 1 << 28; @@ -109,4 +112,9 @@ const int COLOR_BLACK_ON_WHITE = 8; typedef std::vector position_list; typedef std::vector direction_list; +const int GOLD_SMALL = 1; +const int GOLD_NORMAL = 2; +const int GOLD_MERCHANT = 4; +const int GOLD_DRAGON = 6; + #endif diff --git a/src/goblin.cc b/src/goblin.cc index 3155232..9abc4ac 100644 --- a/src/goblin.cc +++ b/src/goblin.cc @@ -2,9 +2,8 @@ #include #include -goblin::goblin(RNG *rng, const position_list &available_positions): - character{rng, race::rshade} { - pos = available_positions[rng->rand_under(available_positions.size())]; +goblin::goblin(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { gold = 0; hostile = true; } @@ -13,8 +12,8 @@ result goblin::attack(const direction dir, character_list &chlist) { position tmp{pos + MOVE[dir]}; for (auto &ch : chlist) - if (tmp == ch.get_position()) { - auto res = ch.get_hit(race, ATK, base_hit_rate); + if (tmp == ch->get_position()) { + auto res = ch->get_hit(race, ATK, base_hit_rate); if (res == result::died) { gold += GAIN_GOLD; diff --git a/src/goblin.h b/src/goblin.h index c416a4f..a1d81cf 100644 --- a/src/goblin.h +++ b/src/goblin.h @@ -2,12 +2,10 @@ #define __GOBLIN_H__ #include "characters.h" -const int GAIN_GOLD = 5; - class goblin final: public character { + static const int GAIN_GOLD = 5; public: - goblin(RNG *rng, - const position_list &available_positions); + goblin(RNG *rng, const position &pos); virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk, diff --git a/src/gold.h b/src/gold.h index 738f066..92bc7c8 100644 --- a/src/gold.h +++ b/src/gold.h @@ -2,7 +2,9 @@ #define __GOLD_H__ #include +#include "constants.h" #include "position.h" +#include "rng.h" struct gold { int amount; @@ -11,4 +13,21 @@ struct gold { typedef std::vector gold_list; +int rand_gold_pile(RNG *rng) { + const int denominator = 8; + const int normal = 5; + const int dragon = 6; + + int tmp = rng->rand_under(denominator); + + if (tmp < normal) + return GOLD_NORMAL; + else if (tmp < dragon) + return GOLD_DRAGON; + else + return GOLD_SMALL; + + return 0; +} + #endif diff --git a/src/level.h b/src/level.h index 4488d5f..3174861 100644 --- a/src/level.h +++ b/src/level.h @@ -2,30 +2,56 @@ #define __LEVEL_H__ #include +#include +#include #include "display.h" -#include "characters.h" -#include "potion.h" +#include "enemies.h" +#include "potions.h" #include "constants.h" #include "gold.h" #include "map.h" class level { private: + static const int MAX_POTION_CNT = 15; + static const int MAX_ENEMIE_CNT = 30; + static const int MIN_POTION_CNT = 10; + static const int MIN_ENEMIE_CNT = 20; + static const int GOLD_CNT = 10; + + const feature enabled_features; game_map map; - const character &player; + character *player; + std::vector>pchlist; + std::vector>pplist; character_list chlist; potion_list plist; gold_list glist; public: // randomly generate a map - level(const character &player); + level(character *player, RNG *rng, const feature enabled_features); // read map from a string - level(const std::string &map_data, const character &player); - void print(display &out) const; - void run(); - position_list get_available_positions() const; + level(const std::string &map_data, character *player, RNG *rng, + const feature enabled_features); + void print(display *out) const; + + bool is_available(const position &pos) const; + position_list get_available_around(const position &pos) const; position get_up_stairs() const; position get_down_stairs() const; + + // you can delete the pointers to the stuff + character_list &get_chlist(); + potion_list &get_plist(); + gold_list &get_glist(); +private: + // every gen will delete the positions in tiles + void gen_potions(RNG *rng, std::vector &tiles); + void gen_gold(RNG *rng, std::vector &tiles); + void gen_enemies(RNG *rng, std::vector &tiles); + + position get_rand_pos(RNG *rng, std::vector &tiles); + gold_list dragon_hoard(); }; #endif diff --git a/src/map.cc b/src/map.cc index 6401513..24f64e8 100644 --- a/src/map.cc +++ b/src/map.cc @@ -1,11 +1,10 @@ #include "map.h" #include -#include #include -game_map::game_map(RNG *rng, const feature enabled_features): +game_map::game_map(character *player, RNG *rng, const feature enabled_features): enabled_features{enabled_features} { map.reserve(MAP_HEIGHT * MAP_WIDTH); @@ -26,21 +25,40 @@ game_map::game_map(RNG *rng, const feature enabled_features): 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); + 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) { - auto tmp = remove_from_list(empty_spots, down_stairs); - up_stairs = rng->get_rand_in_vector(tmp); + 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, @@ -50,11 +68,15 @@ position game_map::random_size(int min_width, int min_height, rng->rand_between(min_height, max_height + 1)}; } -game_map::game_map(const std::string &map_data, +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; @@ -67,24 +89,17 @@ game_map::game_map(const std::string &map_data, } 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) { - 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)] = '\\'; + 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 { @@ -113,6 +128,14 @@ void game_map::print(display *out) const { 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; } @@ -130,29 +153,11 @@ int game_map::get_down_stairs_room() const { } position_list game_map::get_room_list(int idx) const { - position_list result; - - for (size_t i = 0; i < map.size(); ++i) { - if (map[i] == idx) - result.push_back(remap_index(i)); - } - - result.shrink_to_fit(); - return result; + return rooms_tile_list[idx]; } std::vector game_map::get_room_list() const { - std::vector result; - result.reserve(room_data.size()); - - for (size_t i = 0; i < room_data.size(); ++i) - result.push_back({}); - - for (size_t i = 0; i < map.size(); ++i) - if (map[i] >= 0 && map[i] < MAX_ROOM_CNT) - result[map[i]].push_back(remap_index(i)); - - return result; + return rooms_tile_list; } std::vector> game_map::gen_room_dims(RNG *rng) { diff --git a/src/map.cc.bak b/src/map.cc.bak deleted file mode 100644 index d9827cd..0000000 --- a/src/map.cc.bak +++ /dev/null @@ -1,68 +0,0 @@ -#include "map.h" - -#include -#include -#include -#include - -game_map::game_map(int lvl) { - level = lvl; - // TODO: randomly generate a map -} - -game_map::game_map(const std::string &map_data, int lvl) { - level = lvl; - std::istringstream iss{map_data}; - - std::string line; - - for (int i = 0; i < MAP_HEIGHT; ++i) { - getline(iss, line); - map[i] = line; - } -} - -game_map::~game_map() {} - -int game_map::get_level() const { - return this->level; -} - -std::vector game_map::get_available_positions() const { - std::vector result; - - for (int line = 0; line < MAP_HEIGHT; ++line) - for (int x = 0; x < MAP_WIDTH; ++x) - if (map[line][x] == '.' || - map[line][x] == '#' || - map[line][x] == '+') - result.push_back(position{x, line}); - return result; -} - -void game_map::print() const { - - // TODO: write a print function using ncurses -} - -void game_map::print(display &display) const { - for (int i = 0; i < MAP_HEIGHT; ++i) - display.print_str(position{0, i}, map[i]); -} - -void game_map::apply_potion(character *who, const stat_name statn, - const int amount) { - effects.push_back(effect{who, statn, amount}); - who->apply_buff(statn, amount); -} - -void game_map::enter_level(character *who) { - for (auto eff : effects) - if (eff.who == who) - who->apply_buff(eff.statn, eff.amount); -} - -void game_map::leave_level(character *who) { - who->set_ATK(STARTING_ATK[who->get_race()]); - who->set_DEF(STARTING_DEF[who->get_race()]); -} diff --git a/src/map.h b/src/map.h index f96b529..1e348ab 100644 --- a/src/map.h +++ b/src/map.h @@ -8,6 +8,7 @@ #include "position.h" #include "rng.h" #include "fraction.h" +#include "characters.h" class game_map final { private: @@ -26,10 +27,9 @@ private: const feature enabled_features; std::vector map; - // IMPORTANT: keep empty_spots ordered - position_list empty_spots; position up_stairs; position down_stairs; + std::vector rooms_tile_list; public: struct room { int top; @@ -51,13 +51,14 @@ public: } }; // randomly generate a map - game_map(RNG *rng, const feature enabled_features); + game_map(character *player, RNG *rng, const feature enabled_features); // read map from a string - game_map(const std::string &map_data, RNG *rng, + game_map(character *player, const std::string &map_data, RNG *rng, const feature enabled_features); bool is_available(const position &pos) const; position_list get_available_around(const position &pos) const; + int which_room(const position &pos) const; position_list get_room_list(int idx) const; std::vector get_room_list() const; @@ -115,6 +116,8 @@ private: void connect(position s, position t); room hit_room(const position &a); bool hit_room(const position &a, const room &r); + + void gen_stairs(int player_room, RNG *rng); }; const std::string default_map = diff --git a/src/position.cc b/src/position.cc index b30636d..452d9c2 100644 --- a/src/position.cc +++ b/src/position.cc @@ -65,16 +65,12 @@ std::vector remove_from_list(const std::vector return result; } -std::vector remove_from_list(const std::vector - &sorted_positions, - position &excluded) { - - std::vector result{sorted_positions.size() - 1}; - - for (auto src : sorted_positions) { - if (src != excluded) - result.push_back(src); - } - - return result; +void remove_from_list(std::vector + &positions, + position &excluded) { + for (auto i = positions.begin(); i != positions.end(); ++i) + if (*i == excluded) { + positions.erase(i); + return; + } } diff --git a/src/position.h b/src/position.h index b7a6d55..c796540 100644 --- a/src/position.h +++ b/src/position.h @@ -24,7 +24,6 @@ std::size_t find(const std::vector &sorted_list, std::vector remove_from_list(const std::vector &sorted_positions, std::vector excluded); -std::vector remove_from_list(const std::vector - &sorted_positions, - position &excluded); +void remove_from_list(std::vector &sorted_positions, + position &excluded); #endif diff --git a/src/potion.cc b/src/potion.cc index b2e5e36..38bf023 100644 --- a/src/potion.cc +++ b/src/potion.cc @@ -40,3 +40,7 @@ potion &potion::operator=(potion &&p) { pos = p.pos; return *this; } + +void potion::print(display *out, bool color) { + out->print_char(pos, 'P'); +} diff --git a/src/potion.h b/src/potion.h index 64d6786..9dcc92e 100644 --- a/src/potion.h +++ b/src/potion.h @@ -4,6 +4,7 @@ #include #include "constants.h" #include "fraction.h" +#include "display.h" // IMPORTANT: pop all potions of duration == 0, and when entering a // new level, pop all potions of duration == -1 @@ -31,6 +32,8 @@ public: int get_duration() const; position get_pos() const; void set_pos(const position &npos); + + virtual void print(display *out, bool color = false); }; typedef std::vector potion_list; diff --git a/src/races.h b/src/races.h index dd3862e..ddd0058 100644 --- a/src/races.h +++ b/src/races.h @@ -4,5 +4,6 @@ #include "shade.h" #include "goblin.h" #include "vampire.h" +#include "dragon.h" #endif diff --git a/src/restore_health.cc b/src/restore_health.cc index b1fa352..97cd861 100644 --- a/src/restore_health.cc +++ b/src/restore_health.cc @@ -2,8 +2,8 @@ #include -restore_health::restore_health(): - potion{potion_type::restore_health, -1, {0, 0}} {} +restore_health::restore_health(const position &pos): + potion{potion_type::restore_health, -1, pos} {} void restore_health::apply(const enum race &race, int &HP, int &ATK, int &DEF, fraction &base_hit_rate) { diff --git a/src/restore_health.h b/src/restore_health.h index 30916af..4f00a75 100644 --- a/src/restore_health.h +++ b/src/restore_health.h @@ -5,7 +5,7 @@ class restore_health final: public potion { public: - restore_health(); + restore_health(const position &pos); void apply(const enum race &race, int &HP, int &ATK, int &DEF, fraction &base_hit_rate) override; int get_priority() const override; diff --git a/src/rng.cc b/src/rng.cc index 07a0105..6a65f35 100644 --- a/src/rng.cc +++ b/src/rng.cc @@ -33,9 +33,36 @@ int RNG::get_curr_rand_num() const { } bool RNG::trial(const fraction &psuccess) { - return (rand() % psuccess.denominator) < psuccess.numerator; + return ((curr_rand_num = rand()) % psuccess.denominator) < + psuccess.numerator; } -bool RNG::coin_flip() const { - return rand() % 2; +bool RNG::coin_flip() { + return (curr_rand_num = rand()) % 2; +} + +int RNG::exclude_middle(const int lower_bound, const int upper_bound, + const int excluded) { + curr_rand_num = rand(); + int tmp = (curr_rand_num % (upper_bound - lower_bound - 1)); + return tmp >= excluded ? tmp + 1 : tmp; +} + +template T RNG::rand_exclude(std::vector &vec, T &target) { + std::size_t idx = 0; + + for (; idx < vec.size(); ++idx) + if (vec[idx] == target) + break; + + return vec[exclude_middle(0, vec.size(), idx)]; +} + +template T RNG::get_rand_in_vector(std::vector &vec) { + if (!vec.size()) + return T{}; + + curr_rand_num = rand(); + + return vec[curr_rand_num % vec.size()]; } diff --git a/src/rng.h b/src/rng.h index 4283770..aba6324 100644 --- a/src/rng.h +++ b/src/rng.h @@ -28,13 +28,14 @@ public: bool trial(const fraction &psuccess); - bool coin_flip() const; + bool coin_flip(); - template T &get_rand_in_vector(std::vector &vec) { - curr_rand_num = rand(); + int exclude_middle(const int lower_bound, const int upper_bound, + const int excluded); - return vec[curr_rand_num % vec.size()]; - } + template T rand_exclude(std::vector &vec, T &target); + + template T get_rand_in_vector(std::vector &vec); }; #endif diff --git a/src/shade.cc b/src/shade.cc index 38bbe96..9fcac64 100644 --- a/src/shade.cc +++ b/src/shade.cc @@ -3,9 +3,8 @@ #include #include -shade::shade(RNG *rng, const position_list &available_positions): - character{rng, race::rshade} { - pos = available_positions[rng->rand_under(available_positions.size())]; +shade::shade(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { gold = 0; hostile = true; } @@ -14,8 +13,8 @@ result shade::attack(const direction dir, character_list &chlist) { position tmp{pos + MOVE[dir]}; for (auto &ch : chlist) - if (tmp == ch.get_position()) { - return ch.get_hit(race, ATK, base_hit_rate); + if (tmp == ch->get_position()) { + return ch->get_hit(race, ATK, base_hit_rate); } return result::fine; diff --git a/src/shade.h b/src/shade.h index 9848045..59a748c 100644 --- a/src/shade.h +++ b/src/shade.h @@ -5,8 +5,7 @@ class shade final: public character { public: - shade(RNG *rng, const position_list - &available_positions); // spawn at a random place + shade(RNG *rng, const position &pos); // spawn at a random place virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk, diff --git a/src/vampire.cc b/src/vampire.cc index 2528977..705a69f 100644 --- a/src/vampire.cc +++ b/src/vampire.cc @@ -2,9 +2,8 @@ #include #include -vampire::vampire(RNG *rng, const position_list &available_positions): - character{rng, race::rshade} { - pos = available_positions[rng->rand_under(available_positions.size())]; +vampire::vampire(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { gold = 0; hostile = true; } @@ -13,8 +12,8 @@ result vampire::attack(const direction dir, character_list &chlist) { position tmp{pos + MOVE[dir]}; for (auto &ch : chlist) - if (tmp == ch.get_position()) { - auto res = ch.get_hit(race, ATK, base_hit_rate); + if (tmp == ch->get_position()) { + auto res = ch->get_hit(race, ATK, base_hit_rate); if (res != result::miss) { HP += GAIN_HP; diff --git a/src/vampire.h b/src/vampire.h index b74900c..7a68922 100644 --- a/src/vampire.h +++ b/src/vampire.h @@ -6,8 +6,7 @@ const int GAIN_HP = 5; class vampire final: public character { public: - vampire(RNG *rng, - const position_list &available_positions); + vampire(RNG *rng, const position &pos); virtual result attack(const direction dir, character_list &chlist) override; virtual result get_hit(const enum race &race, const int atk,