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 void Weight(Dictionary <PlayerIDType, int> weightedNeighbors) { var opponentID = Bot.Standing.Territories[this.To].OwnerPlayerID; Assert.Fatal(opponentID != TerritoryStanding.NeutralPlayerID); Assert.Fatal(!Bot.IsTeammateOrUs(opponentID)); //Seed the border weight with a lessened neighbor weight this.DefenseImportance = !weightedNeighbors.ContainsKey(opponentID) ? 0 : (weightedNeighbors[opponentID] / 10.0); this.OffenseImportance = this.DefenseImportance; //Are we defending a bonus we control? foreach (var defendingBonus in Bot.Map.Territories[this.From].PartOfBonuses .Select(b => Bot.Map.Bonuses[b]) .Where(b => Bot.PlayerControlsBonus(b) && Bot.Map.Territories[this.To].PartOfBonuses .Select(b2 => Bot.Map.Bonuses[b2]) .Any(b2 => !Bot.PlayerControlsBonus(b2)))) { //Defend importance is bonus value * 10 this.DefenseImportance += Bot.BonusValue(defendingBonus.ID) * 10.0; } //Would attacking break an opponents bonus? foreach (var attackingBonus in Bot.Map.Territories[this.To].PartOfBonuses .Select(b => Bot.Map.Bonuses[b]) .Where(b => Bot.OpponentMightControlBonus(b))) { this.OffenseImportance += Bot.BonusValue(attackingBonus.ID) * (Bot.IsFFA ? 4.0 : 10); //be conservative in FFAs, but aggressive in heads up. } var toTs = Bot.Standing.Territories[this.To]; //How is our current ratio var ourArmies = Bot.Standing.Territories[this.From].NumArmies.NumArmies; var theirArmies = !toTs.NumArmies.Fogged ? toTs.NumArmies.DefensePower : Bot.UseRandomness ? RandomUtility.RandomNumber(ourArmies * 2) : (int)(ourArmies / 2); var ratio = (double)theirArmies / (double)ourArmies; if (ourArmies + theirArmies < 10) { ratio = 1; //Small numbers change so rapidly anyway that we just consider it equal. } this.OffenseImportance *= ratio; this.DefenseImportance *= ratio; //AILog.Log("Returning " + possibleAttack.OffenseImportance + "," + possibleAttack.DefenseImportance); }
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); } } }
/// <summary> /// Returns null if we can't find a way to take the bonus or if we already own it /// </summary> /// <param name="bot"></param> /// <param name="bonusID"></param> /// <returns></returns> public static MultiAttackPathToBonus TryCreate(BotMain bot, TerritoryIDType startFrom, BonusIDType bonusID, GameStanding standing, int maxDistance) { var bonus = bot.Map.Bonuses[bonusID]; var allUnownedTerrsInBonus = bonus.Territories.Where(o => standing.Territories[o].OwnerPlayerID != bot.PlayerID).ToHashSet(true); if (allUnownedTerrsInBonus.Count == 0) { return(null); //already own it } HashSet <TerritoryIDType> terrsWeEnterBonus; int jumpsToGetToBonus = DistanceToTerrs(bot, startFrom, bonus.Territories.ToHashSet(true), standing, maxDistance, out terrsWeEnterBonus); if (jumpsToGetToBonus == int.MaxValue) { return(null); //can't take it within a reasonable searching distance } if (jumpsToGetToBonus == 0) { //We're already in it var armiesNeededToCapture = bot.ArmiesToTakeMultiAttack(allUnownedTerrsInBonus.Select(o => ExpansionHelper.GuessNumberOfArmies(bot, o, standing, MultiAttackExpand.GuessOpponentNumberOfArmiesInFog))); return(new MultiAttackPathToBonus(bot, startFrom, bonusID, 0, armiesNeededToCapture, 0, new List <TerritoryIDType>())); } else { var pathToGetThere = FindPath.TryFindShortestPath(bot, startFrom, t => terrsWeEnterBonus.Contains(t), visit => visit == startFrom || bot.IsTeammateOrUs(standing.Territories[visit].OwnerPlayerID) == false); if (pathToGetThere == null) { return(null); } var getThere = pathToGetThere.ExceptOne(pathToGetThere.Last()); var armiesNeededToCapture = bot.ArmiesToTakeMultiAttack(getThere.Concat(allUnownedTerrsInBonus).Select(o => ExpansionHelper.GuessNumberOfArmies(bot, o, standing, MultiAttackExpand.GuessOpponentNumberOfArmiesInFog))); return(new MultiAttackPathToBonus(bot, startFrom, bonusID, jumpsToGetToBonus, armiesNeededToCapture, getThere.Sum(o => ExpansionHelper.GuessNumberOfArmies(bot, o, standing).DefensePower), pathToGetThere)); } }
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); }
/// <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); } } }