using DominionBase.Cards; using DominionBase.Enums; using DominionBase.Players; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; using System.Xml; namespace DominionBase.Piles { public class BuyCheckEventArgs : EventArgs { public IPlayer CurrentPlayer { get; } public bool Cancelled { get; set; } = false; public BuyCheckEventArgs(IPlayer player) { CurrentPlayer = player; } } public class TokensChangedEventArgs : EventArgs { public Token Token { get; } public TokensChangedEventArgs(Token token) { Token = token; } } public class CardGainEventArgs : EventArgs { public Card Card { get; } public bool Cancelled { get; set; } = false; public Dictionary Actions { get; } = new Dictionary(); public List HandledBy { get; } = new List(); public CardGainEventArgs(Card card) { Card = card; } } public delegate void CardGainMethod(IPlayer player, ref CardGainEventArgs cardGainEventArgs); public class CardGainAction { public IPlayer Player { get; } public Card Card { get; } public string Text { get; } public CardGainMethod Method { get; } public CardGainAction(IPlayer player, Card card, string text, CardGainMethod method) { Player = player; Card = card; Text = text; Method = method; } } public delegate void BuyCheckEventHandler(object sender, BuyCheckEventArgs e); public delegate void TokensChangedEventHandler(object sender, TokensChangedEventArgs e); public class Supply : CardPile, IComparable, IBuyable, IRandomizable, ISupply { public virtual event BuyCheckEventHandler BuyCheck; public virtual event TokensChangedEventHandler TokensChanged; public override event PileChangedEventHandler PileChanged; private IGame _Game; private readonly HashSet _Types = new HashSet(); private Type _CardClassType; private Card _CardBase; private Cost _LastComputedCost; internal Supply(IGame game, PlayerCollection players, Type type, int count = 0) : base(Visibility.All, VisibilityTo.All, null, true) { Init(game, players, type, count); } private void Init(IGame game, PlayerCollection players, Type type, int count = 0) { if (!type.IsSubclassOf(typeof(Card))) throw new ArgumentException("Type must be a subclass of Card class"); _Game = game; _CardClassType = type; _CardBase = Card.CreateInstance(_CardClassType); Visibility = _CardBase.SupplyVisibility; _CardBase.AddedToSupply(_Game, this); AddTo(count); if (players != null) { foreach (var player in players) AddPlayer(player); } } public void Empty() { _Types.Clear(); base.Clear(); } public new void Clear() { Empty(); _CardBase = null; _Game = null; } public new IEnumerator GetEnumerator() { switch (Visibility) { case Visibility.Top: yield return (IDisplayable)TopCard; break; case Visibility.None: yield break; case Visibility.All: var copy = ToList(); foreach (var t in copy) yield return t; break; } } public virtual void CheckSetup(Preset preset, ITable table) { } public virtual void CheckSetup(Preset preset, string cardName, IRandomizable card) { } public virtual CardSettingCollection GenerateSettings() { return new CardSettingCollection(); } public virtual void FinalizeSettings(CardSettingCollection settings) { } public virtual void End(IPlayer player, DisplayableCollection collection) { } public void AddPlayer(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); player.PhaseChanged += Player_PhaseChangedEvent; player.PlayerModeChanged += Player_PlayerModeChangedEvent; } public void RemovePlayer(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); player.PhaseChanged -= Player_PhaseChangedEvent; player.PlayerModeChanged -= Player_PlayerModeChangedEvent; } public override void EndChanges() { AsynchronousChanging = false; if (AsynchronousPileChangedEventArgs != null) { PileChanged?.Invoke(this, AsynchronousPileChangedEventArgs); } AsynchronousPileChangedEventArgs = null; } private void Player_PhaseChangedEvent(object sender, PhaseChangedEventArgs e) { _CardBase?.PhaseChanged(sender, e); PhaseChanged(sender, e); } private void Player_PlayerModeChangedEvent(object sender, PlayerModeChangedEventArgs e) { _CardBase?.PlayerModeChanged(sender, e); PlayerModeChanged(sender, e); } public int StartingStackSize { get; private set; } public TokenCollection Tokens { get; } = new TokenCollection(); public void AddToken(Token token) { Contract.Requires(token != null, "token cannot be null"); Tokens.Add(token); token.AddedTo(this); if (TokensChanged != null) { var etcea = new TokensChangedEventArgs(token); TokensChanged(this, etcea); } var pcea = new PileChangedEventArgs(Operation.Refresh); if (AsynchronousChanging) { AsynchronousPileChangedEventArgs = pcea; } else { PileChanged?.Invoke(this, pcea); } } public Token RemoveToken(Token token) { Contract.Requires(token != null, "token cannot be null"); Tokens.Remove(token); token.RemovedFrom(this); if (TokensChanged != null) { var etcea = new TokensChangedEventArgs(token); TokensChanged(this, etcea); } var pcea = new PileChangedEventArgs(Operation.Refresh); if (AsynchronousChanging) { AsynchronousPileChangedEventArgs = pcea; } else { PileChanged?.Invoke(this, pcea); } return token; } public Token RemoveToken(Type tokenType) { var foundToken = Tokens.FirstOrDefault(t => t.GetType() == tokenType); return foundToken != null ? RemoveToken(foundToken) : null; } public TokenCollection RemoveTokens(Type tokenType) { var tokens = new TokenCollection(Tokens.Where(t => t.Type == tokenType)); foreach (var token in tokens) RemoveToken(token); return tokens; } #region ICardBase variables, properties, & methods public Cost BaseCost => TopCard != null ? TopCard.BaseCost : _CardBase.BaseCost; public Type BaseType => Type; public Categories Category => Randomizer.Category; public virtual bool HasCost => true; public bool HasVPs => false; public string SetupText => Randomizer.SetupText; public Source Source => Randomizer.Source; public string Text => _CardBase.Text; public virtual List GetSerializingTypes() { return new List(); } #endregion #region IDisplayable variables, properties, & methods public CardBack CardBack => Randomizer.CardBack; public IDisplayable DisplayCard => TopCard == null ? RootCard : (IDisplayable)TopCard; public Edition Edition => Randomizer.Edition; public virtual Facing Facing => Facing.FaceUp; public virtual bool IsStackable => false; public Location Location => Randomizer.Location; public Orientation Orientation { get; } = Orientation.Portrait; public IDisplayable RootCard => Randomizer; public Traits Traits => Randomizer.Traits; public virtual DisplayableCollection Stack() { return new DisplayableCollection { this }; } public override void TearDown(IGame game) { base.TearDown(game); Tokens.TearDown(game); _CardBase.TearDown(game); foreach (var player in _Game.Players) RemovePlayer(player); } #endregion #region ITableable Properties public virtual bool CanUndo => true; public IDisplayable Randomizer => _CardBase; public Type TableableType => Type; public IEnumerable Types => _Types; #endregion public string SpecialPresetKey => null; public ICost TopCard => this.Any() ? First() : null; public Type Type => Randomizer.Type; public CardBenefit Benefit => _CardBase.Benefit; public bool IsEndgameTriggered => ((Card)Randomizer).IsEndgameTriggered(this); public void AddTo(IEnumerable cards) { Contract.Requires(cards != null, "cards cannot be null"); foreach (var card in cards) AddTo(card); } public void AddTo(Card card, int skip = 0) { Contract.Requires(card != null, "card cannot be null"); if (!card.Category.HasFlag(Categories.Card)) throw new Exception("Cannot add card of a different type to Supply pile"); Insert(skip, card); card.AddedToSupply(_Game, this); var pileChangedCopy = PileChanged; pileChangedCopy?.Invoke(this, new PileChangedEventArgs(null, Operation.Added, new CardCollection() { card })); } public void AddTo(int count) { if (_CardClassType == null) throw new Exception("Cannot use this method without a proper Card Type"); for (var i = 0; i < count; i++) AddTo(Card.CreateInstance(_CardClassType)); } public void Bought(IPlayer player) { TopCard.Bought(player); foreach (var token in Tokens) { if (token.Buying(_Game.Table, player)) { Tokens.Remove(token); _Game.Table.TokenPiles.Add(token); if (TokensChanged != null) { var etcea = new TokensChangedEventArgs(token); TokensChanged(this, etcea); } } } } public Card Take() { return Take(TopCard.Type, 1).ElementAt(0); } public IEnumerable Take(int count) { return Take(TopCard.Type, count); } public Card Take(Type type) { return Take(type, 1).ElementAt(0); } public IEnumerable Take(Type type, int count) { var returnCards = FindAll(card => card.Type == type).Take(count).ToList(); if (count == 0) return returnCards; if (!returnCards.Any()) throw new Exception("Nothing to take!"); if (_Game.State != GameState.Setup) { for (var index = Tokens.Count - 1; index >= 0; index--) { var token = Tokens[index]; if (token.Gaining()) { Tokens.Remove(token); _Game.Table.TokenPiles.Add(token); if (TokensChanged != null) { var etcea = new TokensChangedEventArgs(token); TokensChanged(this, etcea); } } } } RemoveAll(c => returnCards.Contains(c)); if (_Game.State != GameState.Setup) { if (AsynchronousChanging) { if (AsynchronousPileChangedEventArgs == null) AsynchronousPileChangedEventArgs = new PileChangedEventArgs(null, Operation.Removed, returnCards); else AsynchronousPileChangedEventArgs.RemovedCards.AddRange(returnCards); } else if (PileChanged != null) { var pcea = new PileChangedEventArgs(null, Operation.Removed, returnCards); PileChanged(this, pcea); } } return returnCards; } public Cost CurrentCost { get { var currentCost = _Game.ComputeCost(_CardBase); if (TopCard != null) currentCost = _Game.ComputeCost(TopCard); if (_LastComputedCost == (Cost)null) _LastComputedCost = currentCost; if (_LastComputedCost != currentCost) { var pcea = new PileChangedEventArgs(Operation.Refresh); if (AsynchronousChanging) { AsynchronousPileChangedEventArgs = pcea; } else { PileChanged?.Invoke(this, pcea); } } _LastComputedCost = currentCost; return currentCost; } } public override void Reset() { base.Reset(); if (Tokens.RemoveAll(token => token.IsTemporary) > 0) { if (TokensChanged != null) { var tcea = new TokensChangedEventArgs(null); TokensChanged(this, tcea); } } var pcea = new PileChangedEventArgs(Operation.Refresh); if (AsynchronousChanging) { AsynchronousPileChangedEventArgs = pcea; } else { PileChanged?.Invoke(this, pcea); } } public override string ToString() { var sb = new StringBuilder(); switch (Visibility) { case Visibility.None: case Visibility.All: sb.Append(base.ToString()); break; case Visibility.Top: sb.AppendFormat("{0}: Cost:{1}; {2} Cards", _CardBase, CurrentCost, Count); break; default: return "<>"; } return sb.ToString(); } public bool CanBuy(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); return CanBuy(player, player.Currency); } public bool CanBuy(IPlayer player, Currency currency) { Contract.Requires(player != null, "player cannot be null"); Contract.Requires(currency != default(Currency), "currency cannot be null"); if (CanGain() && !player.TokenPiles[Cards.Empires.TypeClass.DebtToken].Any()) { if (BuyCheck != null) { var bcea = new BuyCheckEventArgs(player); BuyCheck(this, bcea); if (bcea.Cancelled) return false; } return TopCard?.CanBuy(player, currency) == true; } return false; } public bool CanGain() { return TopCard != null && CanGain(TopCard.Type); } public bool CanGain(Type type) { var gainCard = Find(card => card.Type == type); if (gainCard == null) return false; return this.Any() && gainCard.CanGain(); } public int CompareTo(Supply obj) { Contract.Requires(obj != null, "obj cannot be null"); return _CardBase.CompareTo(obj._CardBase); } public int CompareTo(IDisplayable obj) { if (obj is ITableable oTable) return CompareTo(oTable); return -1; } public int CompareTo(ITableable obj) { return _CardBase.CompareTo(obj); } public string Name => _CardBase.Name; public string ImageName => _CardBase.ImageName; public void FullSetup() { Setup(); SnapshotSetup(); FinalizeSetup(); } public void Setup() { // Set up the pile _CardBase?.SetupSupply(_Game, this); // Set up each individual card var visibility = Visibility; Visibility = Visibility.All; foreach (var displayable in this) ((Card)displayable).SetupCard(_Game); Visibility = visibility; } public void SnapshotSetup() { if (StartingStackSize > 0) throw new Exception("Cannot call this method more than once!"); var originalVisibility = Visibility; Visibility = Visibility.All; foreach (var displayable in this) _Types.Add(((Card)displayable).Type); Visibility = originalVisibility; StartingStackSize = Count; } public void FinalizeSetup() { _CardBase?.Finalize(_Game, this); } public XmlNode GenerateXml(XmlDocument doc) { Contract.Requires(doc != null, "xnSupply cannot be null"); var xeSupply = doc.CreateElement("supply"); var xe = doc.CreateElement("type"); xe.InnerText = _CardClassType.ToString(); xeSupply.AppendChild(xe); xe = doc.CreateElement("starting_stack_size"); xe.InnerText = StartingStackSize.ToString(CultureInfo.InvariantCulture); xeSupply.AppendChild(xe); xe = doc.CreateElement("visibility"); xe.InnerText = Visibility.ToString(); xeSupply.AppendChild(xe); xeSupply.AppendChild(LookThrough(c => true).GenerateXml(doc, "cards")); var xeTypes = doc.CreateElement("types"); xeSupply.AppendChild(xeTypes); foreach (var type in _Types) { var xeType = doc.CreateElement("type"); xeType.InnerText = type.ToString(); xeTypes.AppendChild(xeType); } xeSupply.AppendChild(Tokens.GenerateXml(doc, "tokens")); return xeSupply; } public static Supply Load(IGame game, XmlNode xnSupply) { Contract.Requires(xnSupply != null, "xnSupply cannot be null"); var xnType = xnSupply.SelectSingleNode("type"); if (xnType == null) return null; var type = Type.GetType(xnType.InnerText); //Visibility visibility = Visibility.Top; //XmlNode xnVisibility = xnSupply.SelectSingleNode("visibility"); //if (xnVisibility != null) // visibility = (Visibility)Enum.Parse(typeof(Visibility), xnVisibility.InnerText, true); var supply = new Supply(game, null, type); supply.Load(xnSupply); return supply; } public void Load(XmlNode xnSupply) { Contract.Requires(xnSupply != null, "xnSupply cannot be null"); // This needs to be done in reverse order, as AddTo adds cards one at a time on top of the pile instead of the bottom foreach (var xnCard in xnSupply.SelectNodes("cards/card").Cast().Reverse()) { var type = Type.GetType(xnCard.Attributes["type"].Value); AddTo(Card.CreateInstance(type)); } var xnSSS = xnSupply.SelectSingleNode("starting_stack_size"); if (xnSSS != null) StartingStackSize = int.Parse(xnSSS.InnerText, CultureInfo.InvariantCulture); foreach (XmlNode xnType in xnSupply.SelectNodes("types/type")) { var type = Type.GetType(xnType.InnerText); _Types.Add(type); } Tokens.AddRange(TokenCollection.Load(xnSupply.SelectSingleNode("tokens"))); } } }