Skip to content

Commit

Permalink
feat: unit tests for pvpgame and pvpattacker (#3)
Browse files Browse the repository at this point in the history
* wip: pvp tests

* feat: unit tests for pvp
  • Loading branch information
killerninjacat authored Feb 6, 2024
1 parent c93fee4 commit b448a5d
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/attacker/pvpattacker/pvpattacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "logger/pvplogger.hpp"
#include "utils/attributes.hpp"

#include <algorithm>

PvPAttacker PvPAttacker::construct(AttackerType type, Position p, Owner owner) {
Attributes attr = Attacker::attribute_dictionary[type];
return {type, p,
Expand Down
115 changes: 115 additions & 0 deletions test/pvpattacker_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "attacker/pvpattacker/pvpattacker.hpp"

#include <catch2/catch.hpp>
#include <utility>
#include <vector>

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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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<PvPAttacker> 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);
}
}
}
}
}
}
231 changes: 231 additions & 0 deletions test/pvpgame_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#include "attacker/pvpattacker/pvpattacker.hpp"
#include "game/pvpgame.hpp"
#include "utils/game_map.hpp"

#include <catch2/catch.hpp>

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<std::pair<Position, AttackerType>> first_turn_attackers{
{Position(0, 2), AttackerType::A1},
{Position(0, 4), AttackerType::A3},
};
std::vector<std::pair<Position, AttackerType>> first_turn_defenders{
{Position(9, 2), AttackerType::A2},
{Position(9, 4), AttackerType::A3},
};
std::vector<std::pair<Position, AttackerType>> second_turn_attackers{
{Position(0, 3), AttackerType::A2},
};
std::vector<std::pair<Position, AttackerType>> second_turn_defenders{
{Position(9, 6), AttackerType::A1},
};
std::vector<std::pair<Position, AttackerType>> third_turn_attackers{
{Position(3, 3), AttackerType::A2}, // won't spawn as invalid position
};
std::vector<std::pair<Position, AttackerType>> third_turn_defenders;
std::vector<std::pair<Position, AttackerType>> fourth_turn_attackers{
{Position(0, 7), AttackerType::A2},
};
std::vector<std::pair<Position, AttackerType>> fourth_turn_defenders{
{Position(9, 9), AttackerType::A2},
};
std::vector<std::pair<Position, AttackerType>> fifth_turn_attackers;
std::vector<std::pair<Position, AttackerType>> fifth_turn_defenders;
std::vector<std::pair<Position, AttackerType>> sixth_turn_attackers;
std::vector<std::pair<Position, AttackerType>> sixth_turn_defenders;
std::vector<PvPAttacker> attackers;
std::vector<PvPAttacker> 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>{
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>{
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>{
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());
}
}
}
}

0 comments on commit b448a5d

Please sign in to comment.