From b448a5d2c6699113bb589698911ee07a9a7e6d81 Mon Sep 17 00:00:00 2001 From: Nithin Balan <131035683+killerninjacat@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:35:43 +0530 Subject: [PATCH] feat: unit tests for pvpgame and pvpattacker (#3) * wip: pvp tests * feat: unit tests for pvp --- src/attacker/pvpattacker/pvpattacker.cpp | 2 + test/pvpattacker_tests.cpp | 115 +++++++++++ test/pvpgame_tests.cpp | 231 +++++++++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 test/pvpattacker_tests.cpp create mode 100644 test/pvpgame_tests.cpp diff --git a/src/attacker/pvpattacker/pvpattacker.cpp b/src/attacker/pvpattacker/pvpattacker.cpp index 3ad585d..59c7b5b 100644 --- a/src/attacker/pvpattacker/pvpattacker.cpp +++ b/src/attacker/pvpattacker/pvpattacker.cpp @@ -2,6 +2,8 @@ #include "logger/pvplogger.hpp" #include "utils/attributes.hpp" +#include + PvPAttacker PvPAttacker::construct(AttackerType type, Position p, Owner owner) { Attributes attr = Attacker::attribute_dictionary[type]; return {type, p, diff --git a/test/pvpattacker_tests.cpp b/test/pvpattacker_tests.cpp new file mode 100644 index 0000000..74fed8d --- /dev/null +++ b/test/pvpattacker_tests.cpp @@ -0,0 +1,115 @@ +#include "attacker/pvpattacker/pvpattacker.hpp" + +#include +#include +#include + +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))); + std::vector attackers{ + PvPAttacker::construct(AttackerType::A1, {0, 1}, Owner::PLAYER1), + PvPAttacker::construct(AttackerType::A2, {0, 2}, Owner::PLAYER1), + PvPAttacker::construct(AttackerType::A3, {0, 4}, Owner::PLAYER1), + }; + WHEN("the list is empty") { + std::vector defenders; + + PvPAttacker currentAttacker = attackers[0]; + + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + + THEN("nearest defender does not exist") { + REQUIRE(nearest_defender_index.has_value() == false); + } + } + + WHEN("list has defenders") { + WHEN("the attacker is aerial") { + PvPAttacker currentAttacker = attackers[2]; + WHEN("the defender is aerial") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A3, {17, 3}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A3, {10, 2}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender is the closest defender") { + REQUIRE(nearest_defender_index.has_value() == true); + REQUIRE(nearest_defender_index.value() == 1); + } + } + WHEN("the defender is ground") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A1, {4, 4}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A2, {0, 9}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender is the closest defender") { + REQUIRE(nearest_defender_index.has_value() == true); + REQUIRE(nearest_defender_index.value() == 0); + } + } + WHEN("the defender can be either aerial or ground") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A1, {3, 0}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A3, {1, 4}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender is the aerial defender in range") { + REQUIRE(nearest_defender_index.has_value() == true); + REQUIRE(nearest_defender_index.value() == 1); + } + } + } + + WHEN("the attacker is ground") { + PvPAttacker currentAttacker = attackers[1]; + WHEN("the defender is aerial") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A3, {1, 2}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A3, {7, 3}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender does not exist") { + REQUIRE(nearest_defender_index.has_value() == false); + } + } + WHEN("the defender is ground") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A1, {0, 9}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A2, {4, 4}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender is the closest defender") { + REQUIRE(nearest_defender_index.has_value() == true); + REQUIRE(nearest_defender_index.value() == 1); + } + } + WHEN("the defender can be either aerial or ground") { + std::vector defenders{ + PvPAttacker::construct(AttackerType::A1, {6, 1}, Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A3, {3, 0}, Owner::PLAYER2), + }; + auto nearest_defender_index = + currentAttacker.get_nearest_defender_index(defenders); + THEN("nearest defender is the ground defender") { + REQUIRE(nearest_defender_index.has_value() == true); + REQUIRE(nearest_defender_index.value() == 0); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/pvpgame_tests.cpp b/test/pvpgame_tests.cpp new file mode 100644 index 0000000..4d704bb --- /dev/null +++ b/test/pvpgame_tests.cpp @@ -0,0 +1,231 @@ +#include "attacker/pvpattacker/pvpattacker.hpp" +#include "game/pvpgame.hpp" +#include "utils/game_map.hpp" + +#include + +SCENARIO("PVPGame::simulate") { + + GIVEN("a game with 2 attackers each") { + Map::no_of_rows = 10; + Map::no_of_cols = 10; + Map game_map({ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }); + 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 + 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 + 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 + std::vector> first_turn_attackers{ + {Position(0, 2), AttackerType::A1}, + {Position(0, 4), AttackerType::A3}, + }; + std::vector> first_turn_defenders{ + {Position(9, 2), AttackerType::A2}, + {Position(9, 4), AttackerType::A3}, + }; + std::vector> second_turn_attackers{ + {Position(0, 3), AttackerType::A2}, + }; + std::vector> second_turn_defenders{ + {Position(9, 6), AttackerType::A1}, + }; + std::vector> third_turn_attackers{ + {Position(3, 3), AttackerType::A2}, // won't spawn as invalid position + }; + std::vector> third_turn_defenders; + std::vector> fourth_turn_attackers{ + {Position(0, 7), AttackerType::A2}, + }; + std::vector> fourth_turn_defenders{ + {Position(9, 9), AttackerType::A2}, + }; + std::vector> fifth_turn_attackers; + std::vector> fifth_turn_defenders; + std::vector> sixth_turn_attackers; + std::vector> sixth_turn_defenders; + std::vector attackers; + std::vector defenders; + PvPGame pvpgame(attackers, defenders); + pvpgame.FIXED_COINS_PER_TURN = 500; + + // FIRST TURN + PvPGame first_turn_state = + pvpgame.simulate({{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); + second_turn_state.FIXED_COINS_PER_TURN = 500; + // THIRD TURN + PvPGame third_turn_state = second_turn_state.simulate( + {}, 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); + + // FIFTH TURN + PvPGame fifth_turn_state = fourth_turn_state.simulate( + {}, fifth_turn_attackers, {}, fifth_turn_defenders); + + // SIXTH TURN + PvPGame sixth_turn_state = fifth_turn_state.simulate( + {}, sixth_turn_attackers, {}, sixth_turn_defenders); + + WHEN("first turn is simulated") { + THEN("all troops are in their respective positions") { + REQUIRE(first_turn_state.get_player_1_attackers() == + std::vector{ + PvPAttacker::construct(AttackerType::A1, {0, 2}, + Owner::PLAYER1), + PvPAttacker::construct(AttackerType::A3, {0, 4}, + Owner::PLAYER1), + }); + } + THEN("set targets are invalid") { + REQUIRE( + std::ranges::none_of(first_turn_state.get_player_1_attackers(), + [](const PvPAttacker &pvpattacker) { + return pvpattacker.is_target_set_by_player(); + })); + REQUIRE( + std::ranges::none_of(first_turn_state.get_player_2_attackers(), + [](const PvPAttacker &pvpattacker) { + return pvpattacker.is_target_set_by_player(); + })); + } + THEN("nearest defender is identified correctly") { + REQUIRE(first_turn_state.get_player_1_attackers()[0] + .get_nearest_defender_index( + first_turn_state.get_player_2_attackers()) + .value() == 0); + REQUIRE(first_turn_state.get_player_1_attackers()[1] + .get_nearest_defender_index( + first_turn_state.get_player_2_attackers()) + .value() == 1); + } + } + WHEN("second turn is simulated") { + THEN("all troops of both players are alive and new troops are spawned") { + REQUIRE(second_turn_state.get_player_1_attackers() == + std::vector{ + PvPAttacker::construct(AttackerType::A1, {4, 2}, + Owner::PLAYER1), + PvPAttacker::construct(AttackerType::A3, {4, 4}, + Owner::PLAYER1), + PvPAttacker::construct(AttackerType::A2, {0, 3}, + Owner::PLAYER1), + }); + REQUIRE(second_turn_state.get_player_2_attackers() == + std::vector{ + PvPAttacker::construct(AttackerType::A2, {5, 2}, + Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A3, {5, 4}, + Owner::PLAYER2), + PvPAttacker::construct(AttackerType::A1, {9, 6}, + Owner::PLAYER2), + }); + } + THEN("no troops have attacked yet") { + for (int i = 0; i < 2; i++) { + REQUIRE(second_turn_state.get_player_1_attackers()[i].get_hp() == + first_turn_state.get_player_1_attackers()[i].get_hp()); + REQUIRE(second_turn_state.get_player_2_attackers()[i].get_hp() == + first_turn_state.get_player_2_attackers()[i].get_hp()); + } + } + THEN("troops spawned in the previous turn are in the MOVING state and " + "troops spawned in this turn are in the SPAWNED state") { + REQUIRE(second_turn_state.get_player_1_attackers()[2].get_state() == + Attacker::State::SPAWNED); + REQUIRE(second_turn_state.get_player_2_attackers()[2].get_state() == + Attacker::State::SPAWNED); + for (int index = 0; index < 2; index++) { + REQUIRE( + second_turn_state.get_player_1_attackers()[index].get_state() == + Attacker::State::MOVING); + REQUIRE( + second_turn_state.get_player_2_attackers()[index].get_state() == + Attacker::State::MOVING); + } + } + } + WHEN("third turn is simulated") { + THEN("all troops of both players are alive but no new troops are " + "spawned") { + REQUIRE(third_turn_state.get_player_1_attackers().size() == 3); + REQUIRE(third_turn_state.get_player_2_attackers().size() == 3); + } + THEN("troops spawned in first turn have started attacking and " + "troops spawned in second turn have not started attacking yet") { + for (int i = 0; i < 2; i++) { + REQUIRE(second_turn_state.get_player_1_attackers()[i].get_hp() > + third_turn_state.get_player_1_attackers()[i].get_hp()); + REQUIRE(second_turn_state.get_player_2_attackers()[i].get_hp() > + third_turn_state.get_player_2_attackers()[i].get_hp()); + } + REQUIRE(second_turn_state.get_player_1_attackers()[2].get_hp() == + third_turn_state.get_player_1_attackers()[2].get_hp()); + REQUIRE(second_turn_state.get_player_2_attackers()[2].get_hp() == + third_turn_state.get_player_2_attackers()[2].get_hp()); + } + } + WHEN("fourth turn is simulated") { + THEN("some troops are gone and no new troops are spawned") { + REQUIRE(fourth_turn_state.get_player_1_attackers().size() == 2); + REQUIRE(fourth_turn_state.get_player_2_attackers().size() == 3); + } + THEN("first attacker is dead for player 1") { + REQUIRE(fourth_turn_state.get_player_1_attackers()[0].get_id() != + third_turn_state.get_player_1_attackers()[0].get_id()); + } + } + WHEN("fifth turn is simulated") { + THEN("some troops are gone") { + REQUIRE(fifth_turn_state.get_player_1_attackers().size() == 2); + REQUIRE(fifth_turn_state.get_player_2_attackers().size() == 2); + } + THEN("first attacker is dead for player 2") { + REQUIRE(fifth_turn_state.get_player_2_attackers()[0].get_id() != + fourth_turn_state.get_player_2_attackers()[0].get_id()); + } + } + WHEN("sixth turn is simulated") { + THEN("some troops are gone") { + REQUIRE(sixth_turn_state.get_player_1_attackers().size() == 1); + REQUIRE(sixth_turn_state.get_player_2_attackers().size() == 1); + } + THEN("only the last spawned attacker is aliive for both players") { + REQUIRE(sixth_turn_state.get_player_1_attackers()[0].get_id() == + second_turn_state.get_player_1_attackers()[2].get_id()); + REQUIRE(sixth_turn_state.get_player_2_attackers()[0].get_id() == + second_turn_state.get_player_2_attackers()[2].get_id()); + } + } + } +} \ No newline at end of file