using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime; using System.Text; using System.Xml.Serialization; namespace DominionBase.Cards { [Serializable] public enum ConstraintType { [DescriptionAttribute("Select constraint")] [ToolTipAttribute("Blank constraint that doesn't do anything")] Unknown, [DescriptionAttribute("Must use Card")] [ToolTipAttribute("The card listed must be used")] CardMustUse, [DescriptionAttribute("Cannot use Card")] [ToolTipAttribute("The card listed cannot be used")] CardDontUse, [DescriptionAttribute("Card is in Set")] [ToolTipAttribute("The card was released in the Set listed")] SetIs, [DescriptionAttribute("Card Type is")] [ToolTipAttribute("The card's Type is only the listed Type (no multi-Type)")] CategoryIs, [DescriptionAttribute("Card Type has")] [ToolTipAttribute("The card's Type has listed Type in its Types")] CategoryContains, [DescriptionAttribute("Card costs")] [ToolTipAttribute("The card costs exactly the listed amount")] CardCosts, [DescriptionAttribute("Card cost contains Potion")] [ToolTipAttribute("The card cost has Potion in it")] CardCostContainsPotion, [DescriptionAttribute("Card is in Group")] [ToolTipAttribute("The card is a member of the Group listed")] MemberOfGroup, } public class ToolTipAttribute : Attribute { private String _ToolTip; /// Summary: /// Specifies the default value for the DominionBase.TooltipAttribute, /// which is an empty string (""). This static field is read-only. public static readonly ToolTipAttribute Default; /// Summary: /// Initializes a new instance of the DominionBase.TooltipAttribute /// class with no parameters. public ToolTipAttribute() : this(String.Empty) { } /// Summary: /// Initializes a new instance of the DominionBase.TooltipAttribute /// class with a tooltip. /// /// Parameters: /// tooltip: /// The tooltip text. [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public ToolTipAttribute(string tooltip) { this._ToolTip = tooltip; } /// Summary: /// Gets the tooltip stored in this attribute. /// /// Returns: /// The tooltip stored in this attribute. public virtual string ToolTip { get { return _ToolTip; } } /// Summary: /// Gets or sets the string stored as the tooltip. /// /// Returns: /// The string stored as the tooltip. The default value is an empty string /// (""). protected string ToolTipValue { get { return _ToolTip; } set { _ToolTip = value; } } /// Summary: /// Returns whether the value of the given object is equal to the current DominionBase.TooltipAttribute. /// /// Parameters: /// obj: /// The object to test the value equality of. /// /// Returns: /// true if the value of the given object is equal to that of the current; otherwise, /// false. public override bool Equals(object obj) { if (obj == this) { return true; } ToolTipAttribute tooltipAttribute = obj as ToolTipAttribute; return tooltipAttribute != null && tooltipAttribute.ToolTip == this.ToolTip; } public override int GetHashCode() { return this.ToolTip.GetHashCode(); } /// Summary: /// Returns a value indicating whether this is the default DominionBase.TooltipAttribute /// instance. /// /// Returns: /// true, if this is the default DominionBase.TooltipAttribute instance; /// otherwise, false. public override bool IsDefaultAttribute() { return false; } } public class ConstraintException : Exception { public ConstraintException() { } public ConstraintException(string message) : base(message) { } public ConstraintException(string message, Exception innerException) : base(message, innerException) { } internal ConstraintException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } [Serializable] public class Constraint { private ConstraintType _ConstraintType = ConstraintType.Unknown; private Object _ConstraintValue = null; private int _Minimum = 0; private int _Maximum = 10; public int Minimum { get { return _Minimum; } set { this._Minimum = value; if (this._Minimum < 0) this._Minimum = 0; if (this.Maximum < this._Minimum) this.Maximum = this._Minimum; } } public int Maximum { get { return _Maximum; } set { this._Maximum = value; if (this._Maximum > this.RangeMax) this._Maximum = this.RangeMax; if (this.Minimum > this._Maximum) this.Minimum = this._Maximum; } } public ConstraintType ConstraintType { get { return _ConstraintType; } set { if (_ConstraintType == value) return; Boolean resetValue = false; if ((_ConstraintType == Cards.ConstraintType.CardMustUse && value != Cards.ConstraintType.CardDontUse) || (_ConstraintType == Cards.ConstraintType.CardDontUse && value != Cards.ConstraintType.CardMustUse) || (_ConstraintType == Cards.ConstraintType.CategoryIs && value != Cards.ConstraintType.CategoryContains) || (_ConstraintType == Cards.ConstraintType.CategoryContains && value != Cards.ConstraintType.CategoryIs)) resetValue = true; _ConstraintType = value; if (resetValue) this.ConstraintValue = null; switch (value) { case Cards.ConstraintType.CardDontUse: this.Minimum = this.Maximum = 0; break; case Cards.ConstraintType.CardMustUse: this.Minimum = this.Maximum = 1; break; } } } public Object ConstraintValue { get { return _ConstraintValue; } set { switch (this.ConstraintType) { case Cards.ConstraintType.CategoryIs: case Cards.ConstraintType.CategoryContains: if (value != null) _ConstraintValue = (Category)value; else _ConstraintValue = Category.Action; break; case Cards.ConstraintType.SetIs: if (value != null) _ConstraintValue = (Source)value; else _ConstraintValue = Source.Base; break; default: _ConstraintValue = value; break; } } } private int _RangeMax = 10; [XmlIgnore] public int RangeMax { get { return _RangeMax; } set { _RangeMax = value; if (this._RangeMax < this._Maximum) this.Maximum = this._RangeMax; } } public Constraint() : this(DominionBase.Cards.ConstraintType.Unknown, null, 0, 10) { } public Constraint(ConstraintType constraintType, String cardName) { this.ConstraintType = constraintType; switch (constraintType) { case Cards.ConstraintType.CardDontUse: this.ConstraintValue = cardName; break; case Cards.ConstraintType.CardMustUse: this.ConstraintValue = cardName; break; default: throw new ArgumentException("Specified ConstraintType not allowed for this constructor!"); } } public Constraint(ConstraintType constraintType, object constraintValue, int minimum, int maximum) { this.ConstraintType = constraintType; this.ConstraintValue = constraintValue; this.Minimum = minimum; this.Maximum = maximum; } internal Boolean Matches(Card card) { return this.PredicateFunction(card); } internal Boolean MinimumMet(IEnumerable chosenCards) { if (chosenCards.Count(this.PredicateFunction) >= this.Minimum) return true; return false; } internal Boolean IsChoosable(IEnumerable chosenCards, Card card) { if (chosenCards.Count(this.PredicateFunction) < this.Maximum) return true; return false; } public IEnumerable GetMatchingCards(IEnumerable _CardsAvailable) { IEnumerable cards = _CardsAvailable.Where(this.PredicateFunction); return cards; } private Func PredicateFunction { get { switch (this.ConstraintType) { case Cards.ConstraintType.CardMustUse: case Cards.ConstraintType.CardDontUse: if (this.ConstraintValue.GetType() == typeof(Card)) return card => ((Card)this.ConstraintValue).CardType == card.CardType; else if (this.ConstraintValue.GetType() == typeof(String)) return card => (String)this.ConstraintValue == card.Name; return card => true; case Cards.ConstraintType.SetIs: return card => card.Source == (Source)this.ConstraintValue; case Cards.ConstraintType.CategoryIs: return card => card.Category == (Category)this.ConstraintValue; case Cards.ConstraintType.CategoryContains: return card => (card.Category & ((Category)this.ConstraintValue)) == (Category)this.ConstraintValue; case Cards.ConstraintType.CardCosts: return card => card.BaseCost == (Cost)this.ConstraintValue; case Cards.ConstraintType.CardCostContainsPotion: return card => card.BaseCost.Potion > 0; case Cards.ConstraintType.MemberOfGroup: return card => (card.GroupMembership & (Group)this.ConstraintValue) == (Group)this.ConstraintValue; default: return card => true; } } } } [Serializable] public class ConstraintCollection : List, INotifyCollectionChanged { [field:NonSerialized] public event NotifyCollectionChangedEventHandler CollectionChanged; private int _MaxCount = 10; public int MaxCount { get { return _MaxCount; } set { _MaxCount = value; foreach (Constraint constraint in this) constraint.RangeMax = value; } } public ConstraintCollection() { } public ConstraintCollection(IEnumerable collection) : base(collection) { } public Boolean IsChoosable(IEnumerable cardCollection, Card card) { foreach (Constraint constraint in this) { if (constraint.Matches(card) && !constraint.IsChoosable(cardCollection, card)) return false; } return true; } public new void Add(Constraint item) { item.RangeMax = this.MaxCount; base.Add(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } public new void AddRange(IEnumerable collection) { foreach (Constraint item in collection) item.RangeMax = this.MaxCount; base.AddRange(collection); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection)); } public new void Insert(int index, Constraint item) { item.RangeMax = this.MaxCount; base.Insert(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } public new void Remove(Constraint item) { base.Remove(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); } public new void Sort() { this.Sort(delegate(Constraint c1, Constraint c2) { return -c1.Minimum.CompareTo(c2.Minimum); }); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } internal bool MinimumMet(List cardCollection) { foreach (Constraint constraint in this) { if (!constraint.MinimumMet(cardCollection)) return false; } return true; } protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) CollectionChanged(this, e); } public IList SelectCards(IList availableCards, int numberCardsToSelect) { List _CardsChosen = new List(); // Remove all "CardDontUse" constraint cards first IList usableCards = new List(availableCards); foreach (Cards.Constraint constraint in this.Where(c => c.ConstraintType == Cards.ConstraintType.CardDontUse)) foreach (Cards.Card card in constraint.GetMatchingCards(availableCards)) usableCards.Remove(card); IEnumerable usableConstraints = this.Where(c => c.ConstraintType != Cards.ConstraintType.CardDontUse); Dictionary> constraintCards = new Dictionary>(); foreach (Cards.Constraint constraint in usableConstraints) constraintCards[constraint] = new Cards.CardCollection(constraint.GetMatchingCards(availableCards)); int attempts = 0; // Satisfy Minimum constraints first do { attempts++; _CardsChosen.Clear(); // Add in required cards first foreach (Cards.Constraint constraint in usableConstraints.Where(c => c.ConstraintType == Cards.ConstraintType.CardMustUse)) _CardsChosen.AddRange(constraintCards[constraint]); if (_CardsChosen.Count > numberCardsToSelect) throw new ConstraintException(String.Format("Too many required cards specified in constraints! Please double-check your setup and loosen the requirements. {0} needed & found {1} required constraints", numberCardsToSelect, _CardsChosen.Count)); foreach (Cards.Constraint constraint in usableConstraints.OrderByDescending(c => c.Minimum)) { if (constraint.MinimumMet(_CardsChosen)) continue; Utilities.Shuffler.Shuffle(constraintCards[constraint]); foreach (Cards.Card card in constraintCards[constraint]) { if (this.IsChoosable(_CardsChosen, card)) { _CardsChosen.Add(card); if (constraint.MinimumMet(_CardsChosen)) break; } } } // Give it 50 attempts at trying to satisfy the Minimum requirements if (attempts > 50) throw new ConstraintException("Cannot satisfy specified constraints! Please double-check and make sure it's possible to construct a Kingdom card setup with the constraints specified"); } while (!this.MinimumMet(_CardsChosen) || _CardsChosen.Count > numberCardsToSelect); // After satisfying the Minimums, Maximums should be pretty easy to handle List _CardsChosenNeeded = new List(_CardsChosen); attempts = 0; while (_CardsChosen.Count < numberCardsToSelect) { attempts++; // Give it 50 attempts at trying to satisfy the Minimum requirements if (attempts > 50) throw new ConstraintException("Cannot satisfy specified constraints! Please double-check and make sure it's possible to construct a Kingdom card setup with the constraints specified"); _CardsChosen.Clear(); _CardsChosen.AddRange(_CardsChosenNeeded); Utilities.Shuffler.Shuffle(usableCards); foreach (Cards.Card chosenCard in usableCards) { if (_CardsChosen.Contains(chosenCard)) continue; if (this.IsChoosable(_CardsChosen, chosenCard)) _CardsChosen.Add(chosenCard); if (_CardsChosen.Count == numberCardsToSelect) break; } } return _CardsChosen; } } }