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); } } } } } }
private void TryDoAttack(PossibleAttack attack, ref int armiesToOffense) { bool commanders = true; var toTS = Bot.Standing.Territories[attack.To]; int attackWith = Bot.ArmiesToTake(toTS.NumArmies.Fogged == false ? toTS.NumArmies : ExpansionHelper.GuessNumberOfArmies(Bot, toTS.ID)); //Add a few more to what's required so we're not as predictable. if (Bot.UseRandomness) { attackWith += SharedUtility.Round(attackWith * (RandomUtility.RandomPercentage() * .2)); //Once in a while, be willing to do a stupid attack. Sometimes it will work out, sometimes it will fail catastrophically if (RandomUtility.RandomNumber(20) == 0) { var origAttackWith = attackWith; attackWith = SharedUtility.Round(attackWith * RandomUtility.RandomPercentage()); commanders = false; if (attackWith != origAttackWith) { AILog.Log("Offense", "Willing to do a \"stupid\" attack from " + Bot.TerrString(attack.From) + " to " + Bot.TerrString(attack.To) + ": attacking with " + attackWith + " instead of our planned " + origAttackWith); } } } else { attackWith += SharedUtility.Round(attackWith * 0.1); } int have = Bot.MakeOrders.GetArmiesAvailable(attack.From); int need = Math.Max(0, attackWith - have); if (need > armiesToOffense) { //We can't swing it. Just deploy the rest and quit. Will try again next turn. if (armiesToOffense > 0 && Bot.Orders.TryDeploy(attack.From, armiesToOffense)) { AILog.Log("Offense", "Could not attack from " + Bot.TerrString(attack.From) + " to " + Bot.TerrString(attack.To) + " with " + attackWith + ". Short by " + need + ". Just deploying " + armiesToOffense + " to the source."); armiesToOffense = 0; } } else { //We can attack. First deploy however many we needed if (need > 0) { if (!Bot.Orders.TryDeploy(attack.From, need)) { return; } armiesToOffense -= need; Assert.Fatal(armiesToOffense >= 0); } //Now issue the attack Bot.Orders.AddAttack(attack.From, attack.To, AttackTransferEnum.AttackTransfer, attackWith, false, commanders: commanders); AILog.Log("Offense", "Attacking from " + Bot.TerrString(attack.From) + " to " + Bot.TerrString(attack.To) + " with " + attackWith + " by deploying " + need); } }
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; } } }