diff --git a/src/attacker/attacker.cpp b/src/attacker/attacker.cpp index 57fb101..1172e89 100644 --- a/src/attacker/attacker.cpp +++ b/src/attacker/attacker.cpp @@ -121,3 +121,27 @@ void Attacker::log_shoot(Actor &opponent) const { void Attacker::set_state(State s) { this->_state = s; } Attacker::State Attacker::get_state() const { return this->_state; } + +void Attacker::activate_ability(size_t turn) { + // Already activated, skip. + if (this->_ability_activated_turn.has_value()) + return; + + this->_ability_activated_turn = turn; +} + +void Attacker::check_ability(size_t turn) { + this->_is_ability_active = this->_ability_activated_turn.has_value() && + turn <= this->_ability_activated_turn.value() + + this->get_num_ability_turns(); +} + +bool Attacker::is_ability_active() const { return this->_is_ability_active; } + +unsigned Attacker::get_ability_activation_cost() { + return this->attribute_dictionary[this->_type].ability_activation_cost; +} + +unsigned Attacker::get_num_ability_turns() { + return this->attribute_dictionary[this->_type].num_ability_turns; +} diff --git a/src/attacker/attacker.hpp b/src/attacker/attacker.hpp index 7ec3638..ede8ebf 100644 --- a/src/attacker/attacker.hpp +++ b/src/attacker/attacker.hpp @@ -66,6 +66,13 @@ class Attacker : public Actor { [[nodiscard]] State get_state() const; + void activate_ability(size_t turn); + bool is_ability_active() const; + void check_ability(size_t turn); + + unsigned get_ability_activation_cost(); + unsigned get_num_ability_turns(); + private: void set_state(State s); static inline size_t _id_counter = 0; @@ -77,4 +84,7 @@ class Attacker : public Actor { Position _destination; bool _is_target_set_by_player; size_t _target_id; + + std::optional _ability_activated_turn; + bool _is_ability_active = false; }; diff --git a/src/defender/defender.cpp b/src/defender/defender.cpp index 5fd096f..2598a91 100644 --- a/src/defender/defender.cpp +++ b/src/defender/defender.cpp @@ -37,8 +37,7 @@ std::optional Defender::get_nearest_attacker_index( } if (this->is_aerial_type()) { auto nearest_attacker = std::min_element( - attackers.begin(), attackers.end(), - [this](const Attacker a, const Attacker b) { + attackers.begin(), attackers.end(), [this](Attacker a, Attacker b) { if (a.is_aerial_type() && !b.is_aerial_type() && this->is_in_range(a)) { return true; @@ -47,20 +46,30 @@ std::optional Defender::get_nearest_attacker_index( this->is_in_range(b)) { return false; } + + if (a.is_ability_active()) + return false; + if (b.is_ability_active()) + return true; + return this->get_position().distance_to(a.get_position()) < this->get_position().distance_to(b.get_position()); }); return std::distance(attackers.begin(), nearest_attacker); } auto nearest_attacker = std::min_element( - attackers.begin(), attackers.end(), - [this](const Attacker a, const Attacker b) { + attackers.begin(), attackers.end(), [this](Attacker a, Attacker b) { if (a.is_aerial_type() && !b.is_aerial_type()) { return false; } if (b.is_aerial_type() && !a.is_aerial_type()) { return true; } + if (a.is_ability_active()) + return false; + if (b.is_ability_active()) + return true; + return this->get_position().distance_to(a.get_position()) < this->get_position().distance_to(b.get_position()); }); diff --git a/src/game/game.cpp b/src/game/game.cpp index b854ea0..6e07b37 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -23,19 +23,24 @@ Game::Game(std::vector attackers, std::vector defenders, } Game Game::simulate( + const Game::turn_t turn, const std::unordered_map &player_set_targets, - const std::vector> &spawn_positions) - const { + const std::vector> &spawn_positions, + const std::vector &ability_activations) { const auto &prev_state_attackers = this->get_attackers(); const auto &prev_state_defenders = this->get_defenders(); + auto coins_left = this->get_coins(); + std::vector attackers(prev_state_attackers.begin(), prev_state_attackers.end()); std::vector defenders(prev_state_defenders.begin(), prev_state_defenders.end()); - ranges::for_each(attackers, - [](Attacker &attacker) { attacker.clear_destination(); }); + ranges::for_each(attackers, [turn](Attacker &attacker) { + attacker.clear_destination(); + attacker.check_ability(turn); + }); ranges::for_each( player_set_targets, @@ -50,6 +55,23 @@ Game Game::simulate( } }); + ranges::for_each(ability_activations, [&](Game::attacker_id id) { + auto attacker_index = this->get_attacker_index_by_id(id); + unsigned ability_activation_cost = + attackers[*attacker_index].get_ability_activation_cost(); + + if (coins_left < ability_activation_cost) { + return; + } + coins_left -= ability_activation_cost; + // In case the ability is already active, + // the user would have paid the cost but the ability would not be activated + // This is the penalty for trying to activate an already active ability. + if (attacker_index.has_value()) { + attackers[*attacker_index].activate_ability(turn); + } + }); + // Attacker Loop ranges::for_each(attackers, [&](Attacker &attacker) mutable { std::optional defender_index{std::nullopt}; @@ -77,9 +99,15 @@ Game Game::simulate( ranges::for_each(defenders, [&](Defender &defender) mutable { if (auto attacker_index = defender.get_nearest_attacker_index(prev_state_attackers)) { - if ((defender.is_in_range(attackers[*attacker_index])) && - ((defender.is_aerial_type()) || - (!attackers[*attacker_index].is_aerial_type()))) { + + bool should_attack = (defender.is_in_range(attackers[*attacker_index])) && + ((defender.is_aerial_type()) || + (!attackers[*attacker_index].is_aerial_type())); + + should_attack = + should_attack && !attackers[*attacker_index].is_ability_active(); + + if (should_attack) { defender.attack(attackers[*attacker_index]); // set defender's state to ATTACKING defender.set_state(Defender::State::ATTACKING); @@ -131,8 +159,6 @@ Game Game::simulate( }), defenders.end()); - auto coins_left = this->get_coins(); - // new attackers are spawned here auto positions = std::set{}; ranges::for_each(spawn_positions, [&](const auto &spawn_details) { diff --git a/src/game/game.hpp b/src/game/game.hpp index 8acccdd..3a2b155 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -5,26 +5,30 @@ #include "utils/position.hpp" #include +#include +#include #include class Game { public: - Game(std::vector attackers, std::vector defenders, - unsigned coins); - using defender_id = size_t; using attacker_id = size_t; using index_t = size_t; + using turn_t = size_t; + + Game(std::vector attackers, std::vector defenders, + unsigned coins); [[nodiscard]] const std::vector &get_attackers() const; [[nodiscard]] const std::vector &get_defenders() const; [[nodiscard]] Game simulate( + const turn_t turn, const std::unordered_map &player_set_targets, - const std::vector> &spawn_positions) - const; + const std::vector> &spawn_positions, + const std::vector &ability_activations); [[nodiscard]] unsigned get_coins() const; diff --git a/src/game/pvpgame.cpp b/src/game/pvpgame.cpp index 18a00ff..cb2947f 100644 --- a/src/game/pvpgame.cpp +++ b/src/game/pvpgame.cpp @@ -27,6 +27,7 @@ PvPGame::PvPGame(std::vector player_1_attackers, } PvPGame PvPGame::simulate( + const turn_t turn, const std::unordered_map &player_1_set_targets, const std::vector> @@ -34,7 +35,10 @@ PvPGame PvPGame::simulate( const std::unordered_map &player_2_set_targets, const std::vector> - &player_2_spawn_positions) const { + &player_2_spawn_positions, + const std::vector &player_1_ability_activations, + const std::vector &player_2_ability_activations) + const { const auto &prev_state_player_1_attackers = this->get_player_1_attackers(); const auto &prev_state_player_2_attackers = this->get_player_2_attackers(); @@ -46,14 +50,19 @@ PvPGame PvPGame::simulate( prev_state_player_2_attackers.begin(), prev_state_player_2_attackers.end()); - std::ranges::for_each(player_1_attackers, [](PvPAttacker &attacker) { + std::ranges::for_each(player_1_attackers, [turn](PvPAttacker &attacker) { attacker.clear_destination(); + attacker.check_ability(turn); }); - std::ranges::for_each(player_2_attackers, [](PvPAttacker &attacker) { + std::ranges::for_each(player_2_attackers, [turn](PvPAttacker &attacker) { attacker.clear_destination(); + attacker.check_ability(turn); }); + unsigned player_1_coins = PvPGame::FIXED_COINS_PER_TURN; + unsigned player_2_coins = PvPGame::FIXED_COINS_PER_TURN; + std::ranges::for_each( player_1_set_targets, [&](const std::pair &entry) { @@ -82,6 +91,48 @@ PvPGame PvPGame::simulate( } }); + std::ranges::for_each( + player_1_ability_activations, [&](player_1_attacker_id id) { + auto player_1_attacker_index = + this->get_player_1_attacker_index_by_id(id); + unsigned ability_activation_cost = + player_1_attackers[*player_1_attacker_index] + .get_ability_activation_cost(); + + if (player_1_coins < ability_activation_cost) { + return; + } + player_1_coins -= ability_activation_cost; + // In case the ability is already active, + // the user would have paid the cost but the ability would not be + // activated This is the penalty for trying to activate an already + // active ability. + if (player_1_attacker_index.has_value()) { + player_1_attackers[*player_1_attacker_index].activate_ability(turn); + } + }); + + std::ranges::for_each( + player_2_ability_activations, [&](player_2_attacker_id id) { + auto player_2_attacker_index = + this->get_player_2_attacker_index_by_id(id); + unsigned ability_activation_cost = + player_2_attackers[*player_2_attacker_index] + .get_ability_activation_cost(); + + if (player_2_coins < ability_activation_cost) { + return; + } + player_2_coins -= ability_activation_cost; + // In case the ability is already active, + // the user would have paid the cost but the ability would not be + // activated This is the penalty for trying to activate an already + // active ability. + if (player_2_attacker_index.has_value()) { + player_2_attackers[*player_2_attacker_index].activate_ability(turn); + } + }); + std::ranges::for_each(player_1_attackers, [&](PvPAttacker &attacker) mutable { std::optional player_2_attacker_index{std::nullopt}; if (attacker.is_target_set_by_player() && @@ -173,8 +224,6 @@ PvPGame PvPGame::simulate( auto player_1_positions = std::set{}; auto player_2_positions = std::set{}; - unsigned player_1_coins = PvPGame::FIXED_COINS_PER_TURN; - unsigned player_2_coins = PvPGame::FIXED_COINS_PER_TURN; std::ranges::for_each( player_1_spawn_positions, [&](const auto &spawn_details) { diff --git a/src/game/pvpgame.hpp b/src/game/pvpgame.hpp index b3fbb51..d48d743 100644 --- a/src/game/pvpgame.hpp +++ b/src/game/pvpgame.hpp @@ -14,6 +14,7 @@ class PvPGame { using player_1_attacker_id = size_t; using player_2_attacker_id = size_t; using index_t = size_t; + using turn_t = size_t; static inline unsigned int FIXED_COINS_PER_TURN; @@ -21,15 +22,19 @@ class PvPGame { [[nodiscard]] const std::vector &get_player_2_attackers() const; - [[nodiscard]] PvPGame - simulate(const std::unordered_map - &player_1_set_targets, - const std::vector> - &player_1_spawn_positions, - const std::unordered_map - &player_2_set_targets, - const std::vector> - &player_2_spawn_positions) const; + [[nodiscard]] PvPGame simulate( + const turn_t turn, + const std::unordered_map + &player_1_set_targets, + const std::vector> + &player_1_spawn_positions, + const std::unordered_map + &player_2_set_targets, + const std::vector> + &player_2_spawn_positions, + const std::vector &player_1_ability_activations, + const std::vector &player_2_ability_activations) + const; private: std::optional diff --git a/src/main.cpp b/src/main.cpp index 747538c..7b89103 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "utils/game_type.hpp" #include +#include #include #include #include @@ -24,13 +25,15 @@ void NormalGame() { for (unsigned attacker_type_id = 1; attacker_type_id <= n_attacker_types; ++attacker_type_id) { // for normal game weight is not required so we just input and ignore it - unsigned hp, range, attack_power, speed, price, weight; + unsigned hp, range, attack_power, speed, price, weight, num_ability_turns, + ability_activation_cost; bool is_aerial; std::cin >> hp >> range >> attack_power >> speed >> price >> is_aerial >> - weight; + weight >> num_ability_turns >> ability_activation_cost; Attacker::attribute_dictionary.insert(std::make_pair( AttackerType(attacker_type_id), - Attributes(hp, range, attack_power, speed, price, is_aerial))); + Attributes(hp, range, attack_power, speed, price, is_aerial, + num_ability_turns, ability_activation_cost))); } unsigned n_defender_types; @@ -42,7 +45,7 @@ void NormalGame() { std::cin >> hp >> range >> attack_power >> speed >> price >> is_aerial; Defender::attribute_dictionary.insert(std::make_pair( DefenderType(defender_type_id), - Attributes(hp, range, attack_power, speed, price, is_aerial))); + Attributes(hp, range, attack_power, speed, price, is_aerial, 0, 0))); } auto map = Map::get(std::cin); @@ -57,7 +60,7 @@ void NormalGame() { Game game({}, defenders, coins); - for (size_t turn = 0; turn < turns; ++turn) { + for (Game::turn_t turn = 0; turn < turns; ++turn) { Logger::log_turn(turn); unsigned n_attackers; @@ -74,8 +77,9 @@ void NormalGame() { // Get all the manually to be set targets as input, we need attacker's id // and targetted defender id std::unordered_map player_set_targets; - int no_of_player_set_targets = 0; + int no_of_player_set_targets = -1; std::cin >> no_of_player_set_targets; + assert(no_of_player_set_targets >= 0); while ((no_of_player_set_targets--) > 0) { Game::attacker_id att_id = 0; Game::defender_id def_id = 0; @@ -83,14 +87,28 @@ void NormalGame() { player_set_targets[att_id] = def_id; } - game = game.simulate(player_set_targets, spawn_positions); + // TODO: Update the runner format to include ability activations + std::vector ability_activations; + int no_of_ability_activations = -1; + std::cin >> no_of_ability_activations; + assert(no_of_ability_activations >= 0); + + while ((no_of_ability_activations--) > 0) { + Game::attacker_id att_id = 0; + std::cin >> att_id; + ability_activations.push_back(att_id); + } + + game = game.simulate(turn, player_set_targets, spawn_positions, + ability_activations); auto active_attackers = game.get_attackers(); std::cout << active_attackers.size() << "\n"; std::ranges::for_each(active_attackers, [](const Attacker &attacker) { std::cout << attacker.get_id() << " " << attacker.get_position().get_x() << " " << attacker.get_position().get_y() << " " - << (int)attacker.get_type() << " " << attacker.get_hp() << "\n"; + << (int)attacker.get_type() << " " << attacker.get_hp() << " " + << (int)attacker.is_ability_active() << "\n"; }); auto active_defenders = game.get_defenders(); @@ -132,13 +150,15 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, std::cin >> n_attacker_types; for (unsigned attacker_type_id = 1; attacker_type_id <= n_attacker_types; ++attacker_type_id) { - unsigned hp, range, attack_power, speed, price, weight; + unsigned hp, range, attack_power, speed, price, weight, num_ability_turns, + ability_activation_cost; bool is_aerial; std::cin >> hp >> range >> attack_power >> speed >> price >> is_aerial >> - weight; + weight >> num_ability_turns >> ability_activation_cost; Attacker::attribute_dictionary.insert(std::make_pair( AttackerType(attacker_type_id), - Attributes(hp, range, attack_power, speed, price, is_aerial))); + Attributes(hp, range, attack_power, speed, price, is_aerial, + num_ability_turns, ability_activation_cost))); PvPAttacker::pvp_weight_dictionary.insert( std::make_pair(AttackerType(attacker_type_id), weight)); } @@ -152,7 +172,7 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, std::cin >> hp >> range >> attack_power >> speed >> price >> is_aerial; Defender::attribute_dictionary.insert(std::make_pair( DefenderType(defender_type_id), - Attributes(hp, range, attack_power, speed, price, is_aerial))); + Attributes(hp, range, attack_power, speed, price, is_aerial, 0, 0))); } PvPLogger::log_init(); @@ -211,8 +231,28 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, player_2_set_targets[att_id] = def_id; } - game = game.simulate(player_1_set_targets, player_1_spawn_positions, - player_2_set_targets, player_2_spawn_positions); + int no_of_player_1_ability_activations = 0; + player1_stream.get() >> no_of_player_1_ability_activations; + std::vector player_1_ability_activations; + while ((no_of_player_1_ability_activations--) > 0) { + PvPGame::player_1_attacker_id att_id = 0; + player1_stream.get() >> att_id; + player_1_ability_activations.push_back(att_id); + } + + int no_of_player_2_ability_activations = 0; + player2_stream.get() >> no_of_player_2_ability_activations; + std::vector player_2_ability_activations; + while ((no_of_player_2_ability_activations--) > 0) { + PvPGame::player_2_attacker_id att_id = 0; + player2_stream.get() >> att_id; + player_2_ability_activations.push_back(att_id); + } + + game = game.simulate(turn, player_1_set_targets, player_1_spawn_positions, + player_2_set_targets, player_2_spawn_positions, + player_1_ability_activations, + player_2_ability_activations); auto player1_active_attackers = game.get_player_1_attackers(); player1_stream.put() << player1_active_attackers.size() << "\n"; @@ -222,7 +262,8 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, player1_stream.put() << attacker.get_id() << " " << attacker.get_position().get_x() << " " << attacker.get_position().get_y() << " " - << (int)attacker.get_type() << " " << attacker.get_hp() << "\n"; + << (int)attacker.get_type() << " " << attacker.get_hp() << " " + << (int)attacker.is_ability_active() << "\n"; player1_stream.put().flush(); }); @@ -236,7 +277,8 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, player1_stream.put() << attacker.get_id() << " " << attacker.get_position().get_x() << " " << attacker.get_position().get_y() << " " - << (int)attacker.get_type() << " " << attacker.get_hp() << "\n"; + << (int)attacker.get_type() << " " << attacker.get_hp() << " " + << (int)attacker.is_ability_active() << "\n"; player1_stream.put().flush(); }); player1_stream.put().flush(); @@ -247,7 +289,8 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, player2_stream.put() << attacker.get_id() << " " << attacker.get_position().get_x() << " " << attacker.get_position().get_y() << " " - << (int)attacker.get_type() << " " << attacker.get_hp() << "\n"; + << (int)attacker.get_type() << " " << attacker.get_hp() << " " + << (int)attacker.is_ability_active() << "\n"; player2_stream.put().flush(); }); @@ -259,7 +302,8 @@ void PVPGame(std::string p1_in, std::string p1_out, std::string p2_in, player2_stream.put() << attacker.get_id() << " " << attacker.get_position().get_x() << " " << attacker.get_position().get_y() << " " - << (int)attacker.get_type() << " " << attacker.get_hp() << "\n"; + << (int)attacker.get_type() << " " << attacker.get_hp() << " " + << (int)attacker.is_ability_active() << "\n"; player2_stream.put().flush(); }); diff --git a/src/utils/attributes.cpp b/src/utils/attributes.cpp index 4ee7e50..2f00ba2 100644 --- a/src/utils/attributes.cpp +++ b/src/utils/attributes.cpp @@ -1,9 +1,13 @@ #include "utils/attributes.hpp" Attributes::Attributes(unsigned hp, unsigned range, unsigned attack_power, - unsigned speed, unsigned price, bool is_aerial) + unsigned speed, unsigned price, bool is_aerial, + unsigned num_ability_turns, + unsigned ability_activation_cost) : hp(hp), range(range), attack_power(attack_power), speed(speed), - price(price), is_aerial(is_aerial) {} + price(price), is_aerial(is_aerial), num_ability_turns(num_ability_turns), + ability_activation_cost(ability_activation_cost) {} Attributes::Attributes() - : hp(0), range(0), attack_power(0), speed(0), price(0), is_aerial(false) {} + : hp(0), range(0), attack_power(0), speed(0), price(0), is_aerial(false), + num_ability_turns(0), ability_activation_cost(0) {} diff --git a/src/utils/attributes.hpp b/src/utils/attributes.hpp index 4ba8a62..a585a84 100644 --- a/src/utils/attributes.hpp +++ b/src/utils/attributes.hpp @@ -7,8 +7,12 @@ struct Attributes { const unsigned speed; const unsigned price; const bool is_aerial; + const unsigned num_ability_turns; + const unsigned ability_activation_cost; + Attributes(unsigned hp, unsigned range, unsigned attack_power, unsigned speed, - unsigned price, bool is_aerial); + unsigned price, bool is_aerial, unsigned num_ability_turns, + unsigned ability_activation_cost); /** * A default constructor is to be added for this, solely for the reason of diff --git a/test/attacker_tests.cpp b/test/attacker_tests.cpp index 3eb4923..a3dadda 100644 --- a/test/attacker_tests.cpp +++ b/test/attacker_tests.cpp @@ -12,7 +12,7 @@ SCENARIO("Attacker::move") { Position initial_position(0, 0); Attacker::attribute_dictionary.clear(); Attacker::attribute_dictionary.insert(std::make_pair( - AttackerType::A1, Attributes(0, range, 0, speed, 0, true))); + AttackerType::A1, Attributes(0, range, 0, speed, 0, true, 0, 0))); Attacker attacker = Attacker::construct(AttackerType::A1, initial_position); WHEN("distance is more than range + speed") { @@ -56,7 +56,7 @@ SCENARIO("Attacker::move") { Position initial_position(0, 0); Attacker::attribute_dictionary.clear(); Attacker::attribute_dictionary.insert(std::make_pair( - AttackerType::A1, Attributes(0, range, 0, speed, 0, true))); + AttackerType::A1, Attributes(0, range, 0, speed, 0, true, 0, 0))); Attacker attacker = Attacker::construct(AttackerType::A1, initial_position); WHEN("distance is more than range + speed") { @@ -100,7 +100,7 @@ SCENARIO("Attacker::move") { Position initial_position(0, 0); Attacker::attribute_dictionary.clear(); Attacker::attribute_dictionary.insert(std::make_pair( - AttackerType::A1, Attributes(0, range, 0, speed, 0, true))); + AttackerType::A1, Attributes(0, range, 0, speed, 0, true, 0, 0))); Attacker attacker = Attacker::construct(AttackerType::A1, initial_position); WHEN("distance is more than range + speed") { @@ -144,8 +144,8 @@ SCENARIO("Attacker::move") { SCENARIO("Aerial Attacker::get_nearest_defender_index") { GIVEN("a list of defenders of varying length") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, true))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); Attacker attacker = Attacker::construct(AttackerType::A1, {0, 0}); WHEN("the given list is empty") { @@ -182,10 +182,10 @@ SCENARIO("Ground Attacker::get_nearest_defender_index") { GIVEN("List of different types of defenders around") { WHEN("The list has both ground defenders and aerial defenders") { Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 0, 0, 0, 0, true))); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D2, Attributes(0, 0, 0, 0, 0, false))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D2, Attributes(0, 0, 0, 0, 0, false, 0, 0))); std::vector defenders{ Defender::construct(DefenderType::D1, {0, 1}), Defender::construct(DefenderType::D1, {0, 2}), @@ -196,8 +196,8 @@ SCENARIO("Ground Attacker::get_nearest_defender_index") { Defender::construct(DefenderType::D2, {0, 7})}; Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, false))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, false, 0, 0))); Attacker attacker = Attacker::construct(AttackerType::A1, {0, 0}); auto nearest_defender = attacker.get_nearest_defender_index(defenders); diff --git a/test/defender_tests.cpp b/test/defender_tests.cpp index a8f69ee..d86783f 100644 --- a/test/defender_tests.cpp +++ b/test/defender_tests.cpp @@ -6,8 +6,8 @@ SCENARIO("Defender::get_nearest_attacker_index") { GIVEN("a list of attackers of varying length") { Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 0, 0, 0, 0, true))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); WHEN("the given list is empty") { @@ -44,10 +44,10 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { GIVEN("List of different types of attackers around") { WHEN("The list has both ground attackers and aerial attackers") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, true))); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A2, Attributes(0, 0, 0, 0, 0, false))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A2, Attributes(0, 0, 0, 0, 0, false, 0, 0))); std::vector attackers{ Attacker::construct(AttackerType::A1, {0, 1}), Attacker::construct(AttackerType::A1, {0, 2}), @@ -58,8 +58,8 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { Attacker::construct(AttackerType::A2, {0, 7})}; Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 0, 0, 0, 0, false))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 0, 0, 0, 0, false, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); auto nearest_attacker = defender.get_nearest_attacker_index(attackers); @@ -72,8 +72,8 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { WHEN("The list has only aerial attackers") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, true))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); std::vector attackers{ Attacker::construct(AttackerType::A1, {0, 1}), Attacker::construct(AttackerType::A1, {0, 2}), @@ -84,8 +84,8 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { Attacker::construct(AttackerType::A1, {0, 7})}; Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 0, 0, 0, 0, false))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 0, 0, 0, 0, false, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); auto nearest_attacker = defender.get_nearest_attacker_index(attackers); @@ -97,8 +97,8 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { WHEN("The list has only ground attackers") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A2, Attributes(0, 0, 0, 0, 0, false))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A2, Attributes(0, 0, 0, 0, 0, false, 0, 0))); std::vector attackers{ Attacker::construct(AttackerType::A2, {0, 1}), Attacker::construct(AttackerType::A2, {0, 2}), @@ -109,8 +109,8 @@ SCENARIO("Ground Defender::get_nearest_attacker_index") { Attacker::construct(AttackerType::A2, {0, 7})}; Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 0, 0, 0, 0, false))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 0, 0, 0, 0, false, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); auto nearest_attacker = defender.get_nearest_attacker_index(attackers); @@ -128,10 +128,10 @@ SCENARIO("Aerial Defender::get_nearest_attacker_index") { WHEN("The list has both ground attackers and aerial attackers with aerial " "attacker in range") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, true))); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A2, Attributes(0, 0, 0, 0, 0, false))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A2, Attributes(0, 0, 0, 0, 0, false, 0, 0))); std::vector attackers{ Attacker::construct(AttackerType::A1, {0, 1}), Attacker::construct(AttackerType::A1, {0, 2}), @@ -142,8 +142,8 @@ SCENARIO("Aerial Defender::get_nearest_attacker_index") { Attacker::construct(AttackerType::A2, {0, 0})}; Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 3, 0, 0, 0, true))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 3, 0, 0, 0, true, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); auto nearest_attacker = defender.get_nearest_attacker_index(attackers); @@ -157,10 +157,10 @@ SCENARIO("Aerial Defender::get_nearest_attacker_index") { WHEN("The list has both ground attackers and aerial attackers with aerial " "attacker not in range") { Attacker::attribute_dictionary.clear(); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 0, 0, 0, 0, true))); - Attacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A2, Attributes(0, 0, 0, 0, 0, false))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 0, 0, 0, 0, true, 0, 0))); + Attacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A2, Attributes(0, 0, 0, 0, 0, false, 0, 0))); std::vector attackers{ Attacker::construct(AttackerType::A1, {0, 5}), Attacker::construct(AttackerType::A1, {0, 6}), @@ -171,8 +171,8 @@ SCENARIO("Aerial Defender::get_nearest_attacker_index") { Attacker::construct(AttackerType::A2, {0, 0})}; Defender::attribute_dictionary.clear(); - Defender::attribute_dictionary.insert( - std::make_pair(DefenderType::D1, Attributes(0, 3, 0, 0, 0, true))); + Defender::attribute_dictionary.insert(std::make_pair( + DefenderType::D1, Attributes(0, 3, 0, 0, 0, true, 0, 0))); Defender defender = Defender::construct(DefenderType::D1, {0, 0}); auto nearest_attacker = defender.get_nearest_attacker_index(attackers); diff --git a/test/game_tests.cpp b/test/game_tests.cpp index 822e525..531843a 100644 --- a/test/game_tests.cpp +++ b/test/game_tests.cpp @@ -47,16 +47,16 @@ SCENARIO("Game::simulate") { 50, // attack_power 0, // speed 0, - true // price - ))); + true, // price + 10, 5))); Defender::attribute_dictionary.insert( std::make_pair(DefenderType::D2, Attributes(200, // hp 3, // range 100, // attack_power 0, // speed 0, - false // price - ))); + false, // price + 10, 5))); Attacker::attribute_dictionary.clear(); Attacker::attribute_dictionary.insert( @@ -65,8 +65,8 @@ SCENARIO("Game::simulate") { 50, // attack_power 2, // speed 100, - true // price - ))); + true, // price, + 10, 5))); Attacker::attribute_dictionary.insert( std::make_pair(AttackerType::A2, Attributes(150, // hp @@ -74,12 +74,12 @@ SCENARIO("Game::simulate") { 75, // attack_power 3, // speed 150, - false // price - ))); + false, // price, + 0, 0))); std::vector defenders_initial_state = game_map.spawn_defenders(); Game game(std::vector{}, defenders_initial_state, - 1200 // number of coins + 1200 // number of coins, ); std::vector> initial_spawn_positions{ {{4, 0}, AttackerType::A1}, // cost 100 @@ -102,8 +102,9 @@ SCENARIO("Game::simulate") { std::vector> fifth_turn_spawn_pos; + Game::turn_t turn = 0; + std::vector activations; // FIRST TURN - // First turn, no attackers would be there so no point in adding a target // If lets say somehow there's some requests for targets by the player in // first turn itself, that means it is invalid. @@ -112,8 +113,8 @@ SCENARIO("Game::simulate") { {1, 1}, {2, 1}}; // attacker_id 1 and 2 both targetting defender_id 1 - Game first_turn_state = - game.simulate(first_turn_player_set_targets, initial_spawn_positions); + Game first_turn_state = game.simulate(turn++, first_turn_player_set_targets, + initial_spawn_positions, activations); // SECOND TURN @@ -128,15 +129,17 @@ SCENARIO("Game::simulate") { second_turn_player_set_targets = { {manually_attacking_attackers_id, targetted_defenders_id}}; - Game second_turn_state = first_turn_state.simulate( - second_turn_player_set_targets, second_turn_spawn_pos); + Game second_turn_state = + first_turn_state.simulate(turn++, second_turn_player_set_targets, + second_turn_spawn_pos, activations); // THIRD TURN std::unordered_map third_turn_player_set_targets; - Game third_turn_state = second_turn_state.simulate( - third_turn_player_set_targets, third_turn_spawn_pos); + Game third_turn_state = + second_turn_state.simulate(turn++, third_turn_player_set_targets, + third_turn_spawn_pos, activations); // FOURTH TURN const Attacker attacker_at_0_0 = *find_actor_by_position( @@ -146,15 +149,17 @@ SCENARIO("Game::simulate") { std::unordered_map fourth_turn_player_set_targets = { {attacker_at_0_0.get_id(), defender_at_4_5.get_id()}}; - Game fourth_turn_state = third_turn_state.simulate( - fourth_turn_player_set_targets, fourth_turn_spawn_pos); + Game fourth_turn_state = + third_turn_state.simulate(turn++, fourth_turn_player_set_targets, + fourth_turn_spawn_pos, activations); // FIFTH TURN std::unordered_map fifth_turn_player_set_targets = { {attacker_at_0_0.get_id(), defender_at_4_5.get_id()}}; - Game fifth_turn_state = fourth_turn_state.simulate( - fourth_turn_player_set_targets, fourth_turn_spawn_pos); + Game fifth_turn_state = + fourth_turn_state.simulate(turn++, fourth_turn_player_set_targets, + fourth_turn_spawn_pos, activations); WHEN("FIRST TURN") { THEN("ONLY NEW ATTACKERS WILL BE SPAWNED IN THE GAME, ALL DEFENDERS ARE " @@ -411,4 +416,85 @@ SCENARIO("Game::simulate") { } } } + + // ABILITY TESTS + + const unsigned ACTIVATION_TURNS = 3; + const unsigned INITIAL_HP = 10000; + + // We're making the HP very high so that no one dies. + // We just want to check wether they take damage or not. + // The range is also very high so that the attacker is always + // be able to be attacked. + Defender::attribute_dictionary.clear(); + Defender::attribute_dictionary.insert( + std::make_pair(DefenderType::D1, Attributes(INITIAL_HP, // hp + 2000000, // range + 50, // attack_power + 0, // speed + 0, + true, // price + 0, 0))); + + Attacker::attribute_dictionary.clear(); + Attacker::attribute_dictionary.insert( + std::make_pair(AttackerType::A1, Attributes(INITIAL_HP, // hp + 2000000, // range 2 + 50, // attack_power + 2, // speed + 100, + true, // price, + ACTIVATION_TURNS, 0))); + + std::vector ability_defenders_initial_state = { + Defender::construct(DefenderType::D1, {0, 0}), + }; + + Game abilitygame(std::vector{}, ability_defenders_initial_state, + 100000 // number of coins, + ); + + std::vector> ability_test_spawn_positions{ + {{0, 0}, AttackerType::A1}, + }; + + Game::turn_t turn = 0; + + abilitygame = + abilitygame.simulate(turn++, {}, ability_test_spawn_positions, {}); + + // auto attackers = abilitygame.get_attackers(); + WHEN("INITIAL SINGLE ATTACKER") { + THEN("There is only one single attacker on the map right now.") { + auto cur_attackers = abilitygame.get_attackers(); + REQUIRE(cur_attackers.size() == 1); + + abilitygame = abilitygame.simulate(turn++, {}, {}, {}); + + // The attacker should have taken damage. + REQUIRE(abilitygame.get_attackers().at(0).get_hp() < INITIAL_HP); + + auto prev_hp = abilitygame.get_attackers().at(0).get_hp(); + abilitygame = abilitygame.simulate(turn++, {}, {}, {}); + + // The attacker should continuously be taking damage. + REQUIRE(abilitygame.get_attackers().at(0).get_hp() < prev_hp); + auto id = abilitygame.get_attackers().at(0).get_id(); + + // Now we're gonna activate the ability. + abilitygame = abilitygame.simulate(turn++, {}, {}, {id}); + + // For the next couple of turns, no damage should be taken. + for (unsigned i = 0; i < ACTIVATION_TURNS; ++i) { + prev_hp = abilitygame.get_attackers().at(0).get_hp(); + abilitygame = abilitygame.simulate(turn++, {}, {}, {}); + REQUIRE(abilitygame.get_attackers().at(0).get_hp() == prev_hp); + } + + // Now that the ability is over, we should start taking damage again. + prev_hp = abilitygame.get_attackers().at(0).get_hp(); + abilitygame = abilitygame.simulate(turn++, {}, {}, {}); + REQUIRE(abilitygame.get_attackers().at(0).get_hp() < prev_hp); + } + } } diff --git a/test/pvpattacker_tests.cpp b/test/pvpattacker_tests.cpp index 74fed8d..20e3c17 100644 --- a/test/pvpattacker_tests.cpp +++ b/test/pvpattacker_tests.cpp @@ -7,12 +7,12 @@ SCENARIO("PvPAttacker::get_nearest_defender_index") { GIVEN("a list of defenders") { PvPAttacker::attribute_dictionary.clear(); - PvPAttacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A1, Attributes(0, 2, 0, 4, 0, false))); - PvPAttacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A2, Attributes(0, 4, 0, 2, 0, false))); - PvPAttacker::attribute_dictionary.insert( - std::make_pair(AttackerType::A3, Attributes(0, 4, 0, 4, 0, true))); + PvPAttacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A1, Attributes(0, 2, 0, 4, 0, false, 1, 1))); + PvPAttacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A2, Attributes(0, 4, 0, 2, 0, false, 1, 1))); + PvPAttacker::attribute_dictionary.insert(std::make_pair( + AttackerType::A3, Attributes(0, 4, 0, 4, 0, true, 1, 1))); std::vector attackers{ PvPAttacker::construct(AttackerType::A1, {0, 1}, Owner::PLAYER1), PvPAttacker::construct(AttackerType::A2, {0, 2}, Owner::PLAYER1), diff --git a/test/pvpgame_tests.cpp b/test/pvpgame_tests.cpp index 4d704bb..472e226 100644 --- a/test/pvpgame_tests.cpp +++ b/test/pvpgame_tests.cpp @@ -24,19 +24,18 @@ SCENARIO("PVPGame::simulate") { PvPAttacker::attribute_dictionary.clear(); PvPAttacker::attribute_dictionary.insert(std::make_pair( AttackerType::A1, - Attributes( - 10, 1, 5, 4, 50, - false))); // hp = 10, range = 1, damage = 5, speed = 4, cost = 50 + Attributes(10, 1, 5, 4, 50, false, 1, + 1))); // hp = 10, range = 1, damage = 5, speed = 4, cost = 50 PvPAttacker::attribute_dictionary.insert(std::make_pair( AttackerType::A2, Attributes( - 20, 1, 5, 4, 100, - false))); // hp = 20, range = 1, damage = 5, speed = 4, cost = 100 + 20, 1, 5, 4, 100, false, 1, + 1))); // hp = 20, range = 1, damage = 5, speed = 4, cost = 100 PvPAttacker::attribute_dictionary.insert(std::make_pair( AttackerType::A3, Attributes( - 20, 1, 5, 4, 150, - true))); // hp = 20, range = 1, damage = 5, speed = 4, cost = 150 + 20, 1, 5, 4, 150, true, 1, + 1))); // hp = 20, range = 1, damage = 5, speed = 4, cost = 150 std::vector> first_turn_attackers{ {Position(0, 2), AttackerType::A1}, {Position(0, 4), AttackerType::A3}, @@ -65,6 +64,7 @@ SCENARIO("PVPGame::simulate") { std::vector> fifth_turn_defenders; std::vector> sixth_turn_attackers; std::vector> sixth_turn_defenders; + // TODO: Add tests for the case of abilities in pvp game std::vector attackers; std::vector defenders; PvPGame pvpgame(attackers, defenders); @@ -72,30 +72,30 @@ SCENARIO("PVPGame::simulate") { // FIRST TURN PvPGame first_turn_state = - pvpgame.simulate({{0, 0}, {1, 1}}, first_turn_attackers, - {{0, 0}, {1, 1}}, first_turn_defenders); + pvpgame.simulate(0, {{0, 0}, {1, 1}}, first_turn_attackers, + {{0, 0}, {1, 1}}, first_turn_defenders, {}, {}); first_turn_state.FIXED_COINS_PER_TURN = 500; // SECOND TURN PvPGame second_turn_state = first_turn_state.simulate( - {}, second_turn_attackers, {}, second_turn_defenders); + 1, {}, second_turn_attackers, {}, second_turn_defenders, {}, {}); second_turn_state.FIXED_COINS_PER_TURN = 500; // THIRD TURN PvPGame third_turn_state = second_turn_state.simulate( - {}, third_turn_attackers, {}, third_turn_defenders); + 2, {}, third_turn_attackers, {}, third_turn_defenders, {}, {}); third_turn_state.FIXED_COINS_PER_TURN = 60; // won't spawn as insufficient coins // FOURTH TURN PvPGame fourth_turn_state = third_turn_state.simulate( - {}, fourth_turn_attackers, {}, fourth_turn_defenders); + 3, {}, fourth_turn_attackers, {}, fourth_turn_defenders, {}, {}); // FIFTH TURN PvPGame fifth_turn_state = fourth_turn_state.simulate( - {}, fifth_turn_attackers, {}, fifth_turn_defenders); + 4, {}, fifth_turn_attackers, {}, fifth_turn_defenders, {}, {}); // SIXTH TURN PvPGame sixth_turn_state = fifth_turn_state.simulate( - {}, sixth_turn_attackers, {}, sixth_turn_defenders); + 5, {}, sixth_turn_attackers, {}, sixth_turn_defenders, {}, {}); WHEN("first turn is simulated") { THEN("all troops are in their respective positions") {