Skip to content

Commit

Permalink
feat: abilities (#2)
Browse files Browse the repository at this point in the history
* feat: invuln ability

* fix: main & tests

* fix: include cassert

* move ability into attacker class

* modify attribute dict

* update comparator

* fix: comparator takes ability to account

* add tests for attacker abilities

* fix: minor fixes

* fix: integrate with abilities

---------

Co-authored-by: shubham-1806 <[email protected]>
  • Loading branch information
avyjit and shubham-1806 authored Feb 10, 2024
1 parent b448a5d commit bacf676
Show file tree
Hide file tree
Showing 15 changed files with 398 additions and 133 deletions.
24 changes: 24 additions & 0 deletions src/attacker/attacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
10 changes: 10 additions & 0 deletions src/attacker/attacker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -77,4 +84,7 @@ class Attacker : public Actor {
Position _destination;
bool _is_target_set_by_player;
size_t _target_id;

std::optional<size_t> _ability_activated_turn;
bool _is_ability_active = false;
};
17 changes: 13 additions & 4 deletions src/defender/defender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ std::optional<size_t> 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;
Expand All @@ -47,20 +46,30 @@ std::optional<size_t> 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());
});
Expand Down
44 changes: 35 additions & 9 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,24 @@ Game::Game(std::vector<Attacker> attackers, std::vector<Defender> defenders,
}

Game Game::simulate(
const Game::turn_t turn,
const std::unordered_map<attacker_id, defender_id> &player_set_targets,
const std::vector<std::pair<Position, AttackerType>> &spawn_positions)
const {
const std::vector<std::pair<Position, AttackerType>> &spawn_positions,
const std::vector<attacker_id> &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<Attacker> attackers(prev_state_attackers.begin(),
prev_state_attackers.end());
std::vector<Defender> 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,
Expand All @@ -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<index_t> defender_index{std::nullopt};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -131,8 +159,6 @@ Game Game::simulate(
}),
defenders.end());

auto coins_left = this->get_coins();

// new attackers are spawned here
auto positions = std::set<Position>{};
ranges::for_each(spawn_positions, [&](const auto &spawn_details) {
Expand Down
14 changes: 9 additions & 5 deletions src/game/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@
#include "utils/position.hpp"

#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>

class Game {

public:
Game(std::vector<Attacker> attackers, std::vector<Defender> 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<Attacker> attackers, std::vector<Defender> defenders,
unsigned coins);

[[nodiscard]] const std::vector<Attacker> &get_attackers() const;

[[nodiscard]] const std::vector<Defender> &get_defenders() const;

[[nodiscard]] Game simulate(
const turn_t turn,
const std::unordered_map<attacker_id, defender_id> &player_set_targets,
const std::vector<std::pair<Position, AttackerType>> &spawn_positions)
const;
const std::vector<std::pair<Position, AttackerType>> &spawn_positions,
const std::vector<attacker_id> &ability_activations);

[[nodiscard]] unsigned get_coins() const;

Expand Down
59 changes: 54 additions & 5 deletions src/game/pvpgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ PvPGame::PvPGame(std::vector<PvPAttacker> player_1_attackers,
}

PvPGame PvPGame::simulate(
const turn_t turn,
const std::unordered_map<player_1_attacker_id, player_2_attacker_id>
&player_1_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_1_spawn_positions,
const std::unordered_map<player_2_attacker_id, player_1_attacker_id>
&player_2_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_2_spawn_positions) const {
&player_2_spawn_positions,
const std::vector<player_1_attacker_id> &player_1_ability_activations,
const std::vector<player_2_attacker_id> &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();
Expand All @@ -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<player_1_attacker_id, player_2_attacker_id> &entry) {
Expand Down Expand Up @@ -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<index_t> player_2_attacker_index{std::nullopt};
if (attacker.is_target_set_by_player() &&
Expand Down Expand Up @@ -173,8 +224,6 @@ PvPGame PvPGame::simulate(

auto player_1_positions = std::set<Position>{};
auto player_2_positions = std::set<Position>{};
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) {
Expand Down
23 changes: 14 additions & 9 deletions src/game/pvpgame.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,27 @@ 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;

[[nodiscard]] const std::vector<PvPAttacker> &get_player_1_attackers() const;

[[nodiscard]] const std::vector<PvPAttacker> &get_player_2_attackers() const;

[[nodiscard]] PvPGame
simulate(const std::unordered_map<player_1_attacker_id, player_2_attacker_id>
&player_1_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_1_spawn_positions,
const std::unordered_map<player_2_attacker_id, player_1_attacker_id>
&player_2_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_2_spawn_positions) const;
[[nodiscard]] PvPGame simulate(
const turn_t turn,
const std::unordered_map<player_1_attacker_id, player_2_attacker_id>
&player_1_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_1_spawn_positions,
const std::unordered_map<player_2_attacker_id, player_1_attacker_id>
&player_2_set_targets,
const std::vector<std::pair<Position, AttackerType>>
&player_2_spawn_positions,
const std::vector<player_1_attacker_id> &player_1_ability_activations,
const std::vector<player_2_attacker_id> &player_2_ability_activations)
const;

private:
std::optional<index_t>
Expand Down
Loading

0 comments on commit bacf676

Please sign in to comment.