Skip to content

Commit

Permalink
feat: pvp (#4)
Browse files Browse the repository at this point in the history
* wip:pvp

* feat: pvp main, fds

* feat: pvp

* feat: pvp logger,attackers

* feat:pvp

* update: loggers

* feat: unit tests for pvpgame and pvpattacker (#3)

* wip: pvp tests

* feat: unit tests for pvp

* feat: abilities (#2)

* 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]>

---------

Co-authored-by: Bhoopesh <[email protected]>
Co-authored-by: Nithin Balan <[email protected]>
Co-authored-by: Avyyukt Ajith <[email protected]>
  • Loading branch information
4 people authored Feb 25, 2024
1 parent b1b6a09 commit f0edee8
Show file tree
Hide file tree
Showing 34 changed files with 1,767 additions and 102 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ build/
compile_commands.json
.cache
.vscode

4 changes: 2 additions & 2 deletions cc-sim.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ RUN apt-get update
# RUN apt-get install -y clang-11
RUN apt-get install -y cmake ninja-build python3 python3-pip
RUN apt-get install -y clang-format vim git
RUN pip install cmake-format compdb conan
RUN pip install cmake-format compdb conan --break-system-packages

WORKDIR /simulator
ENV CONAN_USER_HOME=/simulator/build
COPY requirements.txt .
RUN pip install -r requirements.txt
RUN pip install -r requirements.txt --break-system-packages

# COPY . .
# RUN bash setup_build.sh --build
Expand Down
1 change: 1 addition & 0 deletions src/attacker/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(pvpattacker)
target_sources(cc_simulator PRIVATE attacker.cpp)
40 changes: 37 additions & 3 deletions src/attacker/attacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ void Attacker::update_state() {

if (this->get_hp() == 0) {
this->set_state(State::DEAD);
Logger::log_dead('A', this->_id);
this->log_death();
}
}

void Attacker::log_death() const { Logger::log_dead('A', this->get_id()); }

[[nodiscard]] std::optional<size_t> Attacker::get_nearest_defender_index(
const std::vector<Defender> &defenders) const {
if (defenders.empty()) {
Expand Down Expand Up @@ -100,14 +102,46 @@ void Attacker::move(Position position) {
(int)Map::no_of_rows - 1);
this->_position = Position(new_x, new_y);

Logger::log_move(this->_id, this->_position.get_x(), this->_position.get_y());
this->log_move(this->_position);
}

void Attacker::log_move(Position &p) const {
Logger::log_move(this->get_id(), p.get_x(), p.get_y());
}

void Attacker::attack(Actor &opponent) const {
opponent.take_damage(this->get_attack_power());
Logger::log_shoot('D', this->_id, opponent.get_id(), opponent.get_hp());
this->log_shoot(opponent);
}

void Attacker::log_shoot(Actor &opponent) const {
Logger::log_shoot('D', this->get_id(), opponent.get_id(), opponent.get_hp());
}

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;
}
16 changes: 16 additions & 0 deletions src/attacker/attacker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,21 @@ class Attacker : public Actor {

void update_state() final;

virtual void log_death() const;

virtual void log_move(Position &p) const;

virtual void log_shoot(Actor &opponent) const;

[[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 @@ -71,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;
};
1 change: 1 addition & 0 deletions src/attacker/pvpattacker/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target_sources(cc_simulator PRIVATE pvpattacker.cpp)
70 changes: 70 additions & 0 deletions src/attacker/pvpattacker/pvpattacker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "attacker/pvpattacker/pvpattacker.hpp"
#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,
attr.hp, attr.range,
attr.speed, attr.attack_power,
attr.price, attr.is_aerial,
owner, PvPAttacker::State::SPAWNED};
}

Owner PvPAttacker::get_owner() const { return this->_owner; }

[[nodiscard]] std::optional<size_t> PvPAttacker::get_nearest_defender_index(
const std::vector<PvPAttacker> &defenders) const {
if (defenders.empty()) {
return std::nullopt;
}
if (this->is_aerial_type()) {
auto nearest_attacker = std::min_element(
defenders.begin(), defenders.end(),
[this](const PvPAttacker a, const PvPAttacker b) {
if (a.is_aerial_type() && !b.is_aerial_type() &&
this->is_in_range(a)) {
return true;
}
if (b.is_aerial_type() && !a.is_aerial_type() &&
this->is_in_range(b)) {
return false;
}
return this->get_position().distance_to(a.get_position()) <
this->get_position().distance_to(b.get_position());
});
return std::distance(defenders.begin(), nearest_attacker);
}
auto nearest_attacker = std::min_element(
defenders.begin(), defenders.end(),
[this](const PvPAttacker a, const PvPAttacker b) {
if (a.is_aerial_type() && !b.is_aerial_type()) {
return false;
}
if (b.is_aerial_type() && !a.is_aerial_type()) {
return true;
}
return this->get_position().distance_to(a.get_position()) <
this->get_position().distance_to(b.get_position());
});
if (!nearest_attacker->is_aerial_type()) {
return std::distance(defenders.begin(), nearest_attacker);
}

return std::nullopt;
}

void PvPAttacker::log_death() const {
PvPLogger::log_dead(this->get_id(), this->_owner);
}

void PvPAttacker::log_move(Position &p) const {
PvPLogger::log_move(this->get_id(), p.get_x(), p.get_y(), this->_owner);
}

void PvPAttacker::log_shoot(Actor &opponent) const {
PvPLogger::log_shoot(this->get_id(), opponent.get_id(), opponent.get_hp(),
this->_owner);
}
41 changes: 41 additions & 0 deletions src/attacker/pvpattacker/pvpattacker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "attacker/attacker.hpp"
#include "utils/attributes.hpp"

#include <optional>
#include <unordered_map>
#include <vector>

enum class Owner { PLAYER1, PLAYER2 };

class PvPAttacker : public Attacker {
public:
static inline std::unordered_map<AttackerType, unsigned>
pvp_weight_dictionary;

PvPAttacker(AttackerType type, Position position, unsigned hp, unsigned range,
unsigned speed, unsigned attack_power, unsigned price,
bool is_aerial, Owner owner, State state = State::SPAWNED)
: Attacker{type, position, hp, range, speed,
attack_power, price, is_aerial, state},
_owner(owner) {}

[[nodiscard]] Owner get_owner() const;

[[nodiscard]] static PvPAttacker construct(AttackerType type, Position p,
Owner owner);

// TODO: make this function properly follow the rules of polymorphism
[[nodiscard]] std::optional<size_t>
get_nearest_defender_index(const std::vector<PvPAttacker> &defenders) const;

void log_death() const override final;

void log_move(Position &p) const override final;

void log_shoot(Actor &opponent) const override final;

private:
Owner _owner;
};
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
1 change: 1 addition & 0 deletions src/game/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
target_sources(cc_simulator PRIVATE game.cpp)
target_sources(cc_simulator PRIVATE pvpgame.cpp)
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
Loading

0 comments on commit f0edee8

Please sign in to comment.