/// <summary> /// Copy constructor /// </summary> /// <param name="toClone">The original plan to be copied</param> public ProvinceAttackPlan(ProvinceAttackPlan toClone) { _province = toClone._province; _provinceValue = toClone._provinceValue; _orders = new List <ArmyMovementOrder>(); for (int i = 0; i < toClone._orders.Count; i++) { _orders.Add(toClone._orders[i]); } _combatSimulations = new List <Combat>(); _state = State.INCOMPLETE; _areHeroesInvolved = toClone._areHeroesInvolved; _defendersTrainingCost = toClone._defendersTrainingCost; }
/// <summary> /// Prepare a list of army movement orders for the current turn /// </summary> /// <returns>List of army movement orders</returns> public List <ArmyMovementOrder> SelectMovements() { // this is our guy to be returned List <ArmyMovementOrder> result = new List <ArmyMovementOrder>(); // this is how many combat simulations we have run for this turn int simulationsRan = 0; // shouldn't exceed this number int maxCombatSimulations = 200; // movement orders will depend on the accepted attack plans List <ProvinceAttackPlan> plans = new List <ProvinceAttackPlan>(); // we need to consider all garrisons across our vast empire List <Province> provinces = new List <Province>(_faction.GetProvinces().Values); // we want to consider the most powerful armies first provinces.Sort((x, y) => y.GetUnits().Count.CompareTo(x.GetUnits().Count)); // no need to pay attention to provinces without garrisons provinces.RemoveAll(province => province.GetUnits().Count == 0); for (int i = 0; i < provinces.Count; i++) { if (provinces[i].IsInnerProvince()) { // no enemy neighbors => the move is going to be non-combat List <ArmyMovementOrder> orders = SelectNonCombatMoves(provinces[i]); if (orders.Count > 0) { result.AddRange(orders); } } else { // plans involving garrison of this particular province List <ProvinceAttackPlan> currentPlans = new List <ProvinceAttackPlan>(); List <Unit> attackers = provinces[i].GetUnits(); List <Province> targets = provinces[i].GetTargetableNeighbors(); // consider adding these attackers to an existing invasion plan if (_faction.GetAILevel() > 0) { for (int j = 0; j < plans.Count; j++) { if (!plans[j].IsIncomplete() && plans[j].GetLoss() > 0) { Province province = plans[j].GetTargetProvince(); if (targets.Contains(province)) { ProvinceAttackPlan plan = new ProvinceAttackPlan(plans[j]); plan.AddMovementOrder(new ArmyMovementOrder(provinces[i], province, attackers)); currentPlans.Add(plan); } } } } // create new incomplete attack plans for (int j = 0; j < targets.Count; j++) { ProvinceAttackPlan plan = new ProvinceAttackPlan(targets[j], GameUtils.CalculateProvinceValue(targets[j], _faction)); plan.AddMovementOrder(new ArmyMovementOrder(provinces[i], targets[j], attackers)); currentPlans.Add(plan); } currentPlans.Sort((x, y) => y.GetProvinceValue().CompareTo(x.GetProvinceValue())); // evaluate plans that seem promising double maxScore = -1; for (int j = 0; j < currentPlans.Count; j++) { ProvinceAttackPlan currentPlan = currentPlans[j]; if (simulationsRan < maxCombatSimulations && currentPlan.GetProvinceValue() + currentPlan.GetDefendersTrainingCost() > maxScore) { int numberOfRuns = currentPlan.AreHeroesInvolved() ? 2 * COMBATS_TO_RUN : COMBATS_TO_RUN; int realRuns = currentPlan.RunCombatSimulations(numberOfRuns, _faction.GetAILevel()); simulationsRan += realRuns; maxScore = Math.Max(maxScore, currentPlan.GetScore()); } } plans.AddRange(currentPlans); } } plans.RemoveAll(plan => plan.IsIncomplete()); FileLogger.Trace("AI", "Reviewing attack plans"); List <ProvinceAttackPlan> savedPlans = new List <ProvinceAttackPlan>(); bool havePlansToReview = true; int plansReviewed = 0; int maxPlansToReview = _faction.GetAILevel() == 0 ? 1 : 10; while (havePlansToReview && plansReviewed < maxPlansToReview) { // select the best plan to implement double maxScore = -1; int maxScoreIndex = -1; for (int i = 0; i < plans.Count; i++) { double planScore = plans[i].GetScore(); if (planScore > maxScore) { maxScore = planScore; maxScoreIndex = i; } } if (maxScoreIndex >= 0) { ProvinceAttackPlan currentPlan = plans[maxScoreIndex]; List <ArmyMovementOrder> movements = currentPlan.GetMovementOrders(); result.AddRange(movements); string attackOriginDescription = movements[0].GetOrigin().GetName(); for (int j = 1; j < movements.Count; j++) { attackOriginDescription += ", " + movements[j].GetOrigin().GetName(); } FileLogger.Trace("AI", _faction.GetName() + ": will attack province " + movements[0].GetDestination().GetName() + " from [" + attackOriginDescription + "]"); currentPlan.MarkAccepted(); savedPlans.Add(currentPlan); plans.RemoveAt(maxScoreIndex); // iterating plans starting with the highest index for (int i = plans.Count - 1; i > -1; i--) { if (!plans[i].IsCompatibleWith(movements)) { plans[i].MarkRejected(); savedPlans.Add(plans[i]); plans.RemoveAt(i); } } } else { if (result.Count == 0) { FileLogger.Trace("AI", _faction.GetName() + ": not attacking anyone"); } havePlansToReview = false; } plansReviewed++; FileLogger.Trace("AI", "Selecting attack plans: pass " + plansReviewed.ToString() + " is done"); } // training units may depend on the attack plans SelectTraining(savedPlans); return(result); }