/// <summary> /// Run combat simulations /// </summary> /// <param name="numberOfRuns">The number of simulations to run</param> /// <param name="simulationLevel">Sumulation complexity level</param> /// <returns>Number of simulations that actually ran</returns> public int RunCombatSimulations(int numberOfRuns, int simulationLevel) { FileLogger.Trace("AI", "Running combat simulations for " + _province.GetName()); _state = State.NOT_REVIEWED; List <Unit> units = _province.GetUnits(); // if nobody's here to defend the province attacked, then // attackers always win and there are no losses if (units.Count == 0 && simulationLevel == 0) { _winRatio = 1; _gain = _provinceValue; return(0); } // looks like there will be a fight after all // set up unit stacks for defenders List <UnitStack> defenders = new List <UnitStack>(); // not using _defendersTrainingCost directly because of a hacky way // to simulate enemy retaliation implemented below // remainder: _defendersTrainingCost is zero for non-playable functions // because, frankly, who cares about them? int defendersTrainingCost = _defendersTrainingCost; for (int j = 0; j < units.Count; j++) { defenders.Add(new UnitStack(units[j], _province)); } // now do the same for the attackers List <UnitStack> attackers = new List <UnitStack>(); List <Province> attackingProvinces = new List <Province>(); int attackersTrainingCost = 0; for (int i = 0; i < _orders.Count; i++) { units = _orders[i].GetUnits(); for (int j = 0; j < units.Count; j++) { attackers.Add(new UnitStack(units[j], _orders[i].GetOrigin())); if (!attackingProvinces.Contains(_orders[i].GetOrigin())) { attackingProvinces.Add(_orders[i].GetOrigin()); } attackersTrainingCost += units[j].GetTrainingCost(); } } // that was the easy part // now let's consider possible enemy's retalliation (in a hacky way) // btw, the easy AI doesn't do this if (simulationLevel > 0) { Faction attackingFaction = _orders[0].GetOrigin().GetOwnersFaction(); List <Province> neighbors = _province.GetNeighbors(); for (int i = 0; i < neighbors.Count; i++) { Faction opponent = neighbors[i].GetOwnersFaction(); if (opponent.IsPlayable() && opponent != attackingFaction) { units = neighbors[i].GetUnits(); for (int j = 0; j < units.Count; j++) { defenders.Add(new UnitStack(units[j], neighbors[i])); defendersTrainingCost += units[j].GetTrainingCost(); } } } } // initialize cumulative statistics int numberOfWins = 0; // what's the replacement cost for the losses suffered? double cumulativeLoss = 0; // if fighting a playable faction, same for the enemy // because we want to inflict losses on them double cumulativeEnemyLoss = 0; for (int k = 0; k < numberOfRuns; k++) { Combat combat = new Combat(_province, UnitStack.Clone(attackers), UnitStack.Clone(defenders), false); combat.ResolveCombat(); _combatSimulations.Add(combat); List <UnitStack> remainingAttackers = combat.GetAttackers(); if (remainingAttackers.Count > 0) { numberOfWins++; // count the losses, ours... int remainingAttackersTrainingCost = 0; for (int j = 0; j < remainingAttackers.Count; j++) { remainingAttackersTrainingCost += remainingAttackers[j].GetBaseUnit().GetTrainingCost(); } cumulativeLoss += attackersTrainingCost - remainingAttackersTrainingCost; // ... and our enemies' cumulativeEnemyLoss += defendersTrainingCost; } else { // we lost the battle and all of our units! cumulativeLoss += attackersTrainingCost; // but maybe the enemy lost some too? List <UnitStack> remainingDefenders = combat.GetDefenders(); int remainingDefendersTrainingCost = 0; for (int j = 0; j < remainingDefenders.Count; j++) { Faction opponent = remainingDefenders[j].GetProvinceToRetreat().GetOwnersFaction(); if (opponent.IsPlayable()) { remainingDefendersTrainingCost += remainingDefenders[j].GetBaseUnit().GetTrainingCost(); } } cumulativeEnemyLoss += defendersTrainingCost - remainingDefendersTrainingCost; } } _winRatio = (double)numberOfWins / numberOfRuns; _gain = (double)_provinceValue * numberOfWins / numberOfRuns; _loss = cumulativeLoss / numberOfRuns; _enemyLoss = cumulativeEnemyLoss / numberOfRuns; string attackOriginDescription = GameUtils.DescribeProvinceList(attackingProvinces); FileLogger.Trace("AI", _province.GetName() + " can be conquered " + numberOfWins + " out of " + numberOfRuns + " times if attacked from [" + attackOriginDescription + "] (value: " + _provinceValue + ", loss: " + _loss + ", enemy loss: " + _enemyLoss + ", gain: " + _gain + ", score: " + GetScore() + ")"); return(numberOfRuns); }