diff --git a/src/Makefile b/src/Makefile index 68df479..c00e7cc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -21,7 +21,7 @@ EXEC = ../bin/cc3k # executable name .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/arguments.cc b/src/arguments.cc index d0fe958..d5f3177 100644 --- a/src/arguments.cc +++ b/src/arguments.cc @@ -34,22 +34,28 @@ feature proc_args(int argc, char **argv, if (result & (FEATURE_IN_FILE | FEATURE_OUT_FILE)) return FEATURE_CONFLICT | i; - result |= FEATURE_NCURSES; - } else if (str == "-r") { - result |= FEATURE_RAND_MAP; - } else if (str == "-m") { - result |= FEATURE_MENU; - } else if (str == "-c") { - result |= FEATURE_ENEMIES_CHASE; - } else if (str == "-i") { - result |= FEATURE_INVENTORY; - } else if (str == "-t") { - result |= FEATURE_THROW; - } else if (str == "-R") { - result |= FEATURE_REVISIT; - } else if (str == "-s") { - ++i; - str = argv[i]; + result |= FEATURE_NCURSES; + } else if (str == "-r") { + result |= FEATURE_RAND_MAP; + } else if (str == "-m") { + 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") { + result |= FEATURE_THROW; + } else if (str == "-R") { + 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]; if (!seed.empty() && seed != str) return FEATURE_CONFLICT | i; @@ -119,11 +125,11 @@ feature proc_args(int argc, char **argv, log = std::make_unique(std::move(lout)); } - if (result & FEATURE_NCURSES) { - curse = std::make_unique(); - in = std::make_unique(*curse); - out = std::make_unique(*curse); - } + if (result & FEATURE_NCURSES) { + curse = std::make_unique(); + in = std::make_unique(curse.get()); + out = std::make_unique(curse.get()); + } if (result & FEATURE_SEED) { std::istringstream iss {seed}; @@ -163,13 +169,35 @@ void panic_args(feature panic) { cerr << "Cannot open specified log file!" << endl; break; - default: - cerr << "Something must have went really, really, wrong..." - << endl; - } - } else if (panic & FEATURE_PANIC_SEED) { - cerr << "Invalid seed" << endl; - } else - cerr << "Something must have went really, really, wrong..." - << endl; + default: + cerr << "Something must have went really, really, wrong..." + << endl; + break; + } + } else if (panic & FEATURE_PANIC_SEED) { + cerr << "Invalid seed" << endl; + } else + 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\ +-C : Give things better coloring\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 8226b97..e4e5837 100644 --- a/src/arguments.h +++ b/src/arguments.h @@ -9,21 +9,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 --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, @@ -34,4 +19,6 @@ feature proc_args(int argc, char **argv, void panic_args(feature panic); +void print_args_list(); + #endif 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 1e42ecd..7efeb4a 100644 --- a/src/characters.cc +++ b/src/characters.cc @@ -2,10 +2,21 @@ #include -character::character(RNG &rng, const enum race &nrace): - rng{rng}, - race{nrace}, HP{STARTING_HP[race]}, - ATK{STARTING_ATK[race]}, DEF{STARTING_DEF[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}, pos{pos} {} + +void character::start_turn() { + ATK = STARTING_ATK[race]; + DEF = STARTING_DEF[race]; + 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; @@ -31,14 +42,26 @@ int character::get_gold() const { return gold; } -float character::get_hitrate() const { - return base_hit_rate; +float character::get_hitrate_real() const { + return base_hit_rate.real(); +} + +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; } @@ -59,8 +82,8 @@ void character::set_gold(const int ngold) { gold = ngold; } -void character::set_hitrate(const float nhitrate) { - base_hit_rate = nhitrate; +void character::set_hitrate(const fraction nhitrate) { + base_hit_rate = nhitrate; } void character::set_hostile(const bool is_hostile) { @@ -105,27 +128,55 @@ const { return result; } -result character::apply(direction &dir, const potion_list &potions) { - // TODO: implement this after implementing potions - return result::fine; +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; + } } -position_list remove_from_list(const position_list &sorted_positions, - position_list excluded) { - std::sort(excluded.begin(), excluded.end()); +void character::insert_potion(potion *p) { + effects.push_back(p); - position_list result{sorted_positions.size() - excluded.size()}; + 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]); +} - auto exc = excluded.begin(); +result character::apply_effects() { + potion_list tmp; + tmp.reserve(effects.size()); - for (auto src : sorted_positions) { - if (exc != excluded.end() && src == *exc) - ++exc; - else - result.push_back(src); - } + for (auto p : effects) { + p->apply(this->race, HP, ATK, DEF, base_hit_rate); - return result; + 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 diff --git a/src/characters.h b/src/characters.h index 07e30d5..15c0952 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 @@ -12,53 +6,65 @@ #include #include "constants.h" +#include "display.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 -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 - // IMPORTANT: available_positions do NOT have characters in them - direction_list moveable(const position_list &available_positions) const; - virtual result move(const direction dir, - const position_list &available_positions); - virtual result attack(const direction dir, - character_list &chlist) = 0; - 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 result get_hit(const enum race &race, const int atk, - const float hitrate) = 0; + // usually I wouldn't do this but considering that the map has + // a super small size an O(n) solution is acceptable + // IMPORTANT: available_positions do NOT have characters in them + direction_list moveable(const position_list &available_positions) const; + virtual result move(const direction dir, + const position_list &available_positions); + virtual result attack(const direction dir, + character_list &chlist) = 0; + virtual result move_or_attack(const direction dir, + const position_list &available_positions, + character_list &chlist); + virtual void apply(direction &dir, + potion_list &potions); + virtual result get_hit(const enum race &race, const int atk, + const fraction hitrate) = 0; - enum race get_race() const; - position get_position() const; - int get_HP() const; - int get_ATK() const; - int get_DEF() const; - int get_gold() const; - float get_hitrate() const; - bool is_hostile() const; + // overload for different races + virtual void print(display *out, bool color = false, bool player = false); - void set_position(const position &npos); - void set_HP(const int nHP); - 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_hostile(const bool is_hostile); + result apply_effects(); + void discard_level_effects(); + void start_turn(); + + enum race get_race() const; + position get_position() const; + int get_HP() const; + int get_ATK() const; + int get_DEF() const; + int get_gold() const; + 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); + void set_ATK(const int nATK); + void set_DEF(const int nDEF); + void set_gold(const int ngold); + void set_hitrate(const fraction nhitrate); + 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 @@ -66,29 +72,28 @@ public: // void apply_buff(const stat_name statn, const float mul); // reserved for later protected: - RNG &rng; - const enum race race; + RNG *rng; + const enum race race; int HP; - // IMPORTANT: keep track of ATK and DEF in game at turn time - int ATK; - int DEF; + // 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 + int gold; // characters spawn with gold + potion_list potions; // inventory + potion_list effects; // applied potions - float base_hit_rate; // requires: between [0,1] - - bool hostile; + bool hostile; + int room_num; +private: + void insert_potion(potion *p); }; -// 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 6f9b6f2..d70f9ef 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 }; @@ -90,6 +92,9 @@ 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_EXTRA_LEVELS = 1 << 12; +const feature FEATURE_COLORFUL = 1 << 13; const feature FEATURE_PANIC_SEED = 1 << 27; const feature FEATURE_PANIC_FILE = 1 << 28; @@ -104,6 +109,10 @@ const int COLOR_BLACK_ON_WHITE = 8; typedef std::vector position_list; typedef std::vector direction_list; -typedef std::vector gold_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/curses_input.cc b/src/curses_input.cc index 7d077db..d6da0ce 100644 --- a/src/curses_input.cc +++ b/src/curses_input.cc @@ -1,12 +1,12 @@ #include "curses_input.h" -curses_input::curses_input(cursor &new_curse): - curse{new_curse} {} +curses_input::curses_input(cursor *new_curse): + curse{new_curse} {} game_command curses_input::get_command() { - switch (curse.getcmd()) { - case 'h': - return game_command::move_west; + switch (curse->getcmd()) { + case 'h': + return game_command::move_west; case 'j': return game_command::move_south; @@ -51,9 +51,9 @@ game_command curses_input::get_command() { return game_command_pass; } - switch (curse.getcmd()) { - case 'h': - return game_command::apply_west; + switch (curse->getcmd()) { + case 'h': + return game_command::apply_west; case 'j': return game_command::apply_south; diff --git a/src/curses_input.h b/src/curses_input.h index c36e8df..3d34d5c 100644 --- a/src/curses_input.h +++ b/src/curses_input.h @@ -8,10 +8,10 @@ class curses_input final: public input { private: - cursor &curse; + cursor *curse; public: - curses_input(cursor &new_curse); - game_command get_command() override; + curses_input(cursor *new_curse); + game_command get_command() override; }; #endif diff --git a/src/curses_output.cc b/src/curses_output.cc index c6e8205..06409a7 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): - curse{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, @@ -26,9 +26,9 @@ 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); - tmp += {1, 0}; + for (std::size_t i = 0; i < str.length(); ++i) { + curse->print_char(tmp, str[i], attrs); + tmp += {1, 0}; if (tmp.x >= DISPLAY_WIDTH) tmp = {0, tmp.y + 1}; diff --git a/src/curses_output.h b/src/curses_output.h index 09703f1..13dbd06 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/fraction.cc b/src/fraction.cc index 5dfbab2..595d33f 100644 --- a/src/fraction.cc +++ b/src/fraction.cc @@ -32,8 +32,8 @@ fraction &fraction::simplify() { return *this; } -float fraction::real() { - return (float)numerator / denominator; +float fraction::real() const { + return (float)numerator / denominator; } int fraction::gcd(int a, int b) { diff --git a/src/fraction.h b/src/fraction.h index ae61b36..65e686e 100644 --- a/src/fraction.h +++ b/src/fraction.h @@ -5,12 +5,12 @@ struct fraction final { int numerator; int denominator; - fraction operator+(const fraction &frac); - fraction operator*(const fraction &frac); - bool operator==(const fraction &frac); - bool operator!=(const fraction &frac); - fraction &simplify(); - float real(); + fraction operator+(const fraction &frac); + fraction operator*(const fraction &frac); + bool operator==(const fraction &frac); + bool operator!=(const fraction &frac); + fraction &simplify(); + float real() const; private: int gcd(int a, int b); }; diff --git a/src/game.h b/src/game.h index 0b87547..0137e11 100644 --- a/src/game.h +++ b/src/game.h @@ -12,19 +12,21 @@ class game final { private: - feature features; - input ∈ - display &out; - logger &log; - RNG &rng; - std::unique_ptr player; + feature features; + 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); - game_status run(); + game(const feature enabled_features, + input *new_in, + display *new_out, + logger *new_log, + RNG *new_rng); + game_status run(); private: int getcmd() const; }; diff --git a/src/goblin.cc b/src/goblin.cc index ae6c958..1e8e87d 100644 --- a/src/goblin.cc +++ b/src/goblin.cc @@ -2,19 +2,18 @@ #include #include -goblin::goblin(RNG &rng, const position_list &available_positions): - character{rng, race::rshade} { - pos = available_positions[rng.rand_under(available_positions.size())]; - gold = 0; - hostile = true; +goblin::goblin(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { + gold = 0; + hostile = true; } 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); + for (auto &ch : chlist) + if (tmp == ch->get_position()) { + auto res = ch->get_hit(race, ATK, base_hit_rate); if (res == result::died) { gold += GAIN_GOLD; @@ -27,9 +26,15 @@ result goblin::attack(const direction dir, character_list &chlist) { } result goblin::get_hit(const enum race &race, const int atk, +<<<<<<< HEAD const float hitrate) { if (rng.rand_num() <= hitrate * (float)RAND_MAX) HP = std::max(HP - calc_dmg(atk, DEF), 0); +======= + const fraction hitrate) { + if (rng->trial(hitrate)) + HP = std::max(HP - calc_dmg(atk, DEF), 0); +>>>>>>> paul if (HP == 0) return result::died; diff --git a/src/goblin.h b/src/goblin.h index 97d2e7a..a1d81cf 100644 --- a/src/goblin.h +++ b/src/goblin.h @@ -2,16 +2,14 @@ #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); - 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; + 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, + const fraction hit_rate) override; }; #endif diff --git a/src/gold.h b/src/gold.h new file mode 100644 index 0000000..92bc7c8 --- /dev/null +++ b/src/gold.h @@ -0,0 +1,33 @@ +#ifndef __GOLD_H__ +#define __GOLD_H__ + +#include +#include "constants.h" +#include "position.h" +#include "rng.h" + +struct gold { + int amount; + position pos; +}; + +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 dff3881..3174861 100644 --- a/src/level.h +++ b/src/level.h @@ -1,6 +1,57 @@ #ifndef __LEVEL_H__ #define __LEVEL_H__ -class level; +#include +#include +#include +#include "display.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; + character *player; + std::vector>pchlist; + std::vector>pplist; + character_list chlist; + potion_list plist; + gold_list glist; +public: + // randomly generate a map + level(character *player, RNG *rng, const feature enabled_features); + // read map from a string + 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/main.cc b/src/main.cc index a1a9ff3..5cff79f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,4 +1,6 @@ -#include "game.h" +#include + +#include "cc3k.h" #include "arguments.h" int main(int argc, char **argv) { @@ -14,14 +16,19 @@ int main(int argc, char **argv) { if (enabled_features & (FEATURE_PANIC | FEATURE_PANIC_FILE | FEATURE_CONFLICT | FEATURE_PANIC_SEED)) { - panic_args(enabled_features); - return RETURN_PANICKED; - } + panic_args(enabled_features); + return RETURN_PANICKED; + } else if (enabled_features & FEATURE_LIST_ARGS) { + print_args_list(); + return RETURN_FINE; + } - 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) - out->render(); + // 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 5e8628d..24f64e8 100644 --- a/src/map.cc +++ b/src/map.cc @@ -1,68 +1,559 @@ #include "map.h" -#include -#include -#include #include -game_map::game_map(int lvl) { - level = lvl; - // TODO: randomly generate a map +#include + +game_map::game_map(character *player, RNG *rng, const feature enabled_features): + enabled_features{enabled_features} { + map.reserve(MAP_HEIGHT * MAP_WIDTH); + + for (int i = MAP_HEIGHT * MAP_WIDTH; i > 0; --i) + map.push_back(' '); + + // Note: during generation, walls DO NOT count as being in the rooms + std::vector> room_dims = gen_room_dims(rng); + + std::vector> layer_data; + // width height top left + room_data = distr_rooms(rng, room_dims, layer_data); + + for (size_t r = 0; r < room_data.size(); ++r) + fill_room(room_data[r], r); + + fill_outline(); + + gen_path(layer_data, rng); + + + rooms_tile_list.reserve(room_data.size()); + + for (size_t i = 0; i < room_data.size(); ++i) + rooms_tile_list.push_back({}); + + for (size_t i = 0; i < map.size(); ++i) + if (map[i] >= 0 && map[i] < MAX_ROOM_CNT) + rooms_tile_list[map[i]].push_back(remap_index(i)); + + for (size_t i = 0; i < rooms_tile_list.size(); ++i) + rooms_tile_list[i].shrink_to_fit(); + + rooms_tile_list.shrink_to_fit(); + + int player_room = rng->rand_under(room_data.size()); + player->set_position(rng->get_rand_in_vector(rooms_tile_list[player_room])); + + gen_stairs(player_room, rng); } -game_map::game_map(const std::string &map_data, int lvl) { - level = lvl; - std::istringstream iss{map_data}; +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)]); - std::string line; + if (enabled_features & FEATURE_REVISIT) { + up_stairs = rng->get_rand_in_vector(rooms_tile_list[player_room]); + + map[remap_position(up_stairs)] = '<'; + map[remap_position(down_stairs)] = '>'; + } else { + map[remap_position(down_stairs)] = '\\'; + } - 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; +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)}; } -std::vector game_map::get_available_positions() const { - std::vector result; +game_map::game_map(character *player, const std::string &map_data, + RNG *rng, const feature enabled_features): + enabled_features{enabled_features} { + int map_size = MAP_HEIGHT * MAP_WIDTH; + map.reserve(map_size); + rooms_tile_list.reserve(MAX_ROOM_CNT); + + for (int i = 0; i < MAX_ROOM_CNT; ++i) + rooms_tile_list.push_back({}); + + int max_room_num = 0; + + for (int i = 0; i < map_size; ++i) { + if (map_data[i] >= '0' && map_data[i] <= '9') { + map.push_back(map_data[i] - '0'); + max_room_num = std::max(max_room_num, map_data[i] - '0'); + } else { + map.push_back(map_data[i]); + } + + if (map_data[i] >= '0' && map_data[i] <= '9') + rooms_tile_list[map_data[i] - '0'].push_back(remap_index(i)); + } + + for (int i = 0; i <= max_room_num; ++i) + room_data.push_back({0, 0, 0, 0}); + + int player_room = rng->rand_under(room_data.size()); + player->set_position(rng->get_rand_in_vector(rooms_tile_list[player_room])); + + gen_stairs(player_room, rng); - 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 { +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; - // TODO: write a print function using ncurses + int idx = remap_position(pos); + return map[idx] < MAX_ROOM_CNT || map[idx] == '\\' || map[idx] == '>' || + map[idx] == '<' || map[idx] == '+' || map[idx] == '#'; } -void game_map::print(display &display) const { - for (int i = 0; i < MAP_HEIGHT; ++i) - display.print_str(position{0, i}, map[i]); +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::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::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); } -void game_map::enter_level(character *who) { - for (auto eff : effects) - if (eff.who == who) - who->apply_buff(eff.statn, eff.amount); +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; } -void game_map::leave_level(character *who) { - who->set_ATK(STARTING_ATK[who->get_race()]); - who->set_DEF(STARTING_DEF[who->get_race()]); +position game_map::get_up_stairs() const { + return up_stairs; +} + +position game_map::get_down_stairs() const { + return down_stairs; +} + +int game_map::get_up_stairs_room() const { + return map[remap_position(up_stairs)]; +} + +int game_map::get_down_stairs_room() const { + return map[remap_position(down_stairs)]; +} + +position_list game_map::get_room_list(int idx) const { + return rooms_tile_list[idx]; +} + +std::vector game_map::get_room_list() const { + return rooms_tile_list; +} + +std::vector> game_map::gen_room_dims(RNG *rng) { + position_list room_dim_list; + room_dim_list.reserve(MAX_ROOM_CNT); + std::vector> result; + result.reserve(MAX_ROOM_CNT); + + for (int i = 0; i < MAX_ROOM_CNT; ++i) + room_dim_list.push_back(random_size(MIN_ROOM_WIDTH, MIN_ROOM_HEIGHT, + MAX_ROOM_WIDTH, MAX_ROOM_HEIGHT, + rng)); + + int curr_top = 0; + int curr_left = 0; + // index in result + int curr_layer_idx = 0; + int curr_layer_cnt = 0; + + for (int i = 0; i < MAX_ROOM_CNT && + curr_top <= ACTUAL_MAP_HEIGHT && + curr_left <= ACTUAL_MAP_WIDTH; ++i) { + // DO NOT update curr_top until curr_left is exhausted + if (curr_top + MIN_ROOM_SPACING + + room_dim_list[i].y + HEIGHT_RESERVED <= ACTUAL_MAP_HEIGHT && + curr_left + MIN_ROOM_SPACING + + room_dim_list[i].x + WIDTH_RESERVED <= ACTUAL_MAP_WIDTH) { + result.push_back({room_dim_list[i], curr_layer_cnt}); + curr_left += MIN_ROOM_SPACING + room_dim_list[i].x; + continue; + } + + int tmp = curr_top; + + for (std::size_t j = curr_layer_idx; j < result.size(); ++j) + tmp = std::max(curr_top + result[j].first.y + MIN_ROOM_SPACING, + tmp); + + curr_top = tmp; + curr_layer_idx = result.size(); + curr_left = 0; + ++curr_layer_cnt; + + if (curr_top + MIN_ROOM_SPACING + + room_dim_list[i].y + HEIGHT_RESERVED <= ACTUAL_MAP_HEIGHT && + curr_left + MIN_ROOM_SPACING + + room_dim_list[i].x + WIDTH_RESERVED <= ACTUAL_MAP_WIDTH) { + result.push_back({room_dim_list[i], curr_layer_cnt}); + curr_left += MIN_ROOM_SPACING + room_dim_list[i].x; + } + } + + result.shrink_to_fit(); + + return result; +} + +position random_size(int min_width, int min_height, + int max_width, int max_height, + RNG *rng) { + return {rng->rand_between(min_width, max_width + 1), + rng->rand_between(min_height, max_height + 1)}; +} + +void game_map::fill_room(const room &r, const int num) { + for (int x = 0; x < r.width; ++x) + for (int y = 0; y < r.height; ++y) + map[remap_position({r.left + x, + r.top + y})] = num; + + for (int x = 0; x < r.width; ++x) { + map[remap_position({r.left + x, + r.top - 1})] = '-'; + map[remap_position({r.left + x, + r.top + + r.height})] = '-'; + } + + for (int y = -1; y <= r.height; ++y) { + map[remap_position({r.left - 1, + r.top + y})] = '|'; + map[remap_position({r.left + + r.width, + r.top + y})] = '|'; + } +} + +std::vector game_map::distr_rooms(RNG *rng, + std::vector> &room_dims, + std::vector> &layer_data) { + std::vector result; + result.reserve(room_dims.size()); + + int max_layer = room_dims[room_dims.size() - 1].second; + // left, right, height + layer_data.reserve(max_layer + 1); + + // distributing rooms horizontally + for (int layer = 0; layer <= max_layer; ++layer) { + int l = INF; + int r = 0; + int layer_height = 0; + + // get the interval for the current layer + // and the max height of the layer + for (std::size_t i = 0; i < room_dims.size(); ++i) + if (room_dims[i].second == layer) { + l = std::min(l, (int)i); + r = std::max(r, (int)i); + layer_height = std::max(layer_height, + room_dims[i].first.y); + } + + layer_data.push_back({{l, r}, layer_height}); + + // distribute the current layer + if (l == r) { + result.push_back({ + 0, rng->rand_under(ACTUAL_MAP_WIDTH - + room_dims[l].first.x + 1), + room_dims[l].first.x, + room_dims[l].first.y, {0, 0}, {0, 0}}); + continue; + } + + int left = 0; + int right = ACTUAL_MAP_WIDTH; + + // every time, distribute the last one first + for (int i = l; i < r; ++i) + left += MIN_ROOM_SPACING + room_dims[i].first.x; + + for (int i = r; i >= l; --i) { + int offset = rng->rand_between(left, right - room_dims[i].first.x + 1); + result.push_back({0, offset, + room_dims[i].first.x, + room_dims[i].first.y, {0, 0}, {0, 0}}); + + right = offset - MIN_ROOM_SPACING; + + if (i != l) + left -= MIN_ROOM_SPACING + room_dims[i - 1].first.x; + } + } + + // distributing rooms vertically + int top = 0; + int bottom = ACTUAL_MAP_HEIGHT; + + for (int i = 0; i < max_layer; ++i) + top += MIN_ROOM_SPACING + layer_data[i].second; + + for (int i = max_layer; i >= 0; --i) { + int offset = rng->rand_between(top, + bottom - layer_data[i].second + 1); + + for (int j = layer_data[i].first.x; + j <= layer_data[i].first.y; ++j) + result[j].top = offset; + + bottom = offset - MIN_ROOM_SPACING; + + if (i != 0) + top -= MIN_ROOM_SPACING + layer_data[i - 1].second; + } + + // add some vertical jitter + jitter(rng, result); + + //add padding + for (std::size_t i = 0; i < result.size(); ++i) { + result[i].top += MAP_PADDING; + result[i].left += MAP_PADDING; + } + + return result; +} + +void game_map::jitter(RNG *rng, + std::vector &rooms) { + for (int n = rooms.size() - 1; n >= 0; --n) { + int t = 0; + int b = ACTUAL_MAP_HEIGHT - 1; + + for (std::size_t i = 0; i < rooms.size(); ++i) { + if (rooms[i] == rooms[n]) + continue; + + if (overlap_x(rooms[n], rooms[i])) { + if (rooms[n].top > rooms[i].top) + t = std::max(t, + rooms[i].top + + rooms[i].height + + MIN_ROOM_SPACING); + else + b = std::min(b, + rooms[i].top - + MIN_ROOM_SPACING); + } + } + + if (t < b - rooms[n].height + 1) + rooms[n].top = rng->rand_between(t, + b - rooms[n].height + 1); + } +} + +bool game_map::overlap_x(room &room1, + room &room2) { + return (room1.left >= room2.left && + room1.left - MIN_ROOM_SPACING <= + room2.left + room2.width) || + (room2.left >= room1.left && + room2.left - MIN_ROOM_SPACING <= + room1.left + room1.width); +} + +bool game_map::overlap_y(room &room1, + room &room2) { + return (room1.top >= room2.top && + room1.top - MIN_ROOM_SPACING <= + room2.top + room2.height) || + (room2.top >= room1.top && + room2.top - MIN_ROOM_SPACING <= + room1.top + room1.height); +} + +void game_map::fill_outline() { + for (int x = 0; x < MAP_WIDTH; ++x) { + map[remap_position({x, 0})] = '-'; + map[remap_position({x, MAP_HEIGHT - 1})] = '-'; + } + + + for (int y = 0; y < MAP_HEIGHT; ++y) { + map[remap_position({0, y})] = '|'; + map[remap_position({MAP_WIDTH - 1, y})] = '|'; + } +} + +int game_map::get_room_cnt() const { + return room_data.size(); +} + +game_map::room game_map::get_room(std::size_t idx) const { + if (idx < room_data.size()) + return room_data[idx]; + + return {0, 0, 0, 0, {0, 0}, {0, 0}}; +} + +void game_map::gen_path(std::vector> &layer_data, + RNG *rng) { + for (std::size_t l = 0; l < layer_data.size(); ++l) { + for (int i = layer_data[l].first.x; i < layer_data[l].first.y; ++i) + connect_x(room_data[i + 1], room_data[i], rng); + + if (l < layer_data.size() - 1) { + connect_y(room_data[layer_data[l].first.x], + room_data[layer_data[l + 1].first.x], rng); + connect_y(room_data[layer_data[l].first.y], + room_data[layer_data[l + 1].first.y], rng); + connect_y(room_data[(layer_data[l].first.x + + layer_data[l].first.y) / 2], + room_data[(1 + layer_data[l + 1].first.x + + layer_data[l + 1].first.y) / 2], rng); + } + } +} + +void game_map::connect_x(room &room1, room &room2, RNG *rng) { + room1.rdoor = {room1.left + room1.width, + rng->rand_between(room1.top, room1.top + room1.height) + }; + room2.ldoor = {room2.left - 1, + rng->rand_between(room2.top, room2.top + room2.height) + }; + map[remap_position(room1.rdoor)] = '+'; + map[remap_position(room2.ldoor)] = '+'; + connect({room1.rdoor.x + 1, room1.rdoor.y}, + {room2.ldoor.x - 1, room2.ldoor.y}); +} + +void game_map::connect_y(room &room1, room &room2, RNG *rng) { + room1.bdoor = {rng->rand_between(room1.left, room1.left + room1.width), + room1.top + room1.height + }; + room2.tdoor = {rng->rand_between(room2.left, room2.left + room2.width), + room2.top - 1 + }; + map[remap_position(room1.bdoor)] = '+'; + map[remap_position(room2.tdoor)] = '+'; + connect({room1.bdoor.x, room1.bdoor.y + 1}, + {room2.tdoor.x, room2.tdoor.y - 1}); +} + +// a is to the left of b +void game_map::connect(position s, position t) { + if (s.x > t.x) { + connect(t, s); + return; + } + + map[remap_position(s)] = '#'; + map[remap_position(t)] = '#'; + + const room nil{0, 0, 0, 0}; + + while (s.x < t.x) { + auto r = hit_room({s.x + 1, s.y}); + + if (r == nil) { + ++s.x; + map[remap_position(s)] = '#'; + continue; + } + + if (r.top + r.height - s.y < r.top - 1) { + while (hit_room({s.x + 1, s.y}, r)) { + ++s.y; + map[remap_position(s)] = '#'; + } + } else { + while (hit_room({s.x + 1, s.y}, r)) { + --s.y; + map[remap_position(s)] = '#'; + } + } + } + + if (s.y < t.y) { + while (s.y < t.y) { + auto r = hit_room({s.x, s.y + 1}); + + if (r == nil) { + ++s.y; + map[remap_position(s)] = '#'; + continue; + } + + while (hit_room({s.x, s.y + 1}, r)) { + ++s.x; + map[remap_position(s)] = '#'; + } + + while (hit_room({s.x - 1, s.y + 1}, r)) { + ++s.y; + map[remap_position(s)] = '#'; + } + + while (hit_room({s.x, s.y - 1}, r) && s.x > t.x) { + --s.x; + map[remap_position(s)] = '#'; + } + ++s.y; + map[remap_position(s)] = '#'; + } + } else { + while (s.y > t.y) { + auto r = hit_room({s.x, s.y - 1}); + + if (r == nil) { + --s.y; + map[remap_position(s)] = '#'; + continue; + } + + while (hit_room({s.x, s.y - 1}, r)) { + ++s.x; + map[remap_position(s)] = '#'; + } + + while (hit_room({s.x - 1, s.y - 1}, r)) { + --s.y; + map[remap_position(s)] = '#'; + } + + while (hit_room({s.x, s.y + 1}, r) && s.x > t.x) { + --s.x; + map[remap_position(s)] = '#'; + } + } + } +} + +game_map::room game_map::hit_room(const position &a) { + for (auto r : room_data) { + if (hit_room(a, r)) + return r; + } + + const room nil{0, 0, 0, 0}; + return nil; +} + +bool game_map::hit_room(const position &a, const room &r) { + return a.y >= r.top - 1 && a.y <= r.top + r.height && + a.x >= r.left - 1 && a.x <= r.left + r.width; } diff --git a/src/map.h b/src/map.h index 501ae0a..1e348ab 100644 --- a/src/map.h +++ b/src/map.h @@ -1,60 +1,150 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles map functionality - */ - #ifndef __MAP_H__ #define __MAP_H__ #include #include +#include #include "constants.h" #include "display.h" #include "position.h" +#include "rng.h" +#include "fraction.h" #include "characters.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 = 5; + // setup safezones + 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 = 6; + static const int HEIGHT_RESERVED = 3; + + const feature enabled_features; + std::vector map; + position up_stairs; + position down_stairs; + std::vector rooms_tile_list; 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 + 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); + } + bool operator==(const room &r) { + return top == r.top && + left == r.left && + width == r.width && + height == r.height; + } + }; + // randomly generate a map + game_map(character *player, RNG *rng, const feature enabled_features); + // read map from a string + game_map(character *player, const std::string &map_data, RNG *rng, + const feature enabled_features); - int get_level() const; + 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_available_positions() 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; - // IMPORTANT: always print a map before anything else + position get_up_stairs() const; + position get_down_stairs() const; - // prints using ncurses - void print() const; + int get_up_stairs_room() const; + int get_down_stairs_room() const; - // prints to a string - void print(display &display) 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}; + } - // 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); + int remap_position(const position &pos) const { + return pos.y * MAP_WIDTH + pos.x; + } - // 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; - }; + position random_size(int min_width, int min_height, + int max_width, int max_height, + RNG *rng); - std::string map[MAP_HEIGHT]; - int level; + std::vector> gen_room_dims(RNG *rng); + std::vector distr_rooms(RNG *rng, + std::vector> &room_dims, + std::vector> &layer_data); - // use this to remember every applied potion - std::vector effects; + 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 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); + + void gen_stairs(int player_room, RNG *rng); }; 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 57437c3..0f46d28 100644 --- a/src/position.cc +++ b/src/position.cc @@ -43,3 +43,34 @@ 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; +} + +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 1d5e383..9007d6a 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 @@ -26,4 +20,10 @@ 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); +void remove_from_list(std::vector &sorted_positions, + position &excluded); #endif diff --git a/src/potion.cc b/src/potion.cc index 14e523b..263ed26 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,37 @@ 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; +} + +void potion::print(display *out, bool color) { + out->print_char(pos, 'P'); +} diff --git a/src/potion.h b/src/potion.h index d11f7a5..9dcc92e 100644 --- a/src/potion.h +++ b/src/potion.h @@ -1,30 +1,41 @@ -#ifndef __POTIONS_H__ -#define __POTIONS_H__ +#ifndef __POTION_H__ +#define __POTION_H__ #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 class potion { 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; - int remaining_duration; + // 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 + potion_type type; + int remaining_duration; + position pos; public: - potion(const potion_type type, const int duration); - // 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 int get_priority() const = 0; - potion_type get_type() const; - int get_duration() const; + 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(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); + + virtual void print(display *out, bool color = false); }; -typedef std::vector potion_list; +typedef std::vector potion_list; #endif diff --git a/src/races.h b/src/races.h index 73b9cd4..ddd0058 100644 --- a/src/races.h +++ b/src/races.h @@ -1,14 +1,9 @@ -/* - * CS 246 Final Project - * File: map.h - * Purpose: handles map functionality - */ - #ifndef __RACES_H__ #define __RACES_H__ #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 de08bcf..0734331 100644 --- a/src/restore_health.cc +++ b/src/restore_health.cc @@ -2,16 +2,16 @@ #include -restore_health::restore_health(): - potion{potion_type::restore_health, 1} {} +restore_health::restore_health(const position &pos): + potion{potion_type::restore_health, -1, pos} {} -void restore_health::apply(enum race &race, int &HP, int &ATK, int &DEF, - float &base_hit_rate) { - if (remaining_duration > 0) { - if (race == rdrow) - HP = std::min(HP + 7, MAX_HP[race]); - else - HP = std::min(HP + 5, MAX_HP[race]); +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]); + else + HP = std::min(HP + 5, MAX_HP[race]); --remaining_duration; } diff --git a/src/restore_health.h b/src/restore_health.h index a1b0b39..4f00a75 100644 --- a/src/restore_health.h +++ b/src/restore_health.h @@ -5,10 +5,10 @@ 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; - int get_priority() const override; + 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; }; #endif diff --git a/src/rng.cc b/src/rng.cc index e2610a0..be220f9 100644 --- a/src/rng.cc +++ b/src/rng.cc @@ -32,12 +32,37 @@ int RNG::get_curr_rand_num() const { return curr_rand_num; } -bool RNG::trial(fraction &psuccess) { - return (rand() % psuccess.denominator) < psuccess.numerator; +bool RNG::trial(const fraction &psuccess) { + return ((curr_rand_num = rand()) % psuccess.denominator) < + psuccess.numerator; } -template T &RNG::get_rand_in_vector(const std::vector &vec) { - curr_rand_num = rand(); +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 f86b4d7..adb26bd 100644 --- a/src/rng.h +++ b/src/rng.h @@ -26,9 +26,16 @@ public: unsigned int get_init_seed() const; int get_curr_rand_num() const; - bool trial(fraction &psuccess); + bool trial(const fraction &psuccess); - template T &get_rand_in_vector(const std::vector &vec); + bool coin_flip(); + + int exclude_middle(const int lower_bound, const int upper_bound, + const int excluded); + + 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 52527d5..35f225e 100644 --- a/src/shade.cc +++ b/src/shade.cc @@ -3,28 +3,27 @@ #include #include -shade::shade(RNG &rng, const position_list &available_positions): - character{rng, race::rshade} { - pos = available_positions[rng.rand_under(available_positions.size())]; - gold = 0; - hostile = true; +shade::shade(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { + gold = 0; + hostile = true; } 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); - } + for (auto &ch : chlist) + if (tmp == ch->get_position()) { + return ch->get_hit(race, ATK, base_hit_rate); + } return result::fine; } 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! - HP = std::max(HP - calc_dmg(atk, DEF), 0); + const fraction hitrate) { + if (rng->trial(hitrate)) // This is a hit! + HP = std::max(HP - calc_dmg(atk, DEF), 0); if (HP == 0) return result::died; diff --git a/src/shade.h b/src/shade.h index bfff903..59a748c 100644 --- a/src/shade.h +++ b/src/shade.h @@ -5,12 +5,11 @@ class shade final: public character { public: - 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; + 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, + const fraction hit_rate) override; }; #endif diff --git a/src/vampire.cc b/src/vampire.cc index 60498e9..6725522 100644 --- a/src/vampire.cc +++ b/src/vampire.cc @@ -2,19 +2,18 @@ #include #include -vampire::vampire(RNG &rng, const position_list &available_positions): - character{rng, race::rvampire} { - pos = available_positions[rng.rand_under(available_positions.size())]; - gold = 0; - hostile = true; +vampire::vampire(RNG *rng, const position &pos): + character{rng, race::rshade, pos} { + gold = 0; + hostile = true; } 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); + for (auto &ch : chlist) + if (tmp == ch->get_position()) { + auto res = ch->get_hit(race, ATK, base_hit_rate); if (res != result::miss) { HP += GAIN_HP; @@ -27,9 +26,9 @@ 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) - HP = std::max(HP - calc_dmg(atk, DEF), 0); + const fraction hitrate) { + if (rng->trial(hitrate)) + HP = std::max(HP - calc_dmg(atk, DEF), 0); if (HP == 0) return result::died; diff --git a/src/vampire.h b/src/vampire.h index e44e572..7a68922 100644 --- a/src/vampire.h +++ b/src/vampire.h @@ -6,12 +6,11 @@ const int GAIN_HP = 5; class vampire final: public character { public: - 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; + 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, + const fraction hit_rate) override; }; #endif