#include #include #include #include #include #include #include #undef NDEBUG #include using namespace std; #if 0 auto& debug = cerr; #else struct NullBuffer : streambuf { int overflow(int chr) override { return chr; } }; NullBuffer nullBuffer; ostream debug{ &nullBuffer }; #endif using Mana = int; using HitPoints = int; using Damage = int; using Health = int; using Armor = int; class Player; class Boss; class Spell; shared_ptr chooseRandomSpell(Player& player); class Spell { protected: Spell(string_view name, Mana mana) : name_(name) , mana_(mana) { } public: Spell(const Spell&) = default; Spell(Spell&&) = delete; Spell& operator=(const Spell&) = delete; Spell& operator=(Spell&&) = delete; virtual ~Spell() = default; virtual void Play(Player& player, Boss& boss) = 0; virtual int Timer() { return 0; } const string name_; const Mana mana_; }; class Player { public: // NOLINTBEGIN Player(Mana mana, HitPoints hit_points) : mana_(mana) , hit_points_(hit_points) { } void AddMana(Mana mana) { assert(mana > 0); mana_ += mana; } void SubMana(Mana mana) { assert(mana > 0); mana_ -= mana; } void AddArmor(Armor armor) { assert(armor > 0); armor_ += armor; } void SubArmor(Armor armor) { assert(armor > 0); armor_ -= armor; } void AddHitPoints(HitPoints hit_points) { assert(hit_points > 0); hit_points_ += hit_points; } void SubHitPoints(HitPoints hit_points) { assert(hit_points > 0); hit_points_ -= hit_points; } // NOLINTEND void Damage(Damage damage) { SubHitPoints(max(1, damage - armor_)); } [[nodiscard]] bool CanCastSpell(const shared_ptr& spell) const { return mana_ >= spell->mana_; } [[nodiscard]] bool HasLost() const { return hit_points_ <= 0; } Mana mana_; HitPoints hit_points_; Armor armor_{}; }; class Spells { public: Spells() = default; void AddSpell(const shared_ptr& spell, Player& player, Boss& boos) { debug << "Player casts " << spell->name_ << endl; player.SubMana(spell->mana_); if ( spell->Timer() == 0 ) { spell->Play(player, boos); } else { effects_.push_back(spell); } } bool TryAddRandomSpell(Player& player, Boss& boss) { while ( true ) { auto randomSpell = chooseRandomSpell(player); if ( randomSpell == nullptr ) { return false; } auto iter = ranges::find_if(effects_, [&randomSpell](const auto& effect) { return effect->name_ == randomSpell->name_; }); if ( iter != effects_.end() ) { continue; } AddSpell(randomSpell, player, boss); } } void Play(Player& player, Boss& boss) { for ( const auto& effect: effects_ ) { effect->Play(player, boss); } effects_.remove_if([](const auto& effect) { return effect->Timer() == 0; }); } private: list> effects_; }; class Boss { static auto split(const string& line, char sep = ' ') { vector parts; stringstream input{ line }; for ( string part; getline(input, part, sep); ) { parts.emplace_back(part); } return parts; } public: explicit Boss(string_view filename) { fstream input{ filename }; for ( string line; getline(input, line); ) { auto parts = Boss::split(line); if ( parts[0] == "Hit" ) { hit_points_ = stoi(parts[2]); } else if ( parts[0] == "Damage:" ) { damage_ = stoi(parts[1]); } } } Boss(HitPoints hit_points, Damage damage) // NOLINT : hit_points_(hit_points) , damage_(damage) { } void Play(Player& player) const { debug << "Boss attacks for " << damage_ << " damage" << endl; player.Damage(damage_); } void SubHitPoints(HitPoints hit_points) { assert(hit_points > 0); // NOLINT hit_points_ -= hit_points; } [[nodiscard]] bool HasLost() const { return hit_points_ <= 0; } HitPoints hit_points_; Damage damage_; }; ostream& operator<<(ostream& ostream, const Player& player) { ostream << "- Player has " << player.hit_points_ << " hit points, " << player.armor_ << " armor, " << player.mana_ << " mana"; return ostream; } ostream& operator<<(ostream& ostream, const Boss& boss) { ostream << "- Boss has " << boss.hit_points_ << " hit points"; return ostream; } class MagicMissile : public Spell { public: MagicMissile() : Spell("Magic Missile", Mana(Cost)) { } void Play(Player& /*player*/, Boss& boss) override { boss.SubHitPoints(4); debug << "Magic Missile deals 4 damage" << endl; } static const int Cost = 53; }; class Drain : public Spell { public: Drain() : Spell("Drain", Mana(Cost)) { } void Play(Player& player, Boss& boss) override { player.AddHitPoints(2); boss.SubHitPoints(2); debug << "Drain deals 2 damage, and heals 2 hit points" << endl; } static const int Cost = 73; }; class Effect : public Spell { protected: Effect(string_view name, Mana mana, int timer) // NOLINT : Spell(name, mana) , timer_(timer) { } public: int Timer() final { return timer_; } protected: int timer_; }; class Shield : public Effect { public: explicit Shield(Player& player) : Effect("Shield", Mana(Cost), RunTime) , player_(player) { debug << "Increasing Armor by " << Armor << endl; player_.AddArmor(Armor); } ~Shield() override { debug << "Decreasing Armor by " << Armor << endl; player_.SubArmor(Armor); } void Play(Player& /*player*/, Boss& /*boss*/) override { timer_--; debug << "Shields timer is now " << timer_ << endl; } static const Armor Armor = 7; static const int Cost = 113; static const int RunTime = 6; private: Player& player_; }; class Poison : public Effect { public: Poison() : Effect("Poison", Mana(Cost), RunTime) { } void Play(Player& /*player*/, Boss& boss) override { timer_--; debug << name_ << " deals 3 damage; its timer is now " << timer_ << endl; boss.SubHitPoints(3); } static const int Cost = 173; static const int RunTime = 6; }; class Recharge : public Effect { public: Recharge() : Effect("Recharge", Mana(Cost), RunTime) { } ~Recharge() override { debug << "Recharge wears off" << endl; } void Play(Player& player, Boss& /*boss*/) override { static const Mana AdditionalMana = 101; timer_--; player.AddMana(AdditionalMana); debug << name_ << " provides 101 mana; its timer is now " << timer_ << endl; } static const int Cost = 229; static const int RunTime = 5; }; shared_ptr chooseRandomSpell(Player& player) { if ( player.mana_ < min({ MagicMissile::Cost, Drain::Cost, Shield::Cost, Poison::Cost, Recharge::Cost }) ) { return nullptr; } while ( true ) { static const int NumSpells = 5; auto selection = rand() % NumSpells; if ( selection == 0 && player.mana_ >= MagicMissile::Cost ) { return make_shared(); } if ( selection == 1 && player.mana_ >= Drain::Cost ) { return make_shared(); } if ( selection == 2 && player.mana_ >= Shield::Cost ) { return make_shared(player); } if ( selection == 3 && player.mana_ >= Poison::Cost ) { return make_shared(); } if ( selection == 4 && player.mana_ >= Recharge::Cost ) { return make_shared(); } } } void battle1() { Player player(250, 10); Boss boss(13, 8); Spells spells; // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); if ( boss.HasLost() ) { cout << "Boss killed" << endl; } } void battle2() { Player player(250, 10); Boss boss(14, 8); Spells spells; // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(player), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); // ---------------------- cout << "\n-- Player turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); spells.AddSpell(make_shared(), player, boss); // ---------------------- cout << "\n-- Boss turn --\n"; cout << player << '\n' << boss << endl; spells.Play(player, boss); boss.Play(player); if ( boss.HasLost() ) { cout << "Boss killed" << endl; } cout << "End of Battle" << endl; } int main() { battle2(); }