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.Intrigue { public static class TypeClass { public static readonly Type Baron = typeof(Baron); public static readonly Type Bridge = typeof(Bridge); public static readonly Type Conspirator = typeof(Conspirator); public static readonly Type Coppersmith = typeof(Coppersmith); public static readonly Type Courtyard = typeof(Courtyard); public static readonly Type Duke = typeof(Duke); public static readonly Type GreatHall = typeof(GreatHall); public static readonly Type Harem = typeof(Harem); public static readonly Type Ironworks = typeof(Ironworks); public static readonly Type Masquerade = typeof(Masquerade); public static readonly Type MiningVillage = typeof(MiningVillage); public static readonly Type Minion = typeof(Minion); public static readonly Type Nobles = typeof(Nobles); public static readonly Type Pawn = typeof(Pawn); public static readonly Type Saboteur = typeof(Saboteur); public static readonly Type Scout = typeof(Scout); public static readonly Type SecretChamber = typeof(SecretChamber); public static readonly Type ShantyTown = typeof(ShantyTown); public static readonly Type Steward = typeof(Steward); public static readonly Type Swindler = typeof(Swindler); public static readonly Type Torturer = typeof(Torturer); public static readonly Type TradingPost = typeof(TradingPost); public static readonly Type Tribute = typeof(Tribute); public static readonly Type Upgrade = typeof(Upgrade); public static readonly Type WishingWell = typeof(WishingWell); } public class Baron : Card { public Baron() : this(Edition.First) { } public Baron(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCoin | Traits.PlusBuy | Traits.Gainer | Traits.Discard | Traits.Terminal | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(4); Benefit.Buys = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand[Universal.TypeClass.Estate].Any()) { var choice = Choice.CreateYesNoChoice("You may discard an Estate card for +4. Do you want to discard?", this, player); var result = player.MakeChoice(choice); if (result.Options.Contains(Resource.Yes)) { player.Discard(DeckLocation.Hand, Universal.TypeClass.Estate, 1); var benefit = new CardBenefit(); benefit.Currency.Coin += 4; player.ReceiveBenefit(this, benefit); return; } } player.Gain(player._Game.Table.Estate, this); } } public class Bridge : Card { private IPlayer _turnEndedPlayer; private TurnEndedEventHandler _turnEndedEventHandler; private readonly List _costComputeEventHandlers = new List(); public Bridge() : this(Edition.First) { } public Bridge(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCoin | Traits.PlusBuy | Traits.ModifyCost | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); Benefit.Currency.Coin.Value = 1; Benefit.Buys = 1; } public override void TearDown(IGame game) { base.TearDown(game); if (_turnEndedEventHandler != null && _turnEndedPlayer != null) Player_TurnEnded(_turnEndedPlayer, new TurnEndedEventArgs(_turnEndedPlayer)); _turnEndedEventHandler = null; } protected override bool AllowUndo => true; 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; } _costComputeEventHandlers.Add(new CostComputeEventHandler(Player_BridgePlayed)); player._Game.CostCompute += _costComputeEventHandlers[_costComputeEventHandlers.Count - 1]; player._Game.SendMessage(player, this, 1); } private void Player_BridgePlayed(object sender, CostComputeEventArgs e) { if (e.Card is ICard || e.Card is ISupply) e.Cost.Coin -= 1; } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { var player = sender as IPlayer; if (_turnEndedEventHandler != null && _turnEndedPlayer != null) _turnEndedPlayer.TurnEnded -= _turnEndedEventHandler; _turnEndedPlayer = null; _turnEndedEventHandler = null; foreach (var cceh in _costComputeEventHandlers) player._Game.CostCompute -= cceh; _costComputeEventHandlers.Clear(); } } public class Conspirator : Card { public Conspirator() : this(Edition.First) { } public Conspirator(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(4); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.ActionsPlayed >= 3) player.ReceiveBenefit(this, new CardBenefit() { Actions = 1, Cards = 1 }); } } public class Coppersmith : Card { private IPlayer _turnEndedPlayer; private TurnEndedEventHandler _turnEndedEventHandler; private readonly List _cardPlayedEventHandlers = new List(); public Coppersmith() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.Basic | Traits.Terminal, edition: Edition.First) { BaseCost = new Cost(4); } protected override bool AllowUndo => true; 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) { // TODO -- this should be converted to use BenefitReceiving event instead of CardPlayed var benefit = new CardBenefit(); foreach (var card in e.Cards) { if (card is Universal.Copper) benefit.Currency += new Coin(1); } e.Player.ReceiveBenefit(this, benefit); } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { var player = sender as IPlayer; if (_turnEndedEventHandler != null && _turnEndedPlayer != null) _turnEndedPlayer.TurnEnded -= _turnEndedEventHandler; _turnEndedPlayer = null; _turnEndedEventHandler = null; foreach (var cpeh in _cardPlayedEventHandlers) player.CardPlayed -= cpeh; _cardPlayedEventHandlers.Clear(); } } public class Courtyard : Card { public Courtyard() : this(Edition.First) { } public Courtyard(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.Terminal | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(2); Benefit.Cards = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Choose a card to put on top of your deck", this, player.Hand, ChoiceOutcome.Select, player); var result = player.MakeChoice(choice); if (result.Cards.Any()) player.AddCardToDeck(player.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0]), DeckPosition.Top); } } public class Duke : Card { public Duke() : base(Categories.Victory, Source.Intrigue, Location.Kingdom, Traits.VariableVPs) { BaseCost = new Cost(5); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + collection.Count(c => c is Universal.Duchy); } } public class GreatHall : Card { public GreatHall() : base(Categories.Action | Categories.Victory, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.Cantrip, edition: Edition.First) { BaseCost = new Cost(3); Benefit.Actions = 1; Benefit.Cards = 1; VictoryPoints = 1; } } public class Harem : Card { public Harem() : base(Categories.Treasure | Categories.Victory, Source.Intrigue, Location.Kingdom, Traits.PlusCoin) { BaseCost = new Cost(6); Benefit.Currency.Coin.Value = 2; VictoryPoints = 2; } protected override bool AllowUndo => true; } public class Ironworks : Card { public Ironworks() : this(Edition.First) { } public Ironworks(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Gainer | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(4); } 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(4))); var choice = new Choice(Resource.GainUpTo4, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { var benefit = new CardBenefit(); var cardToGain = result.Supply.TopCard; if (player.Gain(result.Supply, this)) { if (cardToGain.Category.HasFlag(Categories.Action)) benefit.Actions = 1; if (cardToGain.Category.HasFlag(Categories.Treasure)) benefit.Currency += new Coin(1); if (cardToGain.Category.HasFlag(Categories.Victory)) benefit.Cards = 1; player.ReceiveBenefit(this, benefit); } } } } public class Masquerade : Card { public Masquerade() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.DeckReduction | Traits.PlusCurses | Traits.PlusCard | Traits.Trasher | Traits.RemoveCurses | Traits.AffectOthers | Traits.Terminal | Traits.NetCardDraw | Traits.RemoveFromHand, edition: Edition.First) { BaseCost = new Cost(3); Benefit.Cards = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var toPass = new List(player._Game.Players.Count); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) { var choosingPlayer = enumerator.Current; var choice = new Choice("Choose a card to pass to the left", this, choosingPlayer.Hand, ChoiceOutcome.Select, player); var result = choosingPlayer.MakeChoice(choice); if (result.Cards.Any()) { var passingCard = choosingPlayer.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0]); choosingPlayer.Lose(passingCard); toPass.Add(passingCard); player._Game.SendMessage(choosingPlayer, player._Game.GetPlayerFromIndex(choosingPlayer, 1), this, result.Cards[0]); } else toPass.Add(null); } enumerator = player._Game.GetPlayersStartingWithEnumerator(player); var index = 0; while (enumerator.MoveNext()) { var fromRight = toPass[(index + player._Game.Players.Count - 1) % player._Game.Players.Count]; if (fromRight != null) enumerator.Current.Receive(player._Game.GetPlayerFromIndex(enumerator.Current, -1), fromRight, DeckLocation.Hand, DeckPosition.Automatic); index++; } var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, player.Hand, ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); if (resultTrash.Cards.Any()) player.Trash(this, player.RetrieveCardFrom(DeckLocation.Hand, resultTrash.Cards[0])); } } public class MiningVillage : Card { public MiningVillage() : this(Edition.First) { } public MiningVillage(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusCoin | Traits.Trasher | Traits.ConditionalBenefit | Traits.Cantrip, edition: edition) { BaseCost = new Cost(4); Benefit.Cards = 1; Benefit.Actions = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.InPlay.Contains(PhysicalCard)) { var choice = Choice.CreateYesNoChoice("Do you want to trash this card for +2?", this, player); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.Trash(this, player.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard)); var benefit = new CardBenefit(); benefit.Currency += new Coin(2); player.ReceiveBenefit(this, benefit); } } } } public class Minion : Card { public Minion() : this(Edition.First) { } public Minion(Edition edition) : base(Categories.Action | Categories.Attack, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Discard | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(5); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseOne, this, this, new List() { "+2", "Discard your hand, +4Cards, and each other player with at least 5 cards in hand discards his hand and draws 4 cards" }, player); var result = player.MakeChoice(choice); if (result.Options.Contains("+2")) { var benefit = new CardBenefit(); benefit.Currency += new Coin(2); player.ReceiveBenefit(this, benefit); } else { player.DiscardHand(true); player.ReceiveBenefit(this, new CardBenefit() { Cards = 4 }); // Perform attack on each other player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; if (IsAttackBlocked[attackee]) continue; if (attackee.Hand.Count >= 5) { attackee.DiscardHand(true); attackee.DrawHand(4); } } } } } public class Nobles : Card { public Nobles() : this(Edition.First) { } public Nobles(Edition edition) : base(Categories.Action | Categories.Victory, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.ConditionalBenefit | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(6); VictoryPoints = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseOne, this, this, new List() { "+3Cards", "+2Actions" }, player); var result = player.MakeChoice(choice); var benefit = new CardBenefit(); if (result.Options.Contains("+3Cards")) benefit.Cards = 3; if (result.Options.Contains("+2Actions")) benefit.Actions = 2; player.ReceiveBenefit(this, benefit); } } public class Pawn : Card { public Pawn() : this(Edition.First) { } public Pawn(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.PlusBuy | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(2); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseTwo, this, this, new List() { "+1Card", "+1Action", "+1Buy", "+1" }, player, isOrdered: true, minimum: 2, maximum: 2); var result = player.MakeChoice(choice); foreach (var option in result.Options) { var benefit = new CardBenefit(); if (option == "+1Card") benefit.Cards = 1; if (option == "+1Action") benefit.Actions = 1; if (option == "+1Buy") benefit.Buys = 1; if (option == "+1") benefit.Currency += new Coin(1); player.ReceiveBenefit(this, benefit); } } } public class Saboteur : Card { public Saboteur() : base(Categories.Action | Categories.Attack, Source.Intrigue, Location.Kingdom, Traits.Trasher | Traits.Discard | Traits.Terminal, edition: Edition.First) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // skip active player while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; attackee.BeginDrawing(); while (attackee.Revealed[card => player._Game.ComputeCost(card).Coin >= 3].Count < 1 && attackee.CanDraw) attackee.Draw(DeckLocation.Revealed); attackee.EndDrawing(); var cards = attackee.Revealed[c => player._Game.ComputeCost(c).Coin >= 3]; if (cards.Any()) { var card = cards[0]; var trashedCardCost = player._Game.ComputeCost(card); attackee.Trash(this, attackee.RetrieveCardFrom(DeckLocation.Revealed, card)); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= (trashedCardCost.Coin - 2))); var choice = new Choice("You may gain a card", this, gainableSupplies, ChoiceOutcome.Gain, player, true); var result = attackee.MakeChoice(choice); if (result.Supply != null) attackee.Gain(result.Supply, this); } attackee.DiscardRevealed(); } } } public class Scout : Card { public Scout() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.CardOrdering | Traits.PlusAction, edition: Edition.First) { BaseCost = new Cost(4); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Draw(4, DeckLocation.Revealed); player.AddCardsToHand(player.RetrieveCardsFrom(DeckLocation.Revealed, Categories.Victory)); var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, player.Revealed, ChoiceOutcome.Select, player, isOrdered: true, minimum: player.Revealed.Count, maximum: player.Revealed.Count); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Revealed, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } } public class SecretChamber : Card { public SecretChamber() : base(Categories.Action | Categories.Reaction, Source.Intrigue, Location.Kingdom, Traits.ReactToAttack | Traits.Defense | Traits.PlusCoin | Traits.Discard | Traits.Terminal | Traits.ConditionalBenefit, edition: Edition.First) { BaseCost = new Cost(2); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Discard any number of cards. +1 per card discarded.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 0, maximum: player.Hand.Count); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); var benefit = new CardBenefit(); benefit.Currency += new Coin(result.Cards.Count); player.ReceiveBenefit(this, benefit); } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); if (location == DeckLocation.Hand) { player.Attacked -= Player_Attacked; player.Attacked += Player_Attacked; } } internal override void Player_Attacked(object sender, AttackedEventArgs e) { var player = sender as IPlayer; // Secret Chamber only protects against other attackers if (player == e.Attacker) return; // Only allow a single handling by a given card type if (player.Hand.Contains(PhysicalCard) && !e.HandledBy.Contains(Type) && !e.Revealable.ContainsKey(Type)) e.Revealable[Type] = new AttackReaction(this, Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_RevealSecretChamber); } internal void Player_RevealSecretChamber(IPlayer player, ref AttackedEventArgs e) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, PhysicalCard)); player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.Revealed, PhysicalCard)); player.ReceiveBenefit(this, new CardBenefit { Cards = 2 }); var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, e.AttackCard, player.Hand, ChoiceOutcome.Select, player, true, 2, 2); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Hand, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); e.HandledBy.Add(Type); // Attack isn't cancelled... it's just mitigated } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); player.Attacked -= Player_Attacked; } } public class ShantyTown : Card { public ShantyTown() : this(Edition.First) { } public ShantyTown(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.ConditionalBenefit | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(3); Benefit.Actions = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.ReturnHand(player.RevealHand()); if (player.Hand[Categories.Action].Count == 0) player.ReceiveBenefit(this, new CardBenefit { Cards = 2 }); } } public class Steward : Card { public Steward() : this(Edition.First) { } public Steward(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.DeckReduction | Traits.PlusCard | Traits.PlusCoin | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.ConditionalBenefit | Traits.NetCardDraw | Traits.RemoveFromHand, edition: edition) { BaseCost = new Cost(3); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var benefit = new CardBenefit(); var choice = new Choice(Resource.ChooseOne, this, this, new List() { "+2Cards", "+2", "Trash 2 cards from your hand" }, player); var result = player.MakeChoice(choice); if (result.Options.Contains("+2Cards")) benefit.Cards = 2; else if (result.Options.Contains("+2")) benefit.Currency += new Coin(2); else { var choiceTrash = new Choice(Resource.Trash2, this, player.Hand, ChoiceOutcome.Trash, player, minimum: 2, maximum: 2); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); } player.ReceiveBenefit(this, benefit); } } public class Swindler : Card { public Swindler() : this(Edition.First) { } public Swindler(Edition edition) : base(Categories.Action | Categories.Attack, Source.Intrigue, Location.Kingdom, Traits.PlusCurses | Traits.PlusCoin | Traits.Trasher | Traits.Terminal, edition: edition) { 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); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // skip active player while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; if (attackee.CanDraw) { var card = attackee.Draw(DeckLocation.Revealed); var trashedCardCost = player._Game.ComputeCost(card); attackee.Trash(this, attackee.RetrieveCardFrom(DeckLocation.Revealed, card)); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost == trashedCardCost)); var choice = new Choice($"Choose a card for {attackee} to gain", this, gainableSupplies, ChoiceOutcome.Gain, attackee, false); var result = player.MakeChoice(choice); if (result.Supply != null) attackee.Gain(result.Supply, this); } } } } public class Torturer : Card { public Torturer() : this(Edition.First) { } public Torturer(Edition edition) : base(Categories.Action | Categories.Attack, Source.Intrigue, Location.Kingdom, Traits.PlusCurses | Traits.PlusCard | Traits.Discard | Traits.Terminal | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(5); Benefit.Cards = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // skip active player while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; var choice = new Choice("Do you want to discard 2 cards or gain a Curse into your hand?", this, this, new List { Resource.Discard2Cards, "Gain a Curse in hand" }, attackee); var result = attackee.MakeChoice(choice); if (result.Options[0] == Resource.Discard2Cards) { var choiceDiscard = new Choice(Resource.Discard2Cards, this, attackee.Hand, ChoiceOutcome.Discard, attackee, minimum: 2, maximum: 2); var discards = attackee.MakeChoice(choiceDiscard); attackee.Discard(DeckLocation.Hand, discards.Cards); } else { attackee.Gain(player._Game.Table.Curse, this, DeckLocation.Hand); } } } } public class TradingPost : Card { public TradingPost() : this(Edition.First) { } public TradingPost(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.RemoveFromHand, edition: edition) { 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.Trash2, this, player.Hand, ChoiceOutcome.Trash, player, minimum: 2, maximum: 2); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); if (resultTrash.Cards.Count == 2) player.Gain(player._Game.Table.Silver, this, DeckLocation.Hand); } } public class Tribute : Card { public Tribute() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Discard | Traits.AffectOthers | Traits.ConditionalBenefit, edition: Edition.First) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Get the player to my left var playerToLeft = player._Game.GetPlayerFromIndex(player, 1); playerToLeft.Draw(2, DeckLocation.Revealed); var previousCardName = string.Empty; var benefit = new CardBenefit(); foreach (var card in playerToLeft.Revealed) { if (card.Name != previousCardName) { if (card.Category.HasFlag(Categories.Action)) benefit.Actions += 2; if (card.Category.HasFlag(Categories.Treasure)) benefit.Currency += new Coin(2); if (card.Category.HasFlag(Categories.Victory)) benefit.Cards += 2; } previousCardName = card.Name; } playerToLeft.DiscardRevealed(); player.ReceiveBenefit(this, benefit); } } public class Upgrade : Card { public Upgrade() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.DeckReduction | Traits.PlusCard | Traits.PlusAction | Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Cantrip | Traits.TrashForBenefit | Traits.RemoveFromHand) { 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 choiceTrash = new Choice(Resource.ChooseACardToTrash, this, player.Hand, ChoiceOutcome.Trash, player); 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.CurrentCost == (trashedCardCost + new Coin(1)))); 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); } } } public class WishingWell : Card { public WishingWell() : this(Edition.First) { } public WishingWell(Edition edition) : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.ConditionalBenefit | Traits.Cantrip, edition: edition) { 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 availableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( kvp => (kvp.Randomizer != null && !kvp.Randomizer.Traits.HasFlag(Traits.Randomizer)) || kvp.Location == Location.General )); var cards = new CardCollection(); var choice = new Choice(Resource.NameCard, this, availableSupplies, ChoiceOutcome.Select, player, false); foreach (var supply in player._Game.Table.TableEntities.Values.OfType().Union(player._Game.Table.SpecialPiles.Values.OfType())) { foreach (var type in supply.Types) { if (choice.Supplies.All(kvp => kvp.Value is ISupply kvpSupply && kvpSupply.Type != type)) cards.Add(CreateInstance(type)); } } cards.Sort(); choice.AddCards(cards); var result = player.MakeChoice(choice); IDisplayable wishedCard; if (result.Supply != null) wishedCard = result.Supply; else wishedCard = result.Cards[0]; player._Game.SendMessage(player, this, wishedCard); if (player.CanDraw) { player.Draw(DeckLocation.Revealed); if (player.Revealed[wishedCard.Type].Any()) { player.AddCardsToHand(DeckLocation.Revealed); } else { player.AddCardsToDeck(player.RetrieveCardsFrom(DeckLocation.Revealed), DeckPosition.Top); } } } } }