+#include "Battle.h"
+
+#include "AttackChoice.h"
+#include "PartyLayout.h"
+#include "TargetSelection.h"
+#include "../common/Stats.h"
+
+#include <cassert>
+#include <stdexcept>
+
+using common::Stats;
+
+
+namespace battle {
+
+Battle::Battle(
+ const PartyLayout *heroesLayout,
+ const PartyLayout *monstersLayout)
+: heroesLayout(heroesLayout)
+, monstersLayout(monstersLayout)
+, activeHero(-1)
+, attackCursor(-1)
+, expReward(0)
+, goldReward(0) {
+ if (!heroesLayout) {
+ throw std::invalid_argument("construct battle without heroes layout");
+ }
+ if (!monstersLayout) {
+ throw std::invalid_argument("construct battle without monsters layout");
+ }
+ heroes.reserve(4);
+ monsters.resize(monstersLayout->NumPositions());
+}
+
+
+void Battle::AddHero(const Hero &h) {
+ if (NumHeroes() == MaxHeroes()) {
+ throw std::overflow_error("max heroes breached");
+ }
+ heroes.push_back(h);
+ heroes.back().GetAttackChoice() = AttackChoice(this);
+}
+
+void Battle::AddMonster(const Monster &m) {
+ for (int i = 0; i < MaxMonsters(); ++i) {
+ if (!MonsterPositionOccupied(i)) {
+ MonsterAt(i) = m;
+ MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+ return;
+ }
+ }
+ throw std::overflow_error("all monster positions occupied");
+}
+
+void Battle::SetCapsule(const Capsule &c) {
+ capsule = c;
+}
+
+
+int Battle::NumHeroes() const {
+ return heroes.size();
+}
+
+int Battle::MaxHeroes() const {
+ return heroesLayout->NumPositions() - 1;
+}
+
+int Battle::NumMonsters() const {
+ return monsters.size();
+}
+
+int Battle::MaxMonsters() const {
+ return monstersLayout->NumPositions();
+}
+
+bool Battle::HasCapsule() const {
+ return capsule.Active();
+}
+
+
+Hero &Battle::HeroAt(int index) {
+ assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
+ return heroes[index];
+}
+
+const Hero &Battle::HeroAt(int index) const {
+ assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
+ return heroes[index];
+}
+
+
+Monster &Battle::MonsterAt(int index) {
+ assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
+ return monsters[index];
+}
+
+const Monster &Battle::MonsterAt(int index) const {
+ assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
+ return monsters[index];
+}
+
+bool Battle::HeroPositionOccupied(int index) const {
+ return index >= 0 && index < NumHeroes();
+}
+
+bool Battle::HeroAlive(int index) const {
+ return HeroPositionOccupied(index) && HeroAt(index).Health() > 0;
+}
+
+
+bool Battle::MonsterPositionOccupied(int index) const {
+ return MonsterAlive(index);
+}
+
+bool Battle::MonsterAlive(int index) const {
+ return index >= 0 && index < NumMonsters() && MonsterAt(index).Health() > 0;
+}
+
+bool Battle::CapsuleAlive() const {
+ return capsule.Active() && capsule.Health() > 0;
+}
+
+
+void Battle::NextHero() {
+ ++activeHero;
+ while (activeHero < NumHeroes() && HeroAt(activeHero).Health() == 0) {
+ ++activeHero;
+ }
+}
+
+void Battle::PreviousHero() {
+ --activeHero;
+ while (activeHero >= 0 && HeroAt(activeHero).Health() == 0) {
+ --activeHero;
+ }
+}
+
+void Battle::SwapHeroes(int lhs, int rhs) {
+ if (lhs < 0 || lhs >= NumHeroes() || rhs < 0 || rhs >= NumHeroes() || lhs == rhs) return;
+ std::swap(HeroAt(lhs), HeroAt(rhs));
+}
+
+bool Battle::HasChosenAttackType() const {
+ return ActiveHero().GetAttackChoice().GetType() != AttackChoice::UNDECIDED;
+}
+
+bool Battle::AttackSelectionDone() const {
+ return activeHero >= NumHeroes();
+}
+
+
+class OrderCompare {
+public:
+ OrderCompare(Battle *battle) : battle(battle) { }
+ bool operator ()(const Battle::Order &lhs, const Battle::Order &rhs) {
+ return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
+ }
+private:
+ Battle *battle;
+};
+
+
+void Battle::CalculateAttackOrder() {
+ attackOrder.reserve(NumMonsters() + NumHeroes() + 1);
+ for (int i(0); i < NumHeroes(); ++i) {
+ attackOrder.push_back(Order(Order::HERO, i));
+ }
+ for (std::vector<Monster>::size_type i(0), end(NumMonsters()); i < end; ++i) {
+ attackOrder.push_back(Order(Order::MONSTER, i));
+ MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+ }
+ if (GetCapsule().Active() && GetCapsule().Health() > 0) {
+ attackOrder.push_back(Order(Order::CAPSULE));
+ }
+ std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
+}
+
+void Battle::NextAttack() {
+ if (Victory() || Defeat()) {
+ attackCursor = attackOrder.size();
+ return;
+ }
+ ++attackCursor;
+ while (attackCursor < int(attackOrder.size())) {
+ if (attackOrder[attackCursor].IsMonster()) {
+ if (MonsterAlive(attackOrder[attackCursor].index)) break;
+ } else if (attackOrder[attackCursor].IsHero()) {
+ if (HeroAlive(attackOrder[attackCursor].index)) break;
+ } else {
+ if (CapsuleAlive()) break;
+ }
+ ++attackCursor;
+ }
+}
+
+bool Battle::AttacksFinished() const {
+ return attackCursor >= int(attackOrder.size())
+ || Victory() || Defeat();
+}
+
+void Battle::CalculateDamage() {
+ if (CurrentAttack().IsMonster()) {
+ DecideMonsterAttack(MonsterAt(CurrentAttack().index));
+ } else if (CurrentAttack().IsCapsule()) {
+ DecideCapsuleAttack();
+ }
+ AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
+ if (ac.GetType() == AttackChoice::DEFEND) return;
+ TargetSelection &ts(ac.Selection());
+
+ const Stats &attackerStats = CurrentAttack().GetStats(*this);
+ CalculateDamage(attackerStats, ts);
+}
+
+AttackChoice &Battle::Order::GetAttackChoice(Battle &b) const {
+ switch (by) {
+ case HERO:
+ return b.HeroAt(index).GetAttackChoice();
+ case CAPSULE:
+ return b.GetCapsule().GetAttackChoice();
+ case MONSTER:
+ return b.MonsterAt(index).GetAttackChoice();
+ default:
+ throw std::runtime_error("invalid case in Battle::Order::GetAttackChoice()");
+ }
+}
+
+Stats &Battle::Order::GetStats(Battle &b) const {
+ switch (by) {
+ case HERO:
+ return b.HeroAt(index).GetStats();
+ case CAPSULE:
+ return b.GetCapsule().GetStats();
+ case MONSTER:
+ return b.MonsterAt(index).GetStats();
+ default:
+ throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
+ }
+}
+
+void Battle::ApplyDamage() {
+ if (attackCursor < 0) return;
+ AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
+ TargetSelection &ts(ac.Selection());
+ if (ts.TargetsMonsters()) {
+ for (int i(0); i < NumMonsters(); ++i) {
+ Monster &monster(MonsterAt(i));
+ if (ts.IsBad(i)) {
+ monster.SubtractHealth(ts.GetAmount(i));
+ if (monster.Health() == 0) {
+ expReward += monster.ExpReward();
+ goldReward += monster.GoldReward();
+ }
+ }
+ }
+ } else {
+ for (int i(0); i < NumHeroes(); ++i) {
+ Hero &hero(HeroAt(i));
+ if (ts.IsBad(i)) {
+ hero.SubtractHealth(ts.GetAmount(i));
+ }
+ }
+ }
+}
+
+const Battle::Order &Battle::CurrentAttack() const {
+ assert(attackCursor >= 0 && attackCursor < int(attackOrder.size()));
+ return attackOrder[attackCursor];
+}
+
+AttackChoice &Battle::CurrentAttackAttackChoice() {
+ return CurrentAttack().GetAttackChoice(*this);
+}
+
+void Battle::ClearAllAttacks() {
+ attackCursor = -1;
+ activeHero = -1;
+ for (int i(0); i < NumHeroes(); ++i) {
+ HeroAt(i).GetAttackChoice() = AttackChoice(this);
+ }
+ for (int i(0); i < NumMonsters(); ++i) {
+ MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+ }
+ GetCapsule().GetAttackChoice() = AttackChoice(this);
+ attackOrder.clear();
+}
+
+
+void Battle::DecideMonsterAttack(Monster &m) {
+ AttackChoice &ac(m.GetAttackChoice());
+ TargetSelection &ts(ac.Selection());
+ ac.Reset();
+ int target(rand() % NumHeroes());
+ while (!HeroAlive(target)) {
+ target = rand() % NumHeroes();
+ }
+ ac.SetType(AttackChoice::SWORD);
+ ts.SelectHeroes();
+ ts.SetSingle();
+ ts.Select(target);
+}
+
+void Battle::DecideCapsuleAttack() {
+ AttackChoice &ac(GetCapsule().GetAttackChoice());
+ TargetSelection &ts(ac.Selection());
+ ac.Reset();
+ int target(rand() % NumMonsters());
+ while (!MonsterAlive(target)) {
+ target = rand() % NumMonsters();
+ }
+ ac.SetType(AttackChoice::SWORD);
+ ts.SelectMonsters();
+ ts.SetSingle();
+ ts.Select(target);
+}
+
+void Battle::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
+ bool hitSome(false);
+ if (ts.TargetsMonsters()) {
+ for (int i(0); i < MaxMonsters(); ++i) {
+ if (ts.IsSelected(i)) {
+ if (MonsterAt(i).Health() > 0) {
+ const Stats &defenderStats(MonsterAt(i).GetStats());
+ Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+ ts.SetBad(i, damage);
+ hitSome = true;
+ } else {
+ ts.Unselect(i);
+ }
+ }
+ }
+ if (hitSome) return;
+ for (int i(0); i < MaxMonsters(); ++i) {
+ if (MonsterAt(i).Health() > 0) {
+ const Stats &defenderStats(MonsterAt(i).GetStats());
+ Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+ ts.SetBad(i, damage);
+ break;
+ }
+ }
+ } else {
+ for (int i(0); i < NumHeroes(); ++i) {
+ if (ts.IsSelected(i)) {
+ if (HeroAt(i).Health() > 0) {
+ const Stats &defenderStats(HeroAt(i).GetStats());
+ Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+ ts.SetBad(i, damage);
+ hitSome = true;
+ } else {
+ ts.Unselect(i);
+ }
+ }
+ }
+ if (hitSome) return;
+ for (int i(0); i < NumHeroes(); ++i) {
+ if (HeroAt(i).Health() > 0) {
+ const Stats &defenderStats(HeroAt(i).GetStats());
+ Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+ ts.SetBad(i, damage);
+ break;
+ }
+ }
+ }
+}
+
+Uint16 Battle::CalculateDamage(const Stats &attacker, const Stats &defender) const {
+ return attacker.Attack() / 2 - defender.Defense() / 4;
+}
+
+
+bool Battle::Victory() const {
+ for (int i(0); i < NumMonsters(); ++i) {
+ if (MonsterAlive(i)) return false;
+ }
+ return true;
+}
+
+bool Battle::Defeat() const {
+ for (int i(0); i < NumHeroes(); ++i) {
+ if (HeroAlive(i)) return false;
+ }
+ return true;
+}
+
+}