private double EstimateUnitDamage(Unit attacker, Unit defender) { Combat.TurnPhase phase = Combat.TurnPhase.MAGIC; double epsilon = 0.05; while (phase < Combat.TurnPhase.CLEANUP) { UnitStack attackerStack = new UnitStack(new Unit(attacker.GetUnitType(), 1), null); UnitStack defenderStack = new UnitStack(new Unit(defender.GetUnitType(), 1), null); double attackerDamage = EstimateStackAttacksDamage(attackerStack, defenderStack, phase); attackerStack = new UnitStack(new Unit(attacker.GetUnitType(), 1), null); defenderStack = new UnitStack(new Unit(defender.GetUnitType(), 1), null); double defenderDamage = EstimateStackAttacksDamage(defenderStack, attackerStack, phase); phase++; // read as "attacker deals significantly less damage than defender // during this phase" if (attackerDamage + epsilon < defenderDamage) { return(0); } // "defender deals significantly less damage than attacker // during this phase" if (attackerDamage > defenderDamage + epsilon) { return(attackerDamage); } } return(0); }
private double GetEstimatedDamage(UnitStack attacker, UnitStack defender, Combat.TurnPhase phase) { int intPhase = (int)phase; if (attacker.GetAffectingSpells().Count == 0 && defender.GetAffectingSpells().Count == 0) { int attackerTypeId = attacker.GetUnitType().GetId(); if (_expectedDamage.ContainsKey(attackerTypeId)) { Dictionary <int, Dictionary <int, double> > attackerRecords = _expectedDamage[attackerTypeId]; int defenderTypeId = defender.GetUnitType().GetId(); if (attackerRecords.ContainsKey(defenderTypeId)) { Dictionary <int, double> defenderRecords = attackerRecords[defenderTypeId]; if (defenderRecords.ContainsKey(intPhase)) { return(defenderRecords[intPhase]); } } } } // "not found" return(-1); }
/// <summary> /// Get unit type's attacks for a particular phase /// </summary> /// <param name="phase">Combat turn's phase</param> /// <returns>List of unit type's attacks for the phase</returns> public List <Attack> GetAttacksForPhase(Combat.TurnPhase phase) { List <Attack> result = new List <Attack>(); AttackData.Quality quality = AttackData.Quality.NONE; switch (phase) { case Combat.TurnPhase.DIVINE: quality = AttackData.Quality.DIVINE; break; case Combat.TurnPhase.MAGIC: quality = AttackData.Quality.MAGIC; break; case Combat.TurnPhase.RANGED: quality = AttackData.Quality.RANGED; break; case Combat.TurnPhase.SKIRMISH: quality = AttackData.Quality.SKIRMISH; break; case Combat.TurnPhase.CHARGE: quality = AttackData.Quality.CHARGE; break; case Combat.TurnPhase.MELEE: quality = AttackData.Quality.MELEE; break; } if (quality != AttackData.Quality.NONE) { for (int i = 0; i < _attacks.Count; i++) { if (_attacks[i].HasQuality(quality)) { result.Add(_attacks[i]); } } } return(result); }
/// <summary> /// Estimate results of a unit stack attacking another during a specified turn phase /// </summary> /// <param name="attacker">Attacking unit stack</param> /// <param name="defender">Defending unit stack</param> /// <param name="phase">Turn phase</param> /// <returns>Mathematical expectation of the attack damage value</returns> public double EstimateStackAttacksDamage(UnitStack attacker, UnitStack defender, Combat.TurnPhase phase) { double result = GetEstimatedDamage(attacker, defender, phase); if (result >= 0) { return(result); } result = 0; int maxHP = defender.GetUnitType().GetHitPoints(); List <Attack> attacks = attacker.GetUnitType().GetAttacksForPhase(phase); for (int i = 0; i < attacks.Count; i++) { int numberOfAttacks = attacks[i].GetNumberOfAttacks(); for (int attackerPlusDie = 0; attackerPlusDie < _diceSides; attackerPlusDie++) { for (int attackerMinusDie = 0; attackerMinusDie < _diceSides; attackerMinusDie++) { AttackRollResult attackRollResult = CreateAnAttackRollResult(attacker, attacks[i], attackerPlusDie + 1, attackerMinusDie + 1); int defensiveSkill = CalculateDefensiveSkill(attackRollResult.Attack, defender); int shield = CalculateShieldValue(attackRollResult.Attack, defender); int armor = CalculateArmorValue(attackRollResult.Attack, defender, attackRollResult.IsCritical); for (int defenderPlusDie = 0; defenderPlusDie < _diceSides; defenderPlusDie++) { for (int defenderMinusDie = 0; defenderMinusDie < _diceSides; defenderMinusDie++) { // both defenderPlusDie and defenderMinusDie would add +1 to them, // so the +1s would cancel each other int defenseRoll = defenderPlusDie - defenderMinusDie; int totalDefense = defenseRoll + defensiveSkill + shield; result += numberOfAttacks * EstimateWeaponDamage(attackRollResult, defender, totalDefense, armor, maxHP); } } } } } result *= _damageScale; SetEstimatedDamage(attacker, defender, phase, result); return(result); }
private void SetEstimatedDamage(UnitStack attacker, UnitStack defender, Combat.TurnPhase phase, double damage) { if (attacker.GetAffectingSpells().Count == 0 && defender.GetAffectingSpells().Count == 0) { int attackerTypeId = attacker.GetUnitType().GetId(); if (!_expectedDamage.ContainsKey(attackerTypeId)) { _expectedDamage[attackerTypeId] = new Dictionary <int, Dictionary <int, double> >(); } int defenderTypeId = defender.GetUnitType().GetId(); if (!_expectedDamage[attackerTypeId].ContainsKey(defenderTypeId)) { _expectedDamage[attackerTypeId][defenderTypeId] = new Dictionary <int, double>(); } _expectedDamage[attackerTypeId][defenderTypeId][(int)phase] = damage; } FileLogger.Trace("ESTIMATE", "Estimated damage of " + attacker.GetUnitType().GetName() + " vs " + defender.GetUnitType().GetName() + " during " + phase + " phase is " + damage); }
/// <summary> /// Select the unit stack which is going to be cheapest to re-train, taking into account odds of successful defense /// </summary> /// <param name="stacks">Available unit stacks</param> /// <param name="attacker">Attacking enemy unit stack</param> /// <param name="phase">Turn phase of the combat</param> /// <returns>Unit stack selected</returns> public UnitStack SelectMinReplacementCostUnit(List <UnitStack> stacks, UnitStack attacker, Combat.TurnPhase phase) { UnitStack result = null; double replacementCost = double.MaxValue; double altervative; for (int i = 0; i < stacks.Count; i++) { if (stacks[i].GetTotalQty() > 0) { altervative = CombatHelper.Instance.EstimateStackAttacksDamage(attacker, stacks[i], phase) * stacks[i].GetUnitType().GetTrainingCost() / stacks[i].GetUnitType().GetHitPoints(); if (altervative < replacementCost) { replacementCost = altervative; result = stacks[i]; } } } if (result != null) { FileLogger.Trace("TAI", "Medium Level AI selected " + result.GetUnitType().GetName() + " as a target, expected replacement cost is " + replacementCost); } return(result); }
/// <summary> /// Select the unit stack which is going to soak enemy's attacks /// </summary> /// <param name="stacks">Available unit stacks</param> /// <param name="attacker">Attacking enemy unit stack</param> /// <param name="phase">Turn phase of the combat</param> /// <returns>Unit stack selected</returns> public UnitStack SelectDefendingUnitStack(List <UnitStack> stacks, UnitStack attacker, Combat.TurnPhase phase) { switch (_level) { case 1: // general-level AI takes into account successful defense odds // and selects the stack that has the lowest exectated replacement cost return(SelectMinReplacementCostUnit(stacks, attacker, phase)); default: // captain-level AI selects the cheapest unit stack return(SelectCheapestUnit(stacks)); } }