using DominionBase.Cards; using DominionBase.Enums; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Xml; namespace DominionBase.Players.AI.Bots { [Flags] public enum BotStrategies { None = 0, AppliesPPR = 1, Attacking = 2 } public enum BotRuleStrategy { None, AggressiveTrashing, AmbassadorWar, ApothecaryNativeVillage, AttackUntil5Coins, BigTurnBridge, BigTurnGoons, Combo, DukeEnabler, FoolsGoldEnabler, ForEngines, GoldEarlyTrashMid, GoodDeckTracker, GreedyDeckTracker, MarketSquareCombo, MineCopperFirst, ModestTrashing, PlayIfNotBuyingTopCard, RemoveAllCoppers, SilverGainer, Standard, TrashWhenObsolete } public class Bot { public string Name { get; set; } public string Author { get; set; } public string Description { get; set; } public List PlayerCount { get; set; } = new List(); public HashSet Required { get; set; } = new HashSet(); public BotStrategies Strategies { get; set; } = BotStrategies.None; public BotRuleStrategyCollection RuleStrategies { get; set; } = new BotRuleStrategyCollection(); public BotRuleCollection Rules { get; set; } = new BotRuleCollection(); public List CardPriorities { get; set; } = new List(); public bool? ShouldBuy(Type type) { if (Rules.Any(r => r.Buyable == type)) return true; return null; } public static BotCollection ParseBots() { var allCards = CardCollection.GetAllCards( c => c.Location == Location.General || c.Location == Location.Kingdom || c.Location == Location.LandscapeCard || c.Location == Location.Special).ToList(); var assembly = Assembly.GetExecutingAssembly(); var doc = new XmlDocument { XmlResolver = null }; using (var stream = assembly.GetManifestResourceStream("DominionBase.Resources.CardSettings.xml")) using (var reader = XmlReader.Create(stream, new XmlReaderSettings { XmlResolver = null })) doc.Load(reader); var cardPriorities = new ConcurrentBag(); var nodes = doc.SelectNodes("cardCollection/card"); if (nodes != null) { Parallel.ForEach(nodes.Cast(), node => { if (node.Attributes == null) return; var name = node.Attributes["name"].Value; var card = allCards.FirstOrDefault(c => c.Name == name); if (card == null) return; if (!int.TryParse(node.SelectSingleNode("playPriority")?.InnerText, out int pp) || !int.TryParse(node.SelectSingleNode("discardPriority")?.InnerText, out int dp)) return; cardPriorities.Add(new CardPriority { Name = name, CardType = card.Type, PlayPriority = pp, DiscardPriority = dp }); }); } doc = new XmlDocument { XmlResolver = null }; using (var stream = assembly.GetManifestResourceStream("DominionBase.Resources.DomBots.xml")) using (var reader = XmlReader.Create(stream, new XmlReaderSettings { XmlResolver = null })) doc.Load(reader); var parsedBots = new ConcurrentBag(); nodes = doc.SelectNodes("playerCollection/player"); if (nodes != null) { Parallel.ForEach(nodes.Cast(), node => { // Skip empty nodes and any bots that have start_state defined if (node.Attributes == null || node.SelectSingleNode("start_state") != null) return; var bot = new Bot { Name = node.Attributes?["name"].Value, Author = node.Attributes?["author"].Value, Description = node.Attributes?["description"].Value.Replace("XXXX", "\n"), }; bot.CardPriorities.AddRange(cardPriorities); if (node.SelectSingleNode("type[@name='AppliesPPR']") != null) bot.Strategies |= BotStrategies.AppliesPPR; if (node.SelectSingleNode("type[@name='Attacking']") != null) bot.Strategies |= BotStrategies.Attacking; if (node.SelectSingleNode("type[@name='TwoPlayer']") != null) bot.PlayerCount.Add(2); if (node.SelectSingleNode("type[@name='ThreePlayer']") != null) bot.PlayerCount.Add(3); if (node.SelectSingleNode("type[@name='FourPlayer']") != null) bot.PlayerCount.Add(4); var buyableMissing = false; foreach (XmlNode buyNode in node.SelectNodes("buy")) { if (buyNode.Attributes == null) continue; var mappedName = MapCardName(buyNode.Attributes["name"].Value); var cards = allCards.Where(c => c.ImageName.ToUpperInvariant() == mappedName.ToUpperInvariant()).ToList(); foreach (var type in cards.Select(c => c.Type)) bot.Required.Add(type); var buyable = cards.Select(c => c.Type).FirstOrDefault(); if (buyable == null) { buyableMissing = true; break; } if (!Enum.TryParse(buyNode.Attributes["strategy"]?.Value, true, out BotRuleStrategy outStrat)) bot.RuleStrategies[buyable] = outStrat; var rule = new BotRule {Buyable = buyable}; foreach (XmlNode conditionNode in buyNode.SelectNodes("condition")) { var condition = new BotRuleCondition(conditionNode); rule.Conditions.Add(condition); } bot.Rules.Add(rule); } if (!buyableMissing) parsedBots.Add(bot); }); } return new BotCollection(parsedBots); } public bool RequirementsSatisfied(IGame game) { if (game == null) return false; return PlayerCount.Contains(game.Players.Count) && Required.All(item => game.Table.TableEntities.Any(e => e.Value.Types.Contains(item))); } public IBuyable FindCardToBuy(IGame game, IPlayer player, List buyables) { return buyables.FirstOrDefault( b => b.TopCard.Type == Rules.FirstOrDefault(r => r.BuyAllowed(game, player, buyables))?.Buyable); } internal static string MapCardName(string cardName) { var cleanedCardName = cardName?.Replace("_", "").Replace("$", "").Replace(" ", ""); return cleanedCardName; } } }