using DominionBase.Currencies; using DominionBase.Enums; using DominionBase.Piles; using DominionBase.Players; using DominionBase.Properties; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; namespace DominionBase.Cards.Base2ndEdition { public static class TypeClass { public static readonly Type Artisan = typeof(Artisan); public static readonly Type Bandit = typeof(Bandit); public static readonly Type Bureaucrat = typeof(Bureaucrat); public static readonly Type Cellar = typeof(Cellar); public static readonly Type Gardens = typeof(Gardens); public static readonly Type Harbinger = typeof(Harbinger); public static readonly Type Library = typeof(Library); public static readonly Type Merchant = typeof(Merchant); public static readonly Type Militia = typeof(Militia); public static readonly Type Mine = typeof(Mine); public static readonly Type Moat = typeof(Moat); public static readonly Type Moneylender = typeof(Moneylender); public static readonly Type Poacher = typeof(Poacher); public static readonly Type Remodel = typeof(Remodel); public static readonly Type Sentry = typeof(Sentry); public static readonly Type ThroneRoom = typeof(ThroneRoom); public static readonly Type Vassal = typeof(Vassal); public static readonly Type Witch = typeof(Witch); } public class Artisan : Card { public Artisan() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.CardOrdering | Traits.Gainer | Traits.Terminal, edition: Edition.Second) { BaseCost = new Cost(6); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= new Coin(5))); var choice = new Choice(Resource.GainUpTo5ToHand, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this, DeckLocation.Hand); var replaceChoice = new Choice(Resource.TopdeckCard, this, player.Hand, ChoiceOutcome.Select, player); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Hand, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } } public class Bandit : Card { public Bandit() : base(Categories.Action | Categories.Attack, Source.Base, Location.Kingdom, Traits.Gainer | Traits.Trasher | Traits.Discard | Traits.Terminal, edition: Edition.Second) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Gain(player._Game.Table.Gold, this); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; attackee.Draw(2, DeckLocation.Revealed); var treasures = attackee.Revealed[c => c.Category.HasFlag(Categories.Treasure) && !(c is Universal.Copper)]; var choice = new Choice(Resource.TrashNonCopper, this, treasures, ChoiceOutcome.Trash, attackee); var result = attackee.MakeChoice(choice); if (result.Cards.Any()) { var trashCard = attackee.RetrieveCardFrom(DeckLocation.Revealed, result.Cards[0]); attackee.Trash(this, trashCard); } attackee.DiscardRevealed(); } } } public class Bureaucrat : Base.Bureaucrat { public Bureaucrat() : base(Edition.Second) { } } public class Cellar : Base.Cellar { public Cellar() : base(Edition.Second) { } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceDiscard = new Choice(Resource.ChooseDiscards, this, player.Hand, ChoiceOutcome.Discard, player, minimum: 0, maximum: player.Hand.Count); var resultDiscard = player.MakeChoice(choiceDiscard); player.Discard(DeckLocation.Hand, resultDiscard.Cards); player.DrawHand(resultDiscard.Cards.Count); } } public class Gardens : Base.Gardens { public Gardens() : base(Edition.Second) { } } public class Harbinger : Card { public Harbinger() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.PlusAction | Traits.Cantrip, edition: Edition.Second) { BaseCost = new Cost(3); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var discardPileCards = player.DiscardPile.LookThrough(c => true); player._Game.SendMessage(player, this, discardPileCards.ToArray()); var choiceTop = new Choice(Resource.ChooseCardToTopdeck, this, discardPileCards, ChoiceOutcome.Select, player, minimum: 0); var resultTop = player.MakeChoice(choiceTop); if (resultTop.Cards.Any()) player.AddCardsToDeck(player.DiscardPile.Retrieve(player, c => resultTop.Cards.Contains(c)), DeckPosition.Top); } } public class Library : Base.Library { public Library() : base(Edition.Second) { } } public class Merchant : Card { private IPlayer _turnEndedPlayer; private TurnEndedEventHandler _turnEndedEventHandler; private readonly List _cardPlayedEventHandlers = new List(); public Merchant() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Cantrip, edition: Edition.Second) { BaseCost = new Cost(3); Benefit.Cards = 1; Benefit.Actions = 1; } public override void TearDown(IGame game) { base.TearDown(game); if (_turnEndedEventHandler != null && _turnEndedPlayer != null) Player_TurnEnded(_turnEndedPlayer, new TurnEndedEventArgs(_turnEndedPlayer)); _turnEndedEventHandler = null; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (_turnEndedEventHandler == null) { _turnEndedPlayer = player; _turnEndedEventHandler = new TurnEndedEventHandler(Player_TurnEnded); player.TurnEnded += _turnEndedEventHandler; } _cardPlayedEventHandlers.Add(new CardPlayedEventHandler(ActivePlayer_CardPlayed)); player.CardPlayed += _cardPlayedEventHandlers[_cardPlayedEventHandlers.Count - 1]; } private void ActivePlayer_CardPlayed(object sender, CardPlayedEventArgs e) { var benefit = new CardBenefit(); foreach (var card in e.Cards) { if (card is Universal.Silver) { benefit.Currency += new Coin(1); // Need to set the played Silver to not be able to Undo. //card.CanUndo = false; foreach (var cpeh in _cardPlayedEventHandlers) e.Player.CardPlayed -= cpeh; _cardPlayedEventHandlers.Clear(); // Only works on the first Silver break; } } if (benefit.Any) e.Player.ReceiveBenefit(this, benefit); } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { var player = sender as Player; if (_turnEndedEventHandler != null && _turnEndedPlayer != null) _turnEndedPlayer.TurnEnded -= _turnEndedEventHandler; _turnEndedPlayer = null; _turnEndedEventHandler = null; foreach (var cpeh in _cardPlayedEventHandlers) player.CardPlayed -= cpeh; _cardPlayedEventHandlers.Clear(); } } public class Militia : Base.Militia { public Militia() : base(Edition.Second) { } } public class Mine : Card { public Mine() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.Gainer | Traits.Trasher | Traits.Terminal, edition: Edition.Second) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, player.Hand[Categories.Treasure], ChoiceOutcome.Trash, player, minimum: 0, maximum: 1); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); if (resultTrash.Cards.Any()) { var trashedCardCost = player._Game.ComputeCost(resultTrash.Cards[0]); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.TopCard.Category.HasFlag(Categories.Treasure) && supply.CurrentCost <= (trashedCardCost + new Coin(3)))); var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this, DeckLocation.Hand); } } } public class Moat : Base.Moat { public Moat() : base(Edition.Second) { } } public class Moneylender : Card { public Moneylender() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.DeckReduction | Traits.PlusCoin | Traits.Trasher | Traits.Terminal | Traits.ConditionalBenefit, edition: Edition.Second) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand[Universal.TypeClass.Copper].Count > 0) { var choice = Choice.CreateYesNoChoice(Resource.MayTrashCopper, this, this, player, null); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { var singleCopper = player.RetrieveCardFrom(DeckLocation.Hand, Universal.TypeClass.Copper); player.Trash(this, singleCopper); var benefit = new CardBenefit(); benefit.Currency += new Coin(3); player.ReceiveBenefit(this, benefit); } } } } public class Poacher : Card { public Poacher() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Discard, edition: Edition.Second) { BaseCost = new Cost(4); Benefit.Cards = 1; Benefit.Actions = 1; Benefit.Currency.Coin.Value = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var emptyPiles = player._Game.Table.TableEntities.Count(te => (te.Value.Location == Location.Kingdom || te.Value.Location == Location.General) && te.Value.Count == 0); var discardText = ResourcesHelper.Get($"Discard{emptyPiles}Cards"); var choice = new Choice(discardText, this, player.Hand, ChoiceOutcome.Discard, player, minimum: emptyPiles, maximum: emptyPiles); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); } } public class Remodel : Base.Remodel { public Remodel() : base(Edition.Second) { } } public class Sentry : Card { public Sentry() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.DeckReduction | Traits.PlusAction | Traits.PlusCard | Traits.Trasher | Traits.RemoveCurses | Traits.Discard | Traits.Cantrip | Traits.CardOrdering | Traits.RemoveFromHand, edition: Edition.Second) { BaseCost = new Cost(5); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var newCards = player.Draw(2, DeckLocation.Private); var trashChoice = new Choice(Resource.TrashCards, this, newCards, ChoiceOutcome.Trash, player, minimum: 0, maximum: newCards.Count); var trashResult = player.MakeChoice(trashChoice); newCards.RemoveAll(c => trashResult.Cards.Contains(c)); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Private, trashResult.Cards)); var discardChoice = new Choice(Resource.DiscardCards, this, newCards, ChoiceOutcome.Discard, player, minimum: 0, maximum: newCards.Count); var discardResult = player.MakeChoice(discardChoice); newCards.RemoveAll(c => discardResult.Cards.Contains(c)); player.Discard(DeckLocation.Private, discardResult.Cards); var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, newCards, ChoiceOutcome.Select, player, isOrdered: true, minimum: newCards.Count, maximum: newCards.Count); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Private, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } } public class ThroneRoom : Card { private readonly List CardsPlayed = new List(); public ThroneRoom() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.Multiplier, edition: Edition.Second) { BaseCost = new Cost(4); } public override void AddedTo(DeckLocation location, IPlayer player) { base.AddedTo(location, player); CardsPlayed.Clear(); } public override bool CanCleanUp { // If Throne Room played a Duration card but the Duration card only did something once (e.g. Tactician), then Throne Room can be cleaned up get => CardsPlayed.All(cp => cp.CanCleanUpPlayed.Count(cup => !cup) < 2) && base.CanCleanUp; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseActionPlay2x, this, player.Hand[Categories.Action], ChoiceOutcome.Select, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { var cardToPlayTwice = result.Cards[0]; CardsPlayed.Add(cardToPlayTwice); cardToPlayTwice.ModifiedBy = this; player.Actions++; var previousPlayerMode = player.PutCardIntoPlay(cardToPlayTwice); var logicalCard = cardToPlayTwice.LogicalCard; player.PlayCard(cardToPlayTwice.LogicalCard, previousPlayerMode); player.Actions++; previousPlayerMode = player.PutCardIntoPlay(cardToPlayTwice, Resource.AgainFromCard.Replace("{card}", this.ToString())); player.PlayCard(logicalCard, previousPlayerMode); } else player.PlayNothing(); } protected override void ModifyDuration(IPlayer player, Card card) { base.ModifyDuration(player, card); base.ModifyDuration(player, card); } } public class Vassal : Card { public Vassal() : base(Categories.Action, Source.Base, Location.Kingdom, Traits.PlusCoin | Traits.Discard, edition: Edition.Second) { BaseCost = new Cost(3); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var cardToTest = player.Discard(DeckLocation.Deck, 1).FirstOrDefault(); // We might lose track of the discarded card, so let's make sure we can still see it. if (cardToTest != null && player.DiscardPile.First() == cardToTest && cardToTest.Category.HasFlag(Categories.Action)) { var choice = Choice.CreateYesNoChoice(Resource.MayPlayCard.Replace("{card}", cardToTest.Name), this, cardToTest, player, null); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.Actions++; var previousPlayerMode = player.PutCardIntoPlay(cardToTest); var logicalCard = cardToTest.LogicalCard; player.PlayCard(logicalCard, previousPlayerMode); } } } } public class Witch : Base.Witch { public Witch() : base(Edition.Second) { } } }