public override string ToString() { if (Type == MultiAttackPlanType.MainStack) { return(Bot.TerrString(To)); } else { return(Bot.TerrString(To) + " " + Type); } }
/// <summary> /// Runs the commander away from opponents /// </summary> private static void DoCommander(BotMain bot, TerritoryStanding cmdrTerritory, Commander cmdr) { var directive = CommanderDirective(bot, cmdrTerritory.ID); if (directive.HasValue) { AILog.Log("SpecialUnits", "Directive directs us to move the commander from " + bot.TerrString(cmdrTerritory.ID) + " to " + bot.TerrString(directive.Value)); if (directive.Value != cmdrTerritory.ID) { bot.Orders.AddAttack(cmdrTerritory.ID, directive.Value, AttackTransferEnum.AttackTransfer, cmdrTerritory.NumArmies.NumArmies, false, commanders: true); } bot.AvoidTerritories.Add(cmdrTerritory.ID); //add this so we don't deploy there, we want the commander to stay alone return; } var powerDiff = bot.Map.Territories[cmdrTerritory.ID].ConnectedTo.Keys .Select(o => bot.Standing.Territories[o]) .Where(o => bot.IsOpponent(o.OwnerPlayerID) && o.NumArmies.Fogged == false) .Sum(o => o.NumArmies.AttackPower) - cmdrTerritory.NumArmies.DefensePower; var toDeploy = Math.Max(0, powerDiff); if (powerDiff > 0) { if (bot.UseRandomness) { toDeploy = SharedUtility.Round(toDeploy * RandomUtility.BellRandom(0.5, 1.5)); } if (toDeploy > bot.MakeOrders.IncomeTracker.RemainingUndeployed) { toDeploy = bot.MakeOrders.IncomeTracker.RemainingUndeployed; } if (toDeploy > 0 && bot.Orders.TryDeploy(cmdrTerritory.ID, toDeploy)) { AILog.Log("SpecialUnits", "Deployed " + toDeploy + " to defend commander"); } } //Consider this territory and all adjacent territories. Which is the furthest from any enemy? var terrDistances = bot.Map.Territories[cmdrTerritory.ID].ConnectedTo.Keys.ConcatOne(cmdrTerritory.ID) .Where(o => bot.Standing.Territories[o].OwnerPlayerID == bot.PlayerID || bot.Standing.Territories[o].NumArmies.DefensePower <= 4) //don't go somewhere that's defended heavily .ToDictionary(o => o, o => bot.DistanceFromEnemy(o)); AILog.Log("SpecialUnits", "Commander run options: " + terrDistances.Select(o => bot.TerrString(o.Key) + " dist=" + o.Value).JoinStrings(", ")); var sorted = terrDistances.OrderByDescending(o => o.Value).ToList(); sorted.RemoveWhere(o => o.Value < sorted[0].Value); var runTo = bot.UseRandomness ? sorted.Random().Key : sorted[0].Key; if (runTo == cmdrTerritory.ID) { return; //already there } AILog.Log("SpecialUnits", "Moving commander from " + bot.TerrString(cmdrTerritory.ID) + " to " + bot.TerrString(runTo)); bot.Orders.AddAttack(cmdrTerritory.ID, runTo, AttackTransferEnum.AttackTransfer, cmdrTerritory.NumArmies.NumArmies + toDeploy, false, commanders: true); }
private static float GetExpansionWeight(BotMain bot, TerritoryIDType terrID) { var td = bot.Map.Territories[terrID]; var bonusPaths = td.PartOfBonuses .Where(o => bot.BonusValue(o) > 0) .Select(o => BonusPath.TryCreate(bot, o, ts => ts.ID == terrID)) .Where(o => o != null) .ToDictionary(o => o.BonusID, o => o); var turnsToTake = bonusPaths.Keys.ToDictionary(o => o, o => TurnsToTake(bot, td.ID, o, bonusPaths[o])); foreach (var cannotTake in turnsToTake.Where(o => o.Value == null).ToList()) { turnsToTake.Remove(cannotTake.Key); bonusPaths.Remove(cannotTake.Key); } var bonusWeights = bonusPaths.Keys.ToDictionary(o => o, o => ExpansionHelper.WeighBonus(bot, o, ts => ts.ID == terrID, turnsToTake[o].NumTurns)); var weight = 0.0f; weight += ExpansionHelper.WeighMultipleBonuses(td.PartOfBonuses.Where(o => bonusWeights.ContainsKey(o)).ToDictionary(o => o, o => bonusWeights[o])); AILog.Log("PickTerritories", "Expansion weight for terr " + bot.TerrString(terrID) + " is " + weight + ". " + td.PartOfBonuses.Select(b => "Bonus " + bot.BonusString(b) + " Weight=" + (bonusWeights.ContainsKey(b) ? bonusWeights[b] : 0) + " TurnsToTake=" + (turnsToTake.ContainsKey(b) ? turnsToTake[b].ToString() : "") + " Path=" + (bonusPaths.ContainsKey(b) ? bonusPaths[b].ToString() : "")).JoinStrings(", ")); return(weight); }
public static List <TerritoryIDType> Go(BotMain bot, HashSet <TerritoryIDType> allAvailable, int maxPicks) { var expansionWeights = allAvailable.ToDictionary(o => o, o => GetExpansionWeight(bot, o)); var ordered = expansionWeights.OrderByDescending(o => o.Value).ToList(); AILog.Log("PickTerritories", "Came up with " + expansionWeights.Count + " expansion weights: "); foreach (var e in ordered.Take(30)) { AILog.Log("PickTerritories", " - " + bot.TerrString(e.Key) + ": " + e.Value); } if (!bot.UseRandomness) { return(ordered.Select(o => o.Key).Take(maxPicks).ToList()); } //Normalize weights var top = ordered.Take(maxPicks * 2); var sub = top.Min(o => o.Value) - 1; var normalized = top.ToDictionary(o => o.Key, o => o.Value - sub); var picks = new List <TerritoryIDType>(); while (picks.Count < maxPicks && normalized.Count > 0) { var pick = RandomUtility.WeightedRandom(normalized.Keys, o => normalized[o]); picks.Add(pick); normalized.Remove(pick); } return(picks); }
private static bool PlayBombCard(BotMain bot, CardInstance card) { var allBombableEnemyTerritories = bot.Standing.Territories.Values .Where(o => o.OwnerPlayerID == bot.PlayerID) .SelectMany(o => bot.Map.Territories[o.ID].ConnectedTo.Keys) .Distinct() .Select(o => bot.Standing.Territories[o]) .Where(o => bot.IsOpponent(o.OwnerPlayerID) && o.NumArmies.Fogged == false) .ToList(); var minArmies = !bot.UseRandomness ? bot.BaseIncome.Total * 2 : SharedUtility.Round(bot.BaseIncome.Total * RandomUtility.BellRandom(1, 3)); var weights = allBombableEnemyTerritories.Where(o => o.NumArmies.NumArmies > minArmies).ToDictionary(o => o.ID, o => o.NumArmies.NumArmies - minArmies); if (weights.Count == 0) { return(false); } var bomb = bot.UseRandomness ? RandomUtility.WeightedRandom(weights.Keys, o => weights[o]) : weights.OrderByDescending(o => o.Value).First().Key; AILog.Log("PlayCards", "Bombing " + bot.TerrString(bomb)); bot.Orders.AddOrder(GameOrderPlayCardBomb.Create(card.ID, bot.PlayerID, bomb)); return(true); }
private void BuildCities() { if (Bot.Settings.CommerceGame == false || Bot.Settings.CommerceCityBaseCost.HasValue == false) { return; //can't build cities } var totalGold = Bot.Standing.NumResources(Bot.PlayerID, ResourceType.Gold); var spentGold = Bot.Settings.CostOfBuyingArmies(IncomeTracker.TotalArmiesDeployed); var maxPercent = !Bot.UseRandomness ? 0.5 : RandomUtility.BellRandom(0, 0.9); int goldLeftToSpendOnCities = Math.Min(totalGold - spentGold, SharedUtility.Round(totalGold * maxPercent)); //limit our cities to about half our gold to ensure we don't over-build AILog.Log("BuildCities", "totalGold=" + totalGold + " spentGold=" + spentGold + " goldToSpendOnCities=" + goldLeftToSpendOnCities + " maxPercent=" + maxPercent); if (goldLeftToSpendOnCities < Bot.Settings.CommerceCityBaseCost.Value) { return; //can't even afford one city } //randomize the safe range. This makes it int acceptableRangeFromOpponent = !Bot.UseRandomness ? 4 : SharedUtility.Round(RandomUtility.BellRandom(2, 6)); var eligibleTerritories = Bot.TerritoriesNotNearOpponent(acceptableRangeFromOpponent); eligibleTerritories.RemoveAll(Bot.AvoidTerritories); var numCitiesOn = eligibleTerritories.ToDictionary(o => o, o => Bot.Standing.Territories[o].NumStructures(StructureType.City)); //while we might be able to afford a city... while (goldLeftToSpendOnCities > Bot.Settings.CommerceCityBaseCost.Value) { var fewestCities = numCitiesOn.Values.Min(); var cheapestCityCost = fewestCities + Bot.Settings.CommerceCityBaseCost.Value; if (goldLeftToSpendOnCities < cheapestCityCost) { return; //can't afford any more, we must have one on every spot which increases the cost. } //We can afford it, let's build a city var buildCityOn = Bot.UseRandomness ? numCitiesOn.Where(o => o.Value == fewestCities).Random().Key : numCitiesOn.Where(o => o.Value == fewestCities).OrderBy(o => (int)o.Key).First().Key; goldLeftToSpendOnCities -= cheapestCityCost; //remember that we spent it for the loop above. AILog.Log("BuildCities", "Building a city on " + Bot.TerrString(buildCityOn) + " for " + cheapestCityCost + " unspentGold=" + goldLeftToSpendOnCities); Orders.PurchaseOrder.BuildCities.AddTo(buildCityOn, 1); numCitiesOn.AddTo(buildCityOn, 1); //Since we spent gold, adjust the remaining deployable armies so we don't overdeploy later this.UpdateEffectiveIncome(); } }
private void DoDefense(int armies, List <PossibleAttack> orderedDefenses) { AILog.Log("Defense", orderedDefenses.Count + " defend ops:"); foreach (var defend in orderedDefenses.Take(10)) { AILog.Log("Defense", " - " + defend); } if (orderedDefenses.Count == 0) { AILog.Log("Defense", "No defenses"); return; } #if IOS //Work around the "attacking to JIT compile method" for below WeightedRandom call if (RandomUtility.RandomNumber(2) == -1) { new List <PossibleAttack>().Select(o => o.DefenseImportance).ToList(); } #endif var allDefenses = new Dictionary <TerritoryIDType, int>(); if (Bot.UseRandomness) { for (int i = 0; i < armies; i++) { var defend = orderedDefenses.WeightedRandom(o => o.DefenseImportance); if (Bot.Orders.TryDeploy(defend.From, 1)) { allDefenses.AddTo(defend.From, 1); } } } else { var avg = orderedDefenses.Select(o => o.DefenseImportance).Average(); var betterThanAvg = orderedDefenses.Where(o => o.DefenseImportance >= avg).ToList(); var armiesLeft = armies; while (armiesLeft > 0) { bool deployedAny = false; foreach (var d in betterThanAvg) { if (Bot.Orders.TryDeploy(d.From, 1)) { deployedAny = true; allDefenses.AddTo(d.From, 1); armiesLeft--; if (armiesLeft <= 0) { break; } } } if (!deployedAny) { break; //We couldn't deploy any, possibly due to local deployments. } } } AILog.Log("Defense", "Defended " + allDefenses.Count + " territories: " + allDefenses.OrderByDescending(o => o.Value).Select(o => Bot.TerrString(o.Key) + " with " + o.Value).JoinStrings(", ")); }
private static void DoBoss(BotMain bot, TerritoryStanding terr, SpecialUnit su) { AILog.Log("SpecialUnits", "Considering boss " + su.ID + " on " + bot.TerrString(terr.ID)); var routes = bot.Directives.Where(o => o.StartsWith("BossRoute ")).Select(o => new BossRoute(o, bot)).ToList(); var routeNexts = routes.Select(o => new { Route = o, Terr = o.NextTerr(terr.ID) }).Where(o => o.Terr.HasValue).ToList(); if (routeNexts.Count > 0) { var routeNext = routeNexts.WeightedRandom(o => o.Route.Weight); AILog.Log("SpecialUnits", routeNexts.Count + " matching routes: " + routeNexts.Select(o => o.Route.Name).JoinStrings(", ") + ", selected " + routeNext.Route); if (RandomUtility.RandomPercentage() > routeNext.Route.Chance) { AILog.Log("SpecialUnits", "Skipping boss route to " + routeNext.Terr.Value + " due to failed random chance. "); } else { AILog.Log("SpecialUnits", "Moving boss along route to " + bot.TerrString(routeNext.Terr.Value) + ". "); bot.Orders.AddAttack(terr.ID, routeNext.Terr.Value, AttackTransferEnum.AttackTransfer, 0, true, bosses: true); bot.AvoidTerritories.Add(routeNext.Terr.Value); return; } } else if (routes.Count > 0) { //Move towards the nearest route territory. If there's a tie, take the one that's furthest along in that route var terrRoutes = routes.SelectMany(r => r.Route.Select((t, i) => new { Route = r, Terr = t, Index = i })) .GroupBy(o => o.Terr) .Select(o => o.MaxSelectorOrDefault(r => r.Index)) .ToDictionary(o => o.Terr, o => o); var visited = new HashSet <TerritoryIDType>(); visited.Add(terr.ID); while (true) { var visit = visited.SelectMany(o => bot.Map.Territories[o].ConnectedTo.Keys).ToHashSet(false); if (visit.Count == 0) { throw new Exception("Never found route territory"); } var visitOnRoute = visit.Where(o => terrRoutes.ContainsKey(o)).ToList(); if (visitOnRoute.Count > 0) { var final = visitOnRoute.Select(o => terrRoutes[o]).MaxSelectorOrDefault(o => o.Index); if (RandomUtility.RandomPercentage() > final.Route.Chance) { AILog.Log("SpecialUnits", "Skipping moving boss to route due to failed random check: " + final.Route); break; } else { var move = FindPath.TryFindShortestPath(bot, terr.ID, t => t == final.Terr); AILog.Log("SpecialUnits", "Moving boss to get back to route. Moving to " + bot.TerrString(move[0]) + " to get to " + bot.TerrString(final.Terr) + " index=" + final.Index + " " + final.Route); bot.Orders.AddAttack(terr.ID, move[0], AttackTransferEnum.AttackTransfer, 0, true, bosses: true); bot.AvoidTerritories.Add(final.Terr); return; } } visited.AddRange(visit); } } var attackCandidates = bot.Map.Territories[terr.ID].ConnectedTo.Keys.Select(o => bot.Standing.Territories[o]) .Where(o => !bot.IsTeammateOrUs(o.OwnerPlayerID) && o.NumArmies.DefensePower < 300 && !bot.AvoidTerritories.Contains(o.ID)) .ToList(); if (attackCandidates.Count > 0) { var ranks = attackCandidates.ToDictionary(o => o.ID, ts => { if (bot.IsOpponent(ts.OwnerPlayerID)) { return(bot.Players[ts.OwnerPlayerID].IsAI ? 3 : 2); //prefer human player } else if (ts.OwnerPlayerID == TerritoryStanding.NeutralPlayerID) { return(1); } else { throw new Exception("Unexpected owner " + ts.OwnerPlayerID); } }); var max = ranks.Values.Max(); var to = ranks.Where(o => o.Value == max).Random().Key; AILog.Log("SpecialUnits", "Normal boss move to " + bot.TerrString(to)); bot.Orders.AddAttack(terr.ID, to, AttackTransferEnum.AttackTransfer, 0, false, bosses: true); bot.AvoidTerritories.Add(to); } else { //Surrounded by ourself or teammates. Move towards enemy var move = bot.MoveTowardsNearestBorderNonNeutralThenNeutral(terr.ID); if (move.HasValue) { AILog.Log("SpecialUnits", "Landlocked boss move to " + bot.TerrString(move.Value)); bot.Orders.AddAttack(terr.ID, move.Value, AttackTransferEnum.AttackTransfer, 0, false, bosses: true); } } }
public static void Go(BotMain bot) { var terrs = bot.Standing.Territories.Values.Where(o => bot.IsTeammateOrUs(o.OwnerPlayerID) && o.NumArmies.NumArmies == bot.Settings.OneArmyMustStandGuardOneOrZero && o.NumArmies.SpecialUnits.Length == 0).Select(o => o.ID).ToHashSet(true); foreach (var bonus in bot.Map.Bonuses.Values) { if (bonus.Territories.All(o => terrs.Contains(o)) && bonus.ControlsBonus(bot.Standing).HasValue == false) { //bot bonus is entirely controlled by our team with 1s, but not by a single player. The player with the most territories should take it. var owners = bonus.Territories.GroupBy(o => bot.Standing.Territories[o].OwnerPlayerID).ToList(); owners.Sort((f, s) => SharedUtility.CompareInts(s.Count(), f.Count())); Assert.Fatal(owners.Count >= 2); var attacks = bonus.Territories .Where(o => bot.Standing.Territories[o].OwnerPlayerID != bot.PlayerID) //Territories in the bonus by our teammate .Where(o => bot.Map.Territories[o].ConnectedTo.Keys.Any(c => bot.Standing.Territories[c].OwnerPlayerID == bot.PlayerID)) //Where we control an adjacent .Select(o => new PossibleAttack(bot, bot.Map.Territories[o].ConnectedTo.Keys.First(c => bot.Standing.Territories[c].OwnerPlayerID == bot.PlayerID), o)); if (owners[0].Count() == owners[1].Count()) { //The top two players have the same number of terrs. 50% chance we should try taking one. if (attacks.Any() && RandomUtility.RandomNumber(2) == 0) { var doAttack1 = bot.UseRandomness ? attacks.Random() : attacks.First(); var numArmies = bot.ArmiesToTake(bot.Standing.Territories[doAttack1.To].NumArmies); if (bot.Orders.TryDeploy(doAttack1.From, numArmies)) { AILog.Log("ResolveTeamBonuses", "Detected a split bonus " + bot.BonusString(bonus) + ", and we're attempting to break the split by doing a small attack from " + bot.TerrString(doAttack1.From) + " to " + bot.TerrString(doAttack1.To) + " with " + numArmies); bot.Orders.AddAttack(doAttack1.From, doAttack1.To, AttackTransferEnum.Attack, numArmies, true); } } } else if (owners[0].Key == bot.PlayerID) { //We should take the bonus foreach (var doAttack2 in attacks) { var numArmies = bot.ArmiesToTake(bot.Standing.Territories[doAttack2.To].NumArmies); if (bot.Orders.TryDeploy(doAttack2.From, numArmies)) { AILog.Log("ResolveTeamBonuses", "Detected we should take bonus " + bot.BonusString(bonus) + ", so we're attacking from " + bot.TerrString(doAttack2.From) + " to " + bot.TerrString(doAttack2.To) + " with " + numArmies); bot.Orders.AddAttack(doAttack2.From, doAttack2.To, AttackTransferEnum.Attack, 2, true); } } } } } }
public override string ToString() { return(Bot.TerrString(ID) + " Weight=" + Weight + ", CriticalPath=" + WeightFromCriticalPath + ", Bonuses: " + Bonuses.Select(o => Bot.BonusString(o.Key) + " " + o.Value).JoinStrings(", ")); }
private static void Deploy(BotMain bot, string source, TerritoryIDType terrID, int armies) { AILog.Log("DeployRemaining", source + " deploying " + armies + " to " + bot.TerrString(terrID)); bot.Orders.Deploy(terrID, armies, true); }
public override string ToString() { return("From " + Bot.TerrString(From) + " to " + Bot.TerrString(To) + ". DefenseImportance=" + DefenseImportance + " OffensiveImportance=" + OffenseImportance); }
public override void Go(int useArmies, bool highlyValuedOnly) { //In FFA, focus on expansion even moreso than in headsup float minWeight = !highlyValuedOnly ? float.MinValue : ExpansionHelper.BaseBonusWeight + (Bot.IsFFA ? -10 : 0) + (Bot.UseRandomness ? (float)RandomUtility.BellRandom(-9, 9) : 0); AILog.Log("Expand", "Expand called with useArmies=" + useArmies + ", minWeight=" + minWeight); Assert.Fatal(useArmies >= 0, "useArmies negative"); var meetsFilter = AttackableNeutrals.Where(o => o.Value.Weight > minWeight).ToDictionary(o => o.Key, o => o.Value); //Don't bother with anything less than the min weight AILog.Log("Expand", meetsFilter.Count + " items over weight " + minWeight + " (" + AttackableNeutrals.Count + " total), top:"); foreach (var spot in meetsFilter.OrderByDescending(o => o.Value.Weight).Take(10)) { AILog.Log("Expand", " - " + spot.Value); } int armiesToExpandWithRemaining = useArmies; while (meetsFilter.Count > 0) { var expandTo = meetsFilter.OrderByDescending(o => o.Value.Weight).First().Key; meetsFilter.Remove(expandTo); if (Bot.Orders.Orders.OfType <GameOrderAttackTransfer>().Any(o => o.To == expandTo)) { continue; //we've already attacked it } int attackWith = Bot.ArmiesToTake(Bot.Standing.Territories[expandTo].NumArmies.Fogged ? ExpansionHelper.GuessNumberOfArmies(Bot, expandTo) : Bot.Standing.Territories[expandTo].NumArmies); var attackFromList = Bot.Map.Territories[expandTo].ConnectedTo.Keys .Select(o => Bot.Standing.Territories[o]) .Where(o => o.OwnerPlayerID == Bot.PlayerID && !Bot.AvoidTerritories.Contains(o.ID)) .ToDictionary(o => o.ID, o => Bot.MakeOrders.GetArmiesAvailable(o.ID)) .OrderByDescending(o => o.Value).ToList(); if (attackFromList.Count == 0) { continue; //nowhere to attack from } var attackFrom = attackFromList[0]; int armiesNeedToDeploy = Math.Max(0, attackWith - attackFrom.Value); if (armiesToExpandWithRemaining >= armiesNeedToDeploy) { //Deploy if needed if (armiesNeedToDeploy > 0) { armiesToExpandWithRemaining -= armiesNeedToDeploy; if (!Bot.Orders.TryDeploy(attackFrom.Key, armiesNeedToDeploy)) { continue; } else { //Remember that we deployed armies towards the capture of this bonus foreach (var bonusID in AttackableNeutrals[expandTo].Bonuses.Keys) { foreach (var an in AttackableNeutrals.Values) { if (an.Bonuses.ContainsKey(bonusID)) { an.Bonuses[bonusID].DeployedTowardsCapturing += armiesNeedToDeploy; } } } } } AILog.Log("Expand", "Expanding into " + Bot.TerrString(expandTo) + " from " + Bot.TerrString(attackFrom.Key) + " with " + attackWith + " by deploying " + armiesNeedToDeploy + ", already had " + attackFrom.Value + " available"); //Attack Bot.Orders.AddAttack(attackFrom.Key, expandTo, AttackTransferEnum.AttackTransfer, attackWith, false); } } }
private void TryExpand(ref int armiesLeft, int maxDistanceArg, float armyMult, Dictionary <BonusIDType, float> bonusWeights) { var maxDistance = maxDistanceArg; foreach (var borderTerritory in MultiAttackStanding.Territories.Values.Where(o => Bot.IsBorderTerritory(MultiAttackStanding, o.ID)).OrderByDescending(o => o.NumArmies.NumArmies).ToList()) { if (Bot.PastTime(10)) { return; } var stackSize = Math.Max(0, MultiAttackStanding.Territories[borderTerritory.ID].NumArmies.NumArmies - Bot.Settings.OneArmyMustStandGuardOneOrZero); var canDeployOnBorderTerritory = Bot.Standing.Territories[borderTerritory.ID].OwnerPlayerID == Bot.PlayerID; if (stackSize == 0 && !canDeployOnBorderTerritory) { continue; } var bonusPaths = Bot.Map.Bonuses.Keys .Where(o => Bot.BonusValue(o) > 0) .Select(o => { if (maxDistance > 1 && Bot.PastTime(7)) { AILog.Log("MultiAttackExpand", "Due to slow speed, reducing bonus search distance from " + maxDistance + " to 1"); maxDistance = 1; //if we're taking too long, give up on far away bonuses. Otherwise this algorithm can take forever on large maps } if (Bot.PastTime(10)) { return(null); } return(MultiAttackPathToBonus.TryCreate(Bot, borderTerritory.ID, o, MultiAttackStanding, maxDistance)); }) .Where(o => o != null) .ToDictionary(o => o.BonusID, o => o); var adjustedWeights = bonusPaths.Values.ToDictionary(o => o.BonusID, o => bonusWeights[o.BonusID] - o.ArmiesNeedToKillToGetThere * armyMult); //AILog.Log("ExpandMultiAttack", "Found " + bonusPaths.Count + " bonuses in range of " + Bot.TerrString(borderTerritory.ID) + ": " + bonusPaths.Values.OrderByDescending(o => adjustedWeights[o.BonusID]).Select(o => Bot.BonusString(o.BonusID)).JoinStrings(", ")); foreach (var bonus in bonusPaths.Values.OrderByDescending(o => adjustedWeights[o.BonusID])) { var estimatedArmiesNeedToDeploy = Math.Max(0, bonus.EstArmiesNeededToCapture - stackSize); if (estimatedArmiesNeedToDeploy > armiesLeft) { continue; } if (estimatedArmiesNeedToDeploy > 0 && !canDeployOnBorderTerritory) { continue; } AILog.Log("ExpandMultiAttack", "Considering expansion to bonus " + Bot.BonusString(bonus.BonusID) + " from " + Bot.TerrString(borderTerritory.ID) + ". stackSize=" + stackSize + " estimatedArmiesNeedToDeploy=" + estimatedArmiesNeedToDeploy + " weight=" + adjustedWeights[bonus.BonusID] + " ArmiesNeedToKillToGetThere=" + bonus.ArmiesNeedToKillToGetThere + " EstArmiesNeededToCapture=" + bonus.EstArmiesNeededToCapture + " armiesLeft=" + armiesLeft + " PathToGetThere=" + bonus.PathToGetThere.Select(o => Bot.TerrString(o)).JoinStrings(" -> ")); var plan = MultiAttackPlan.TryCreate(Bot, bonus, MultiAttackStanding, borderTerritory.ID); if (plan == null) { AILog.Log("ExpandMultiAttack", " - Could not find a plan"); continue; } var actualArmiesNeedToCapture = Bot.ArmiesToTakeMultiAttack(plan.Select(o => ExpansionHelper.GuessNumberOfArmies(Bot, o.To, MultiAttackStanding, GuessOpponentNumberOfArmiesInFog))); var actualArmiesNeedToDeploy = Math.Max(0, actualArmiesNeedToCapture - stackSize); if (actualArmiesNeedToDeploy > armiesLeft) { AILog.Log("ExpandMultiAttack", " - actualArmiesNeedToDeploy=" + actualArmiesNeedToDeploy + " is not enough, have " + armiesLeft); continue; } var stackStartedFrom = this.StackStartedFrom.ContainsKey(borderTerritory.ID) ? this.StackStartedFrom[borderTerritory.ID] : borderTerritory.ID; if (!Bot.Orders.TryDeploy(stackStartedFrom, actualArmiesNeedToDeploy)) { AILog.Log("ExpandMultiAttack", " - Could not deploy armies"); continue; } armiesLeft -= actualArmiesNeedToDeploy; AILog.Log("ExpandMultiAttack", " - Attempting to capture. actualArmiesNeedToDeploy=" + actualArmiesNeedToDeploy + " plan=" + plan.Select(o => o.ToString()).JoinStrings(" -> ")); var terr = borderTerritory.ID; foreach (var planStep in plan) { Assert.Fatal(Bot.Map.Territories[terr].ConnectedTo.ContainsKey(planStep.To), terr + " does not connect to " + planStep.To); var defendersKill = SharedUtility.Round(ExpansionHelper.GuessNumberOfArmies(Bot, planStep.To).DefensePower *Bot.Settings.DefenseKillRate); if (planStep.Type == MultiAttackPlanType.MainStack) { Bot.Orders.AddAttack(terr, planStep.To, AttackTransferEnum.AttackTransfer, 100, false, true); MultiAttackStanding.Territories[planStep.To] = TerritoryStanding.Create(planStep.To, Bot.PlayerID, MultiAttackStanding.Territories[terr].NumArmies.Subtract(new Armies(defendersKill))); MultiAttackStanding.Territories[terr].NumArmies = new Armies(Bot.Settings.OneArmyMustStandGuardOneOrZero); terr = planStep.To; } else if (planStep.Type == MultiAttackPlanType.OneTerritoryOffshoot) { var attackWith = Bot.ArmiesToTake(ExpansionHelper.GuessNumberOfArmies(Bot, planStep.To)); Bot.Orders.AddAttack(terr, planStep.To, AttackTransferEnum.AttackTransfer, attackWith, false, false); MultiAttackStanding.Territories[planStep.To] = TerritoryStanding.Create(planStep.To, Bot.PlayerID, new Armies(attackWith - defendersKill)); MultiAttackStanding.Territories[terr].NumArmies = MultiAttackStanding.Territories[terr].NumArmies.Subtract(new Armies(attackWith)); } this.StackStartedFrom.Add(planStep.To, stackStartedFrom); } //After taking a bonus, we changed MultiAttackStanding. Therefore, we need to re-calc everything. TryExpand(ref armiesLeft, maxDistance, armyMult, bonusWeights); return; } } }
private static bool PlayBlockadeCard(BotMain bot, CardInstance card) { //Look for bonuses that we can't hold and should blockade foreach (var bonus in bot.Map.Bonuses.Values) { var grouped = bonus.Territories.GroupBy(o => bot.Standing.Territories[o].OwnerPlayerID).ToDictionary(o => o.Key, o => o); if (!grouped.ContainsKey(bot.PlayerID)) { continue; //we're not in it } if (grouped.ContainsKey(TerritoryStanding.NeutralPlayerID)) { continue; //only complete bonuses -- if it's never been taken, don't blockade } var opps = grouped.Keys.Where(o => bot.IsOpponent(o)).ToList(); if (opps.Count == 0) { continue; //no opponents in it } if (bonus.Territories.Any(t => bot.AvoidTerritories.Contains(t))) { continue; //already doing something here, perhaps already blockading it. } var oppTerrs = opps.SelectMany(o => grouped[o].ToList()).ToHashSet(false); var friendlyTerrs = grouped.Where(o => bot.IsTeammateOrUs(o.Key)).SelectMany(o => o.Value.ToList()).ToList(); var friendlyArmies = friendlyTerrs.Sum(o => bot.Standing.Territories[o].NumArmies.DefensePower); var enemyArmies = oppTerrs.Sum(o => ExpansionHelper.GuessNumberOfArmies(bot, o).AttackPower); var ratio = bot.UseRandomness ? RandomUtility.BellRandom(1, 3) : 2; if (friendlyArmies * ratio > enemyArmies) { continue; } var armies = SharedUtility.Round(bot.EffectiveIncome.FreeArmies * (bot.UseRandomness ? RandomUtility.BellRandom(.1, .4) : .25)); if (armies < 5) { armies = 5; } var canBlockade = friendlyTerrs.Where(o => bot.Standing.Territories[o].OwnerPlayerID == bot.PlayerID && bot.Map.Territories[o].ConnectedTo.Keys.None(t => oppTerrs.Contains(t)) && bot.Standing.Territories[o].NumArmies.SpecialUnits.Length == 0 && bot.Standing.Territories[o].NumArmies.NumArmies < armies * 2 ).ToList(); if (canBlockade.Count == 0) { continue; } var blockade = bot.UseRandomness ? canBlockade.Random() : canBlockade.First(); var deploy = Math.Max(0, armies - bot.Standing.Territories[blockade].NumArmies.NumArmies); if (!bot.Orders.TryDeploy(blockade, deploy)) { continue; } AILog.Log("PlayCards", "Blockading " + bot.TerrString(blockade) + " with " + armies + " (had to deploy " + deploy + ")"); bot.Orders.AddOrder(GameOrderPlayCardBlockade.Create(card.ID, bot.PlayerID, blockade)); bot.AvoidTerritories.Add(blockade); return(true); } return(false); }
public static void Go(BotMain bot) { var attacks = bot.Orders.Orders.OfType <GameOrderAttackTransfer>(); foreach (var orders in attacks .Where(o => bot.Standing.Territories[o.From].OwnerPlayerID == bot.PlayerID) .Select(o => new UtilizeSpareArmies(o, bot.MakeOrders.GetArmiesAvailable(o.From))) .Where(o => o.Available > 0) .GroupBy(o => o.Order.From)) { var order = bot.UseRandomness ? orders.Random() : orders.First(); AILog.Log("UtilizeSpareArmies", "Adding " + order.Available + " available armies into attack from " + bot.TerrString(order.Order.From) + " to " + bot.TerrString(order.Order.To) + ", originally had " + order.Order.NumArmies.NumArmies); order.Order.NumArmies = order.Order.NumArmies.Add(new Armies(order.Available)); } }
/// <summary> /// Any armies that are surrounded by our own territories (or our teammates) should move towards the nearest enemy. /// </summary> public static void Go(BotMain bot) { foreach (var landlocked in bot.Standing.Territories.Values.Where(o => o.OwnerPlayerID == bot.PlayerID && bot.Map.Territories[o.ID].ConnectedTo.Keys.All(c => bot.IsTeammateOrUs(bot.Standing.Territories[c].OwnerPlayerID)) && !bot.AvoidTerritories.Contains(o.ID) && bot.MakeOrders.GetArmiesAvailable(o.ID) > 0)) { if (bot.PastTime(5)) { //Extreme cases (i.e. where one player controls all of a big map), this algorithm can take forever. We don't care about these extreme cases since they've already won. Stop processing after too long AILog.Log("MoveLandlockedUp", "Giving up due to time"); break; } var moveTowards = bot.MoveTowardsNearestBorder(landlocked.ID, true); if (moveTowards.HasValue && !bot.AvoidTerritories.Contains(moveTowards.Value)) { var armies = bot.MakeOrders.GetArmiesAvailable(landlocked.ID); AILog.Log("MoveLandlockedUp", "Ordering " + armies + " armies from " + bot.TerrString(landlocked.ID) + " to " + bot.TerrString(moveTowards.Value)); bot.Orders.AddAttack(landlocked.ID, moveTowards.Value, AttackTransferEnum.Transfer, armies, false); } } }