public Boolean AddAction(Type sourceCard, Type card, CardsDiscardAction action)
        {
            Tuple <Type, Type> key = new Tuple <Type, Type>(sourceCard, card);

            if (this.Actions.ContainsKey(key))
            {
                return(false);
            }
            this.Actions[key] = action;
            return(true);
        }
		public void Discard(DeckLocation fromLocation, IEnumerable<Card> cards, CardsDiscardAction discardAction)
		{
			Discard(fromLocation, c => cards.Contains(c), discardAction);
		}
		private void PerformCleanup()
		{
			// Sets up card movements to indicate where each card should go.
			CardMovementCollection cardsToMove = new CardMovementCollection(this.SetAside, c => c.CanCleanUp, DeckLocation.SetAside, DeckLocation.Discard);
			cardsToMove.AddRange(this.Hand, DeckLocation.Hand, DeckLocation.Discard);
			cardsToMove.AddRange(this.InPlay, DeckLocation.InPlay, DeckLocation.Discard);

			foreach (Card durationCard in this.InPlay.Where(c => !c.CanCleanUp))
				cardsToMove[durationCard].Destination = DeckLocation.SetAside;
			IEnumerable<CardMovement> inPlayCards = cardsToMove.Where(cm => cm.CurrentLocation == DeckLocation.InPlay);

			ParallelQuery<CardMovement> pqCardsToMove = cardsToMove.AsParallel().Where(cm =>
				cm.CurrentLocation == DeckLocation.InPlay &&
				cm.Destination == DeckLocation.SetAside &&
				cm.Card.ModifiedBy != null &&
				cardsToMove.Contains(cm.Card.ModifiedBy.PhysicalCard) &&
				cardsToMove[cm.Card.ModifiedBy.PhysicalCard].Destination == DeckLocation.Discard);

			pqCardsToMove.ForAll(cm => cardsToMove[cm.Card.ModifiedBy.PhysicalCard].Destination = DeckLocation.SetAside);

			int drawSize = 5;
			if (CleaningUp != null)
			{
				Boolean cancelled = false;
				// Possibly changing events that can happen in the game
				CleaningUpEventArgs cuea = null;

				do
				{
					cuea = new CleaningUpEventArgs(this, 5, ref cardsToMove);
					cuea.Cancelled |= cancelled;
					CleaningUp(this, cuea);

					OptionCollection options = new OptionCollection();
					IEnumerable<Type> cardTypes = cuea.Actions.Keys;
					foreach (Type key in cardTypes)
						options.Add(new Option(cuea.Actions[key].Text, false));
					if (options.Count > 0)
					{
						options.Sort();
						Choice choice = new Choice("Performing Clean-up", options, this, cuea);
						ChoiceResult result = this.MakeChoice(choice);

						if (result.Options.Count > 0)
							cuea.Actions.First(kvp => kvp.Value.Text == result.Options[0]).Value.Method(this, ref cuea);
						else
							break;
					}
					else
						break;

					cancelled |= cuea.Cancelled;
				} while (CleaningUp != null);

				if (cuea != null)
					cancelled |= cuea.Cancelled;

				if (cancelled)
					return;

				if (cuea.NextPlayer != null)
					_CurrentTurn.NextPlayer = cuea.NextPlayer;
				_CurrentTurn.NextGrantedBy = cuea.NextGrantedBy;
				drawSize = cuea.DrawSize;
			}

			// Discard any Revealed cards (should be none?)
			this.DiscardRevealed();

			CardsDiscardAction cdaHand = null;
			if (cardsToMove.Count(c => c.CurrentLocation == DeckLocation.Hand) > 0)
				cdaHand = new CardsDiscardAction(this, null, "Discard hand", player_DiscardHand, true) { Data = cardsToMove };

			// Discard non-Duration (or Duration-modifying) cards in In Play & Set Aside at the same time
			this.Discard(DeckLocation.InPlayAndSetAside, cardsToMove.Where(cm =>
				(cm.CurrentLocation == DeckLocation.InPlay || cm.CurrentLocation == DeckLocation.SetAside) &&
				cm.Destination == DeckLocation.Discard).Select<CardMovement, Card>(cm => cm.Card), cdaHand);

			// Discard Hand
			this.AddCardsInto(DeckLocation.Discard,
				this.RetrieveCardsFrom(DeckLocation.Hand, c =>
					cardsToMove[c].CurrentLocation == DeckLocation.Hand && cardsToMove[c].Destination == DeckLocation.Discard));

			// Move Duration (and Duration-modifying) cards from In Play into Set Aside
			this.MoveInPlayToSetAside(c => cardsToMove.Contains(c) && cardsToMove[c].CurrentLocation == DeckLocation.InPlay && cardsToMove[c].Destination == DeckLocation.SetAside);

			// Move any cards that have had their Destination changed to their appropriate locations
			IEnumerable<Card> replaceCards = cardsToMove.Where(cm => cm.Destination == DeckLocation.Deck).Select(cm => cm.Card);
			if (replaceCards.Count() > 0)
			{
				Choice replaceChoice = new Choice("Choose order of cards to put back on your deck", null, replaceCards, this, true, replaceCards.Count(), replaceCards.Count());
				ChoiceResult replaceResult = this.MakeChoice(replaceChoice);
				this.RetrieveCardsFrom(DeckLocation.InPlay, c => cardsToMove[c].CurrentLocation == DeckLocation.InPlay && replaceResult.Cards.Contains(c));
				this.RetrieveCardsFrom(DeckLocation.SetAside, c => cardsToMove[c].CurrentLocation == DeckLocation.SetAside && replaceResult.Cards.Contains(c));
				this.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top);
			}

#if DEBUG
			if (this.InPlay.Count > 0)
				throw new Exception("Something happened -- there are cards left in the player's In Play area!");
#endif

			if (CurrentTurn != null)
				CurrentTurn.Finished();

			_Actions = _Buys = 0;
			_Currency.Coin.Value = 0;
			_Currency.Potion.Value = 0;
			_ActionsPlayed = 0;

#if DEBUG
			// Check to see that there are no duplicate cards anywhere
			CardCollection allCards = new CardCollection();
			allCards.AddRange(this.Hand);
			allCards.AddRange(this.Revealed);
			allCards.AddRange(this.Private);
			allCards.AddRange(this.InPlay);
			allCards.AddRange(this.SetAside);
			allCards.AddRange(this.DrawPile.LookThrough(c => true));
			allCards.AddRange(this.DiscardPile.LookThrough(c => true));
			foreach (CardMat mat in this.PlayerMats.Values)
				allCards.AddRange(mat);

			ParallelQuery<Card> duplicateCards = allCards.AsParallel().Where(c => allCards.Count(ct => ct == c) > 1);

			//IEnumerable<Card> duplicateCards = allCards.FindAll(c => allCards.Count(ct => ct == c) > 1);
			if (duplicateCards.Count() > 0)
			{
				// Ruh Roh
				throw new Exception("Duplicate cards found!  Something went wrong!");
			}
#endif

			DrawHand(drawSize);
			if (CleanedUp != null)
			{
				CleanedUp(this, new CleanedUpEventArgs(this, drawSize));
			}
			if (TurnEnded != null)
			{
				TurnEnded(this, new TurnEndedEventArgs(this));
			}
		}
		public void Discard(DeckLocation fromLocation, Predicate<Card> match, int count, CardsDiscardAction discardAction)
		{
			CardCollection matchingCards = this.ResolveDeck(fromLocation)[match];
			if (count >= 0 && count < matchingCards.Count)
			{
				matchingCards.RemoveRange(count, matchingCards.Count - count);
				if (matchingCards.Count != count)
					throw new Exception("Incorrect number of cards found!");
			}
			if (matchingCards.Count == 0)
				return;

			if (CardsDiscarding != null)
			{
				CardsDiscardEventArgs cdea = null;
				List<Object> handledBy = new List<Object>();
				Boolean actionPerformed = false;
				Boolean cancelled = false;
				do
				{
					actionPerformed = false;

					cdea = new CardsDiscardEventArgs(fromLocation, matchingCards);
					cdea.Cancelled = cancelled;
					cdea.HandledBy.AddRange(handledBy);
					CardsDiscarding(this, cdea);

					handledBy = cdea.HandledBy;
					matchingCards = cdea.Cards;
					cancelled |= cdea.Cancelled;

					OptionCollection options = new OptionCollection();
					IEnumerable<Tuple<Type, Type>> cardTypes = cdea.Actions.Keys;
					foreach (Tuple<Type, Type> key in cardTypes)
						options.Add(new Option(cdea.Actions[key].Text, cdea.Actions[key].IsRequired));

					if (options.Count > 0)
					{
						if (discardAction != null && !cdea.HandledBy.Contains(this))
						{
							cdea.AddAction(this.GetType(), discardAction);
							options.Add(new Option(discardAction.Text, true));
						}

						options.Sort();
						Choice choice = new Choice(String.Format("You are discarding {0}", Utilities.StringUtility.Plural("card", matchingCards.Count)), options, this, cdea);
						ChoiceResult result = this.MakeChoice(choice);

						if (result.Options.Count > 0)
						{
							CardsDiscardAction action = cdea.Actions.First(kvp => kvp.Value.Text == result.Options[0]).Value;
							cdea.Data = action.Data;
							action.Method(this, ref cdea);
							actionPerformed = true;
							handledBy = cdea.HandledBy;
							matchingCards = cdea.Cards;
							cancelled |= cdea.Cancelled;
						}
					}

				} while (CardsDiscarding != null && actionPerformed);

				if (cancelled)
					return;
			}

			this.RetrieveCardsFrom(fromLocation, matchingCards);
			if (CardsDiscard != null)
			{
				CardsDiscardEventArgs cdea = null;
				List<Object> handledBy = new List<Object>();

				cdea = new CardsDiscardEventArgs(fromLocation, matchingCards);
				cdea.HandledBy.AddRange(handledBy);
				CardsDiscard(this, cdea);

				handledBy = cdea.HandledBy;
				matchingCards = cdea.Cards;
			}
			this.AddCardsInto(DeckLocation.Discard, matchingCards);

			if (CardsDiscarded != null)
			{
				CardsDiscardEventArgs cdea = null;
				List<Object> handledBy = new List<Object>();
				Boolean actionPerformed = false;
				do
				{
					actionPerformed = false;

					cdea = new CardsDiscardEventArgs(fromLocation, matchingCards);
					cdea.HandledBy.AddRange(handledBy);
					CardsDiscarded(this, cdea);
					handledBy = cdea.HandledBy;

					OptionCollection options = new OptionCollection();
					IEnumerable<Tuple<Type, Type>> cardTypes = cdea.Actions.Keys;
					foreach (Tuple<Type, Type> key in cardTypes)
						options.Add(new Option(cdea.Actions[key].Text, false));

					if (options.Count > 0)
					{
						options.Sort();
						Choice choice = new Choice(String.Format("You discarded {0}", Utilities.StringUtility.Plural("card", matchingCards.Count)), options, this, cdea);
						ChoiceResult result = this.MakeChoice(choice);

						if (result.Options.Count > 0)
						{
							cdea.Actions.First(kvp => kvp.Value.Text == result.Options[0]).Value.Method(this, ref cdea);
							actionPerformed = true;
						}
					}

				} while (CardsDiscarded != null && actionPerformed);
			}
		}
		public void Discard(DeckLocation fromLocation, Predicate<Card> match, CardsDiscardAction discardAction)
		{
			Discard(fromLocation, match, -1, discardAction);
		}
		public Boolean AddAction(Type sourceCard, Type card, CardsDiscardAction action)
		{
			Tuple<Type, Type> key = new Tuple<Type,Type>(sourceCard, card);
			if (this.Actions.ContainsKey(key))
				return false;
			this.Actions[key] = action;
			return true;
		}
		public Boolean AddAction(Type card, CardsDiscardAction action)
		{
			return AddAction(card, card, action);
		}
 public Boolean AddAction(Type card, CardsDiscardAction action)
 {
     return(AddAction(card, card, action));
 }