using DominionBase; using DominionBase.Cards; using DominionBase.Enums; using DominionBase.Piles; using DominionBase.Players; using DominionBase.Utilities; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Windows; using System.Windows.Controls; using CtrlOrientation = System.Windows.Controls.Orientation; namespace Dominion.NET_WPF { /// /// Interaction logic for CardMatControl.xaml /// public partial class CardMatControl : UserControl { public static readonly DependencyProperty SplitAtProperty = DependencyProperty.Register("SplitAt", typeof(Func), typeof(CardMatControl), new PropertyMetadata(null)); public Func SplitAt { get { return (Func)GetValue(SplitAtProperty); } set { SetValue(SplitAtProperty, value); UpdateCardDisplay(); } } public static readonly DependencyProperty MinStackWidthProperty = DependencyProperty.Register("MinStackWidth", typeof(double), typeof(CardMatControl), new PropertyMetadata(0d)); public double MinStackWidth { get { return (double)GetValue(MinStackWidthProperty); } set { SetValue(MinStackWidthProperty, value); UpdateCardDisplay(); } } private CardSize _cardSize = CardSize.Text; private CardMat _cardPile = null; private Dictionary _tokenDict; private const int GlowSize = 20; private string _pileName = "Pile Name"; private bool _displayEmptyPile; private IPlayer _player; private PhaseEnum _playerPhase = PhaseEnum.Action; private PlayerMode _playerMode = PlayerMode.Waiting; private bool _isClickable; public CardMatControl() { InitializeComponent(); CardSize = CardSize.Medium; IsClickable = false; var gs = GlowSize / 2d; wpCardCollections.Margin = new Thickness(gs, gs, gs, 0); wpCardCollections2.Margin = new Thickness(gs, gs, gs, 0); } public IDisplayable ClickedCard { get; } = null; public CtrlOrientation Orientation { get; set; } = CtrlOrientation.Horizontal; public HorizontalAlignment CardHorizontalAlignment { get; set; } = HorizontalAlignment.Left; public bool ExactCount { get; set; } public bool IsCardsVisible { get; set; } public bool IsClickable { get { return _isClickable; } set { _isClickable = value; foreach (var csc in wpCardCollections.Children.OfType()) csc.IsClickable = value; foreach (var csc in wpCardCollections2.Children.OfType()) csc.IsClickable = value; } } public bool IsDisplaySorted { get; set; } public bool IsVPsVisible { get; set; } public bool DisplayEmptyPile { get { return _displayEmptyPile; } set { _displayEmptyPile = value; if (CardMat == null || PileCount == 0) UpdateCardDisplay(); } } public CardSize CardSize { get { return _cardSize; } set { _cardSize = value; UpdateCardDisplay(); } } public CardMat CardMat { get { return _cardPile; } set { // Unsubscribe from the old one if necessary if (_cardPile != null) _cardPile.CardLabels.CardLabelsChanged -= CardLabels_CardLabelsChanged; _cardPile = value; // Subscribe to the new one if necessary if (_cardPile != null) _cardPile.CardLabels.CardLabelsChanged += CardLabels_CardLabelsChanged; UpdateCardDisplay(); } } public Dictionary TokenDict { private get { return _tokenDict; } set { _tokenDict = value; UpdateCardDisplay(); } } public DominionBase.Piles.Visibility PileVisibility => CardMat?.Visibility ?? DominionBase.Piles.Visibility.All; private int PileCount => CardMat?.Count ?? 0; public string PileName { get { return _pileName; } set { _pileName = value; UpdateCardDisplay(); } } private void CardLabels_CardLabelsChanged(object sender, CardLabelsChangedEventArgs e) { if (Dispatcher.CheckAccess()) { // Might need to do more than this; for now, let's see if it triggers correctly UpdateCardDisplay(); } else { Dispatcher.BeginInvoke(new EventHandler(CardLabels_CardLabelsChanged), System.Windows.Threading.DispatcherPriority.Normal, sender, e); } } public void UpdateCardDisplay() { wpCardCollections.Children.Clear(); wpCardCollections2.Children.Clear(); wpToolTipCards.Children.Clear(); nPileName.Content = PileName; if (CardSize == CardSize.Full && CardMat != null && PileCount > 0) { foreach (var card in CardMat) { var ttd = new ToolTipIDisplayable { Card = card, Margin = new Thickness(3) }; wpToolTipCards.Children.Add(ttd); } } else { nPileName.Visibility = string.IsNullOrEmpty(PileName) ? System.Windows.Visibility.Collapsed : System.Windows.Visibility.Visible; if (ExactCount && CardMat != null && PileCount > 0) { lCount.Visibility = System.Windows.Visibility.Visible; lCount.Content = $"({StringUtility.Plural("Card", PileCount, true)})"; } else { lCount.Visibility = System.Windows.Visibility.Collapsed; } var currentWrapPanel = wpCardCollections; foreach (var cardStack in GenerateCardStacks()) { var csc = new Controls.CardStackControl { MinWidth = MinStackWidth, Foreground = Foreground, CardSize = CardSize, ExactCount = ExactCount, IsCardsVisible = IsCardsVisible, Player = Player, Phase = Phase, PlayerMode = PlayerMode, PileVisibility = PileVisibility, CardCollection = cardStack, CardLabels = new CardLabelCollection(CardMat?.CardLabels.Where(cl => cl.AttachedCard == cardStack[0])) }; if (TokenDict != null && TokenDict.ContainsKey(cardStack[0])) csc.Tokens = TokenDict[cardStack[0]]; //if (TokenDict != null && TokenDict.ContainsKey(cardStack[0])) // csc.Tokens = TokenDict[cardStack[0]]; if (IsVPsVisible && cardStack[0].HasVPs) csc.CountVPs(CardMat); else csc.HideVPs(); if (SplitAt != null && csc.CardCollection.Any() && SplitAt(csc.CardCollection.First())) currentWrapPanel = wpCardCollections2; currentWrapPanel.Children.Add(csc); } } InvalidateVisual(); } public IPlayer Player { private get { return _player; } set { _player = value; foreach (var csc in wpCardCollections.Children.OfType()) csc.Player = value; } } public PhaseEnum Phase { private get { return _playerPhase; } set { _playerPhase = value; foreach (var csc in wpCardCollections.Children.OfType()) csc.Phase = value; } } public PlayerMode PlayerMode { private get { return _playerMode; } set { _playerMode = value; foreach (var csc in wpCardCollections.Children.OfType()) csc.PlayerMode = value; } } private IEnumerable GenerateCardStacks() { var builtCardStacks = new OrderedDictionary(); var cardStacks = new List(); if (CardMat == null || PileCount == 0) { if (DisplayEmptyPile) cardStacks.Add(new DisplayableCollection() { new DominionBase.Cards.Universal.Blank() }); return cardStacks; } int count; switch (PileVisibility) { case DominionBase.Piles.Visibility.All: IEnumerable copy = new DisplayableCollection(CardMat); if (IsDisplaySorted) { copy = copy.OrderBy(c => c.Facing).ThenBy(c => CardMat.CardLabels.Any(cl => cl.AttachedCard == c)).ThenBy(c => c.Name).ThenByDescending(c => (c as IPoints)?.ComputeVictoryPoints(Player, CardMat.OfType()) ?? 0); } foreach (var card in copy) { var key1 = IsCardsVisible ? ((card.IsStackable && card.Facing == Facing.FaceUp && !CardMat.CardLabels.Any(cl => cl.AttachedCard == card)) // The card must be stackable, be face up, and not have any labels attached to it ? card.Type.ToString() // Otherwise, it gets its own stack : card.UniqueId.ToString()) // If the cards aren't visible, they all get the same key : string.Empty; // Stacks can be different if the vps for cards with the same name are different (e.g. Distant Lands) var vps = IsCardsVisible && card.IsStackable && card is IPoints cp ? cp.ComputeVictoryPoints(Player, CardMat.OfType()) : 0; var key = new Tuple(key1, vps); if (!builtCardStacks.Contains(key)) builtCardStacks[key] = new DisplayableCollection(); ((DisplayableCollection)builtCardStacks[key]).AddRange(card.Stack()); } cardStacks.AddRange(builtCardStacks.Values.OfType()); break; case DominionBase.Piles.Visibility.Top: if (PileCount > 0) { cardStacks.Add(new DisplayableCollection()); count = PileCount - 1; if (!ExactCount && count > 1) { var variance = Gaussian.NextGaussian(); count += (int)(variance * (count / 4.0)); } for (var index = 0; index < count; index++) cardStacks[0].Add(DominionBase.Cards.Universal.Utility.GenerateCardBack(CardBack.Standard)); if (CardMat.FirstOrDefault() is Card card) cardStacks[0].Add(card); } break; case DominionBase.Piles.Visibility.None: if (PileCount > 0) { cardStacks.Add(new DisplayableCollection()); count = PileCount; if (!ExactCount && count > 1) { var variance = Gaussian.NextGaussian(); count += (int)(variance * (count / 4.0)); } for (var index = 0; index < count; index++) cardStacks[0].Add(DominionBase.Cards.Universal.Utility.GenerateCardBack(CardBack.Standard)); } break; } return cardStacks; } internal Control FindGameObject(IGameObject gameObject) { return wpCardCollections.Children.OfType().Select(csc => csc.FindGameObject(gameObject)).FirstOrDefault(fe => fe != null); } } }