/// <summary> /// Initialize enemy tracker with null arguments, which will be updated once we know initial position of map. /// </summary> public EnemyTracker() { this.bot = null; this.map = null; this.enemyProbs = null; this.enemyDeployed = 0; this.enemyIncome = 0; }
/// <summary> /// Initialize the /// </summary> /// <param name="bot">The name of the bot containing current game-state</param> public void init(BotMain Bot) { bot = Bot; map = bot.Map; enemyProbs = new Dictionary <TerritoryIDType, double>(); foreach (KeyValuePair <TerritoryIDType, TerritoryDetails> territory in map.Territories) { enemyProbs[territory.Key] = 0.0; } enemyIncome = Bot.Settings.MinimumArmyBonus; }
public static List <TerritoryIDType> TryFindShortestPathReversed(BotMain bot, Func <TerritoryIDType, bool> start, TerritoryIDType finish, Func <TerritoryIDType, bool> visitOpt = null) { var ret = TryFindShortestPath(bot, finish, start, visitOpt); if (ret == null) { return(null); } ret.RemoveAt(ret.Count - 1); ret.Reverse(); ret.Add(finish); return(ret); }
public static CaptureTerritories TryFindTurnsToTake(BotMain bot, BonusPath path, int armiesWeHaveInOrAroundBonus, int armiesEarnPerTurn, HashSet <TerritoryIDType> terrsToTake, Func <TerritoryStanding, int> terrToTakeDefenders, int timeout = 30) { Assert.Fatal(armiesEarnPerTurn >= 0, "Negative armiesEarnPerTurn"); var armiesDefending = terrsToTake.Sum(o => terrToTakeDefenders(bot.Standing.Territories[o])); var avgAttackersNeededPerTerritory = bot.ArmiesToTake(new Armies(SharedUtility.Round((double)armiesDefending / (double)terrsToTake.Count))); var avgRemaindersPerTerritory = avgAttackersNeededPerTerritory - SharedUtility.Round(avgAttackersNeededPerTerritory * bot.Settings.DefenseKillRate); var armiesNeeded = bot.ArmiesToTake(new Armies(armiesDefending)); for (int turns = path.TurnsToTakeByDistance; ; turns++) { if (turns >= timeout) { return(null); //could not find a solution in time } var totalDeployed = armiesEarnPerTurn * turns; var totalArmies = armiesWeHaveInOrAroundBonus + totalDeployed; var territoriesGettingRemaindersFrom = Math.Min(terrsToTake.Count - SharedUtility.Round(terrsToTake.Count / (float)turns), terrsToTake.Count - 1); var remaindersCanUse = territoriesGettingRemaindersFrom * avgRemaindersPerTerritory; var armiesToStandGuard = territoriesGettingRemaindersFrom * bot.Settings.OneArmyMustStandGuardOneOrZero; var totalNeed = armiesNeeded + armiesToStandGuard; var totalHave = totalArmies + remaindersCanUse; if (totalHave >= totalNeed) { return(new CaptureTerritories(turns, totalDeployed - (totalHave - totalNeed))); } if (armiesEarnPerTurn == 0) { return(null); //we can't take it with what we have, and we are earning no armies. Just abort rather than infinite loop } } #if CSSCALA throw new Exception(); #endif }
/// <summary> /// Updates enemy tracker with data from new turn. /// </summary> /// <param name="bot">Bot with current game state</param> public void update(BotMain bot) { // TODO: Weighted Updating here // TODO: Handle multiple enemies foreach (KeyValuePair <TerritoryIDType, TerritoryStanding> territory in bot.Standing.Territories) { // enemy doesn't control this territory if (territory.Value.IsNeutral || bot.IsTeammateOrUs(territory.Value.OwnerPlayerID) || territory.Value.OwnerPlayerID == TerritoryStanding.AvailableForDistribution) { enemyProbs[territory.Key] = 0.0; } else if (territory.Value.OwnerPlayerID == TerritoryStanding.FogPlayerID) { // Weighted update based on neighbors } else { enemyProbs[territory.Key] = 1.0; } } }
public static Armies GuessNumberOfArmies(BotMain bot, TerritoryIDType terrID, GameStanding standing, Func <BotMain, TerritoryStanding, Armies> opponentFoggedTerritoryOpt = null) { var ts = standing.Territories[terrID]; if (!ts.NumArmies.Fogged) { return(ts.NumArmies); } if (bot.IsOpponent(ts.OwnerPlayerID)) { //We can see it's an opponent, but the armies are fogged. This can happen in light, dense, or heavy fog. We have no way of knowing what's there, so just assume the minimum if (opponentFoggedTerritoryOpt != null) { return(opponentFoggedTerritoryOpt(bot, ts)); } else { return(new Armies(bot.Settings.OneArmyMustStandGuardOneOrZero)); } } if (bot.DistributionStandingOpt != null) { var dist = bot.DistributionStandingOpt.Territories[terrID]; if (dist.IsNeutral) { return(dist.NumArmies); } Assert.Fatal(dist.OwnerPlayerID == TerritoryStanding.AvailableForDistribution); return(new Armies(Math.Max(bot.Settings.InitialNeutralsInDistribution, bot.Settings.InitialPlayerArmiesPerTerritory))); //TODO: If it's not a random distribution, we could check if this territory is in the distribution and be more accurate on whether a player started with it or not. } return(new Armies(bot.Settings.InitialNonDistributionArmies)); }
/// <returns>A list of the territories leading from start to any of finish. Will not include the start territory in the list, but will include the finish territory.</returns> public static List <TerritoryIDType> TryFindShortestPath(BotMain bot, TerritoryIDType start, Func <TerritoryIDType, bool> finish, Func <TerritoryIDType, bool> visitOpt = null) { if (finish(start)) { var ret = new List <TerritoryIDType>(); ret.Add(start); return(ret); } var previous = new Dictionary <TerritoryIDType, TerritoryIDType>(); var distances = new Dictionary <TerritoryIDType, int>(); var nodes = new List <TerritoryIDType>(); foreach (var vertex in bot.Map.Territories.Keys.Where(o => visitOpt == null || visitOpt(o))) { if (vertex == start) { distances[vertex] = 0; } else { distances[vertex] = int.MaxValue; } nodes.Add(vertex); } while (true) { if (bot.PastTime(10)) { return(null); //if we're taking too long, just abort. This algorithm can take forever on big maps. } if (nodes.Count == 0) { return(null); } nodes.Sort((x, y) => SharedUtility.CompareInts(distances[x], distances[y])); var smallest = nodes[0]; nodes.Remove(smallest); if (finish(smallest)) { var ret = new List <TerritoryIDType>(); while (previous.ContainsKey(smallest)) { ret.Add(smallest); smallest = previous[smallest]; } if (ret.Count == 0) { return(null); } ret.Reverse(); return(ret); } if (distances[smallest] == int.MaxValue) { return(null); } foreach (var neighbor in bot.Map.Territories[smallest].ConnectedTo.Keys.Where(o => visitOpt == null || visitOpt(o))) { var alt = distances[smallest] + 1; if (alt < distances[neighbor]) { distances[neighbor] = alt; previous[neighbor] = smallest; } } } #if CSSCALA throw new Exception(); #endif }
public static BonusPath TryCreate(BotMain bot, BonusIDType bonusID, Func <TerritoryStanding, bool> weOwn) { var bonus = bot.Map.Bonuses[bonusID]; var allUnownedTerrsInBonus = bonus.Territories.Where(o => !weOwn(bot.Standing.Territories[o])).ToHashSet(true); if (allUnownedTerrsInBonus.Count == 0) { return(new BonusPath(bonusID, 0, new HashSet <TerritoryIDType>())); //Already own the bonus. We'll only get here with one-territory bonuses during distribution } var terrsToTake = allUnownedTerrsInBonus.ToHashSet(true); var ownedTerritoriesTraverse = bot.Standing.Territories.Values.Where(o => weOwn(o)).Select(o => o.ID).ToHashSet(true); HashSet <TerritoryIDType> finalTerritoriesCaptured = null; var turns = 1; while (true) { var takeThisTurn = terrsToTake.Where(o => bot.Map.Territories[o].ConnectedTo.Keys.Any(z => ownedTerritoriesTraverse.Contains(z))).ToHashSet(true); if (takeThisTurn.Count == 0) { //We can't take it without leaving the bonus. AILog.Log("BonusPath", " Could not find a way to take bonus " + bot.BonusString(bonus) + " without leaving it"); return(null); } if (takeThisTurn.Count == terrsToTake.Count) { //We captured the bonus finalTerritoriesCaptured = takeThisTurn; break; } //Keep expanding! turns++; ownedTerritoriesTraverse.AddRange(takeThisTurn); terrsToTake.RemoveAll(takeThisTurn); } var terrsWeOwnInOrAroundBonus = bonus.Territories.Concat(bonus.Territories.SelectMany(o => bot.Map.Territories[o].ConnectedTo.Keys)).Where(o => weOwn(bot.Standing.Territories[o])).ToHashSet(false); var traverse = allUnownedTerrsInBonus.Concat(terrsWeOwnInOrAroundBonus).ToHashSet(false); var criticalPath = new HashSet <TerritoryIDType>(); foreach (var final in finalTerritoriesCaptured) { var path = FindPath.TryFindShortestPathReversed(bot, o => weOwn(bot.Standing.Territories[o]), final, o => traverse.Contains(o)); if (path != null) { //AILog.Log("BonusPath", " Critical path to " + bot.TerrString(final) + " goes " + path.Select(o => bot.TerrString(o)).JoinStrings(" -> ")); criticalPath.AddRange(path); } else { AILog.Log("BonusPath", " Could not find a path to " + bot.TerrString(final)); } } //AILog.Log("BonusPath", "With infinite armies, we can take bonus " + bot.BonusString(bonus) + " in " + TurnsToTake + " turns. " + /*" Final territories=" + finalTerritoriesCaptured.Select(o => bot.TerrString(o)).JoinStrings(", ") +*/ " Critical path=" + TerritoriesOnCriticalPath.Select(o => bot.TerrString(o)).JoinStrings(", ")); return(new BonusPath(bonusID, turns, criticalPath)); }
public Neighbor(BotMain bot, PlayerIDType id) { ID = id; Bot = bot; }
public static Armies GuessNumberOfArmies(BotMain bot, TerritoryIDType terrID) { return(GuessNumberOfArmies(bot, terrID, bot.Standing)); }
public static float WeighBonus(BotMain bot, BonusIDType bonusID, Func <TerritoryStanding, bool> weOwn, int turnsToTake) { var bonus = bot.Map.Bonuses[bonusID]; int bonusValue = bot.BonusValue(bonusID); if (bonusValue <= 0) { throw new Exception("Considered zero or negative bonuses"); //we should not even be considering zero or negative bonuses, ensure they're filtered out before we get here. } var weight = BaseBonusWeight; //When randomness is enabled, modify the bonus by a fixed amount for this bonus. weight += bot.BonusFuzz(bonusID); weight += bonusValue * (bot.IsFFA ? 7 : 4); //Subtract value for each additional turn it takes to take over one weight -= bonusValue * (turnsToTake - 1); weight -= bonus.Territories.Count * bot.Settings.OneArmyMustStandGuardOneOrZero; float armyMult = ArmyMultiplier(bot.Settings.DefenseKillRate); //How many territories do we need to take to get it? Subtract one weight for each army standing in our way foreach (var terrInBonus in bonus.Territories) { var ts = bot.Standing.Territories[terrInBonus]; if (weOwn(ts)) { weight += ts.NumArmies.AttackPower * armyMult; //Already own it } else if (ts.OwnerPlayerID == TerritoryStanding.FogPlayerID) { weight -= GuessNumberOfArmies(bot, ts.ID).DefensePower *armyMult; } else if (bot.IsTeammate(ts.OwnerPlayerID)) { weight -= bot.Players[ts.OwnerPlayerID].IsAIOrHumanTurnedIntoAI ? 0 : ts.NumArmies.DefensePower * 4 * armyMult; //Human teammate in it. We'll defer to them since humans know best. } else if (ts.OwnerPlayerID == TerritoryStanding.AvailableForDistribution) { weight -= Math.Max(bot.Settings.InitialNeutralsInDistribution, bot.Settings.InitialPlayerArmiesPerTerritory) * armyMult; //assume another player could start there } else if (ts.IsNeutral) { //Neutral in it if (ts.NumArmies.Fogged == false) { weight -= ts.NumArmies.DefensePower * armyMult; } else { weight -= GuessNumberOfArmies(bot, ts.ID).DefensePower *armyMult; } } else { //Opponent in it - expansion less likely if (ts.NumArmies.Fogged == false) { weight -= ts.NumArmies.DefensePower * 3 * armyMult; } else { weight -= GuessNumberOfArmies(bot, ts.ID).DefensePower *armyMult; } } } return(weight); }