diff --git a/src/characters.cc b/src/characters.cc index c6ec287..9eb09f6 100644 --- a/src/characters.cc +++ b/src/characters.cc @@ -54,10 +54,6 @@ 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; } @@ -86,36 +82,6 @@ void character::set_hitrate(const fraction nhitrate) { base_hit_rate = nhitrate; } -void character::set_hostile(const bool is_hostile) { - hostile = is_hostile; -} - -void character::apply_buff(const stat_name statn, const int amount) { - // TODO: add checks for bounds - switch (statn) { - case stat_name::HP: - HP += amount; - break; - - case stat_name::ATK: - ATK += amount; - break; - - case stat_name::DEF: - DEF += amount; - break; - - case stat_name::hostile: { - if (amount > 0) - hostile = true; - else - hostile = false; - - break; - } - } -} - direction_list character::moveable(const position_list &available_positions) const { direction_list result; @@ -128,13 +94,17 @@ const { return result; } -void character::apply(direction &dir, potion_list &plist) { +character::apply_result character::apply(const direction &dir, + potion_list &plist) { for (size_t i = 0; i < plist.size(); ++i) if (pos + MOVE[dir] == plist[i]->get_pos()) { + apply_result res{applied, plist[i]}; insert_potion(plist[i]); plist.erase(plist.begin() + i); - return; + return res; } + + return {applied_nothing, nullptr}; } void character::insert_potion(potion *p) { @@ -191,30 +161,7 @@ result character::move(const direction dir, return result::fine; } -result character::move_or_attack(const direction dir, - const position_list &available_positions, - character_list &chlist) { - auto res = this->move(dir, available_positions); - - if (res != result::fine) - return res; - - return this->attack(dir, chlist); -} - -result character::move_or_attack(const direction dir, - const position_list &available_positions, - character *ch) { - auto res = this->move(dir, available_positions); - - if (res != result::fine) - return res; - - return this->attack(dir, ch); -} - - int calc_dmg(const int ATK, const int DEF) { - return ceil((100 / (100 + DEF)) * ATK); + return ceil((100.0f / (100.0f + DEF)) * ATK); } diff --git a/src/characters.h b/src/characters.h index 70e75e6..2d75f80 100644 --- a/src/characters.h +++ b/src/characters.h @@ -29,21 +29,33 @@ public: 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 attack(const direction dir, character *ch) = 0; - virtual result move_or_attack(const direction dir, - const position_list &available_positions, - character *ch); + struct attack_result { + result res; + int dmg_dealt; + int remaining_HP; + character *which; // hit whom + }; - virtual void apply(direction &dir, - potion_list &potions); - virtual result get_hit(const enum race &race, const int atk, - const fraction hitrate) = 0; + struct apply_result { + result res; + potion *which; // applied which potion + }; + + struct hit_result { + result res; + int dmg_dealt; + int remaining_HP; + }; + + virtual attack_result attack(const direction dir, + character_list &chlist) = 0; + virtual attack_result attack(const direction dir, character *ch) = 0; + + virtual apply_result apply(const direction &dir, + potion_list &potions); + virtual hit_result get_hit(const enum race &race, const int atk, + const fraction hitrate) = 0; // overload for different races virtual void print(display *out, bool player = false); @@ -60,7 +72,6 @@ public: 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); @@ -69,14 +80,8 @@ public: 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 - void apply_buff(const stat_name statn, const int amount); - // void apply_buff(const stat_name statn, const float mul); - // reserved for later protected: RNG *rng; const enum race race; @@ -94,7 +99,6 @@ protected: potion_list potions; // inventory 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 786f46a..4d54c13 100644 --- a/src/constants.h +++ b/src/constants.h @@ -9,7 +9,11 @@ const int INF = 0x3F3F3F3F; enum error {none}; // TODO: update result to include subject -enum result {fine, died, go_down, go_up, hit, moved, miss, terminate}; +// fine will waste a turn +enum result {fine, died, go_down, go_up, hit, moved, + miss, terminate, applied, applied_nothing, + toggle_the_world, restart_game, unknown + }; enum game_status {terminated, main_menu, in_game, options, dead, won, escaped, restart @@ -22,7 +26,7 @@ enum game_command {game_command_terminate = 0, apply_north, apply_south, apply_east, apply_west, apply_northeast, apply_northwest, apply_southeast, apply_southwest, - apply_prompt, apply_panic, // for curses_input specifically + apply_panic, // for curses_input specifically attack_north, attack_south, attack_east, attack_west, attack_northeast, attack_northwest, attack_southeast, attack_southwest, @@ -33,22 +37,28 @@ enum game_command {game_command_terminate = 0, enum stat_name {HP, ATK, DEF, hostile}; -const int RACE_CNT = 5; // TODO: update as you go +const int RACE_CNT = 6; // TODO: update as you go -enum race {rshade = 0, rvampire, rgoblin, rdrow, rdragon /* TODO: fill out the other races (including enemies) */}; +enum race {rshade = 0, rvampire, rgoblin, rdrow, rdragon, rmerchant /* 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, 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 MAX_HP[RACE_CNT] = {125, INF, 110, 150, 150, 30}; +const int STARTING_HP[RACE_CNT] = {125, 50, 110, 150, 150, 30}; +const int STARTING_ATK[RACE_CNT] = {25, 25, 15, 25, 20, 70}; +const int STARTING_DEF[RACE_CNT] = {25, 25, 20, 15, 20, 5}; +const char CHARACTER_REP[RACE_CNT] = {'S', 'V', 'G', 'd', 'D', 'M'}; +const char *RACE_NAME[RACE_CNT] = { + "Shade", "Vampire", "Goblin", "Drow", "Dragon" +}; 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 }; +const char *POTION_REAL_NAME[POTION_TYPE_CNT] = { + "RH", "BA", "BD", "PH", "WA", "WD" +}; // Calculation priorities const int CALC_ADD_BASE = 0; diff --git a/src/game.cc b/src/game.cc index e3ec72d..246973a 100644 --- a/src/game.cc +++ b/src/game.cc @@ -9,7 +9,8 @@ game::game(const enum race starting_race, logger *new_log, RNG *new_rng): enabled_features{enabled_features}, - in{new_in}, out{new_out}, log{new_log}, rng{new_rng} { + in{new_in}, out{new_out}, log{new_log}, rng{new_rng}, + curr_turn{0} { const position nil{0, 0}; the_world = false; @@ -28,7 +29,11 @@ game::game(const enum race starting_race, levels.reserve(max_level); curr_level = 0; + hostile_merchants = false; + new_level(); + msg += "Player character has spawned. "; + print(); } void game::new_level() { @@ -41,84 +46,116 @@ void game::new_level() { } result game::player_moves(game_command cmd) { - switch (cmd) { - case game_command_terminate: + if (cmd == game_command_terminate) { return result::terminate; - - case move_east: { + } else if (cmd >= move_north && cmd <= move_southwest) { // Tried to move if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(east); + return player_move_or_attack((direction)(cmd - move_north)); - return player->move(east, levels[curr_level]->get_available_around( - player->get_position())); - } - - case move_west: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(west); - - return player->move(west, levels[curr_level]->get_available_around( - player->get_position())); - } - - case move_north: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(north); - - return player->move(north, levels[curr_level]->get_available_around( - player->get_position())); - } - - case move_south: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(south); - - return player->move(south, levels[curr_level]->get_available_around( - player->get_position())); - } - - case move_northeast: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(northeast); - - return player->move(northeast, + return player->move((direction)(cmd - move_north), levels[curr_level]->get_available_around( player->get_position())); + } else if (cmd >= apply_north && cmd <= apply_southwest) { + auto res = player->apply((direction)(cmd - apply_north), + levels[curr_level]->get_plist()); + + if (res.res == applied) { + msg += "PC used potion "; + msg += POTION_REAL_NAME[res.which->get_type()]; + msg += ". "; + } else { + msg += "PC tried to drink thin air. "; + } + + return res.res; + } else if (cmd == apply_panic) { + msg += "PC tried to use in some non-existent direction. "; + return fine; + } else if (cmd >= attack_north && cmd <= attack_southwest) { + auto res = player->attack((direction)(cmd - attack_north), + levels[curr_level]->get_chlist()); + + if (res.res == hit) { + msg += "PC deals " + std::to_string(res.dmg_dealt) + + " damage to " + + CHARACTER_REP[res.which->get_race()] + + " (" + std::to_string(res.remaining_HP) + + " HP). "; + } else if (res.res == miss) { + msg += "PC missed "; + msg += CHARACTER_REP[res.which->get_race()]; + msg += ". "; + } else { + msg += "PC tried to attack thin air. "; + } + + if (res.which->get_race() == rmerchant) + hostile_merchants = true; + + return res.res; + } else if (cmd == up_stairs) { + if (player->get_position() == + levels[curr_level]->get_up_stairs()) + return go_up; + + msg += "PC tried to fly through the ceiling. "; + return fine; + } else if (cmd == down_stairs) { + if (player->get_position() == + levels[curr_level]->get_down_stairs()) + return go_down; + + msg += "PC tried to dig through the floor. "; + return fine; + } else if (cmd == the_world) { + msg += "PC toggled Stand: The World! "; + return toggle_the_world; + } else if (cmd == game_restart) { + return restart_game; + } else if (cmd == game_command_pass) { + return fine; + } else if (cmd == game_command_panic) { + msg += "PC tried to produce some undefined behaviour. "; + return unknown; } - case move_northwest: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(northwest); + msg += "PC tried to produce some undefined behaviour. "; + return unknown; +} - return player->move(northwest, - levels[curr_level]->get_available_around( - player->get_position())); - } +#include +bool compare_characters(character *&a, character *&b) { + return a->get_position() < b->get_position(); +} - case move_southeast: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(southeast); +void game::move_enemies() { + if (the_world) + return; - return player->move(southeast, - levels[curr_level]->get_available_around( - player->get_position())); - } + character_list enemies = levels[curr_level]->get_chlist(); + std::sort(enemies.begin(), enemies.end(), &game::compare_characters); - case move_southwest: { - if (enabled_features & FEATURE_NCURSES) - return player_move_or_attack(southwest); + for (auto ch : enemies) { - return player->move(southwest, - levels[curr_level]->get_available_around( - player->get_position())); - } } } game_status game::run() { - player->start_turn(); + msg = ""; auto res = player_moves(in->get_command()); + if (!(enabled_features & FEATURE_NCURSES) && + res == result::moved && + (player->get_position() == + levels[curr_level]->get_down_stairs())) + res = go_down; + + if (!(enabled_features & FEATURE_NCURSES) && + res == result::moved && + (player->get_position() == + levels[curr_level]->get_up_stairs())) + res = go_up; + switch (res) { case result::terminate: return terminated; @@ -133,6 +170,7 @@ game_status game::run() { player->discard_level_effects(); ++curr_level; new_level(); + msg += "PC went down a floor. "; break; } @@ -140,19 +178,120 @@ game_status game::run() { if (curr_level == 0) return game_status::escaped; + player->discard_level_effects(); --curr_level; player->set_position(levels[curr_level]->get_down_stairs()); + msg += "PC went up a floor. "; break; } + case toggle_the_world: + the_world = !the_world; + break; + + case restart_game: + return restart; + + case unknown: + case fine: + case applied_nothing: + ++curr_turn; + return game_status::in_game; + default: break; } + player->start_turn(); + player->apply_effects(); + + if (player->get_HP() <= 0) + return game_status::dead; + move_enemies(); if (player->get_HP() <= 0) return game_status::dead; + ++curr_turn; return game_status::in_game; } + +const position STATUS_RACE{0, 25}; +const position STATUS_FLOOR{69, 25}; +const position STATUS_HP{0, 26}; +const position STATUS_ATK{0, 27}; +const position STATUS_DEF{0, 28}; +const position STATUS_ACTION{0, 29}; +const char *ASK_ATK_MERCHANT = + "Action: Do you really want to attack the peaceful merchant? [Y/N]"; + +size_t game::get_curr_turn() const { + return curr_turn; +} + +void game::print() { + if (msg.length() > MAX_MSG_LENGTH) { + msg.resize(MAX_MSG_LENGTH); + msg += "..."; + } + + levels[curr_level]->print(out); + player->print(out, true); + + std::string tmp = "Race: "; + tmp += RACE_NAME[player->get_race()]; + tmp += " Gold: " + std::to_string(player->get_gold()); + out->print_str(STATUS_RACE, tmp); + + tmp = "Floor " + std::to_string(curr_level + 1); + out->print_str(STATUS_FLOOR, tmp); + + tmp = "Atk: " + std::to_string(player->get_ATK()); + out->print_str(STATUS_ATK, tmp); + + tmp = "Def: " + std::to_string(player->get_DEF()); + out->print_str(STATUS_DEF, tmp); + + tmp = "Action: " + msg; + out->print_str(STATUS_ACTION, tmp); +} + +result game::player_move_or_attack(const direction &dir) { + auto avlbl = levels[curr_level]->get_available_around( + player->get_position()); + + if (find(avlbl, player->get_position() + MOVE[dir]) + != avlbl.size()) { + player->set_position(player->get_position() + MOVE[dir]); + return result::moved; + } + + auto tmp = player->get_position() + MOVE[dir]; + character *target; + + for (auto ch : levels[curr_level]->get_chlist()) + if (tmp == ch->get_position()) { + target = ch; + } + + auto res = player->attack(dir, target); + + if (target->get_race() == rmerchant) + hostile_merchants = true; + + if (res.res == hit) { + msg += "PC deals " + + std::to_string(res.dmg_dealt) + " damage to " + + CHARACTER_REP[res.which->get_race()] + " (" + + std::to_string(res.remaining_HP) + " HP). "; + } else if (res.res == miss) { + msg += "PC missed "; + msg += CHARACTER_REP[res.which->get_race()]; + msg += ". "; + } else { + msg += "PC tried to attack thin air. "; + } + + return res.res; +} diff --git a/src/game.h b/src/game.h index 7aea250..65fdd52 100644 --- a/src/game.h +++ b/src/game.h @@ -15,6 +15,7 @@ private: static const size_t MAX_LEVEL_CNT = 50; static const size_t MIN_LEVEL_CNT = 30; static const size_t DEFAULT_MAX_LEVEL = 5; + static const size_t MAX_MSG_LENGTH = 300; const feature enabled_features; @@ -23,6 +24,8 @@ private: logger *log; RNG *rng; + unsigned int curr_turn; + std::unique_ptr player; std::vector> levels; @@ -30,6 +33,10 @@ private: size_t curr_level; bool the_world; + bool hostile_merchants; + + // shrink if over 300 + std::string msg; public: game(const enum race starting_race, const feature enabled_features, @@ -39,13 +46,15 @@ public: RNG *new_rng); game_status run(); size_t get_curr_level() const; + size_t get_curr_turn() const; const character *get_player() const; + void print(); private: result player_moves(game_command cmd); void new_level(); void move_enemies(); - result player_move_or_attack(const direction &dir); + bool compare_characters(const character *&a, const character *&b); }; #endif diff --git a/src/level.cc b/src/level.cc index 655fa28..e63db10 100644 --- a/src/level.cc +++ b/src/level.cc @@ -102,7 +102,6 @@ void level::gen_potions(RNG *rng, std::vector &tiles) { void level::print(display *out) const { map.print(out); - player->print(out, true); for (auto ch : chlist) ch->print(out); diff --git a/src/position.cc b/src/position.cc index 092f3ff..baf3aa5 100644 --- a/src/position.cc +++ b/src/position.cc @@ -84,3 +84,8 @@ float distance(const position &a, const position &b) { int distance_sqr(const position &a, const position &b) { return pow(a.x - b.x, 2) + pow(a.y - b.y, 2); } + +bool is_adjacent(const position &a, const position &b) { + return (a.x - b.x >= -1 && a.x - b.x <= 1) && + (a.y - b.y >= -1 && a.y - b.y <= 1); +} diff --git a/src/position.h b/src/position.h index 5466121..8d2edf6 100644 --- a/src/position.h +++ b/src/position.h @@ -29,5 +29,6 @@ void remove_from_list(std::vector &sorted_positions, float distance(const position &a, const position &b); int distance_sqr(const position &a, const position &b); +bool is_adjacent(const position &a, const position &b); #endif diff --git a/src/shade.cc b/src/shade.cc index b89ca90..19bc0dc 100644 --- a/src/shade.cc +++ b/src/shade.cc @@ -9,34 +9,46 @@ shade::shade(RNG *rng, const position &pos): hostile = true; } -result shade::attack(const direction dir, character_list &chlist) { +character::attack_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); + auto res = ch->get_hit(race, ATK, base_hit_rate); + return {res.res, res.dmg_dealt, res.remaining_HP, ch}; } - return result::fine; + return {fine, 0, 0, nullptr}; } -result shade::attack(const direction dir, character *ch) { +character::attack_result shade::attack(const direction dir, character *ch) { position tmp{pos + MOVE[dir]}; - if (tmp == ch->get_position()) - return ch->get_hit(race, ATK, base_hit_rate); + if (tmp == ch->get_position()) { + auto res = ch->get_hit(race, ATK, base_hit_rate); + return {res.res, res.dmg_dealt, res.remaining_HP, ch}; + } - return result::fine; + return {fine, 0, 0, nullptr}; } -result shade::get_hit(const enum race &race, const int atk, - const fraction hitrate) { - if (rng->trial(hitrate)) // This is a hit! - HP = std::max(HP - calc_dmg(atk, DEF), 0); +character::hit_result shade::get_hit(const enum race &race, const int atk, + const fraction hitrate) { + if (rng->trial(hitrate)) { + int tmp = calc_dmg(atk, DEF); - if (HP == 0) - return result::died; + if (tmp > HP) + tmp = HP; - return result::hit; + HP -= tmp; + + if (HP == 0) + return {result::died, tmp, HP}; + + return {result::hit, tmp, HP}; + } + + return {result::miss, 0, HP}; } diff --git a/src/shade.h b/src/shade.h index 93c9ab4..94636d4 100644 --- a/src/shade.h +++ b/src/shade.h @@ -6,11 +6,11 @@ class shade final: public character { public: 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; - virtual result attack(const direction dir, character *ch) override; + virtual attack_result attack(const direction dir, + character_list &chlist) override; + virtual hit_result get_hit(const enum race &race, const int atk, + const fraction hit_rate) override; + virtual attack_result attack(const direction dir, character *ch) override; }; #endif