package com.vdom.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import com.vdom.api.ActionCard; import com.vdom.api.Card; import com.vdom.api.CurseCard; import com.vdom.api.DurationCard; import com.vdom.api.GameEvent; import com.vdom.api.GameEventListener; import com.vdom.api.TreasureCard; import com.vdom.api.VictoryCard; import com.vdom.core.MoveContext.PileSelection; import com.vdom.core.Player.JesterOption; import com.vdom.core.Player.SpiceMerchantOption; import com.vdom.core.Player.TournamentOption; import com.vdom.core.Player.TrustySteedOption; public class ActionCardImpl extends CardImpl implements ActionCard { // template (immutable) protected int addActions; protected int addBuys; protected int addCards; protected int addGold; protected int addVictoryTokens; protected boolean attack; protected boolean looter; boolean trashForced = false; boolean trashOnUse = false; public ActionCardImpl(Builder builder) { super(builder); addActions = builder.addActions; addBuys = builder.addBuys; addCards = builder.addCards; addGold = builder.addGold; addVictoryTokens = builder.addVictoryTokens; attack = builder.attack; looter = builder.looter; trashOnUse = builder.trashOnUse; trashForced = builder.trashForced; } public static class Builder extends CardImpl.Builder{ protected int addActions; protected int addBuys; protected int addCards; protected int addGold; protected int addVictoryTokens; protected boolean attack; protected boolean looter; protected boolean trashOnUse; protected boolean trashForced = false; public Builder(Cards.Type type, int cost) { super(type, cost); } public Builder addActions(int val) { addActions = val; return this; } public Builder addBuys(int val) { addBuys = val; return this; } public Builder addCards(int val) { addCards = val; return this; } public Builder addGold(int val) { addGold = val; return this; } public Builder addVictoryTokens(int val) { addVictoryTokens = val; return this; } public Builder attack() { attack = true; return this; } public Builder looter() { looter = true; return this; } public Builder trashOnUse() { trashOnUse = true; return this; } public Builder trashForced() { trashForced = true; return this; } public ActionCardImpl build() { return new ActionCardImpl(this); } @Override public Builder isShelter() { this.isShelter = true; return this; } @Override public Builder isLooter() { this.isLooter = true; return this; } @Override public Builder isRuins() { this.isRuins = true; return this; } public Builder isKnight() { this.isKnight = true; this.attack = true; return this; } } public int getAddActions() { return addActions; } public int getAddBuys() { return addBuys; } public int getAddCards() { return addCards; } public int getAddGold() { return addGold; } public int getAddVictoryTokens() { return addVictoryTokens; } public boolean isAttack() { return attack; } public boolean trashForced() { return trashForced; } @Override public CardImpl instantiate() { checkInstantiateOK(); ActionCardImpl c = new ActionCardImpl(); copyValues(c); return c; // return instantiate(null); } // protected CardImpl impersonateAnotherCard(Card bandOfMisfits) { // ActionCardImpl c = new ActionCardImpl(); // copyValues(c); // c.setBandOfMisfitsCard(bandOfMisfits); // return c; // } protected void copyValues(ActionCardImpl c) { super.copyValues(c); c.addActions = addActions; c.addBuys = addBuys; c.addCards = addCards; c.addGold = addGold; c.addVictoryTokens = addVictoryTokens; c.attack = attack; c.looter = looter; c.trashOnUse = trashOnUse; } protected ActionCardImpl() { } public void play(Game game, MoveContext context) { play(game, context, true); } public void play(Game game, MoveContext context, boolean fromHand) { super.play(game, context); Player currentPlayer = context.getPlayer(); boolean newCard = false; ActionCardImpl actualCard = (this.getControlCard() != null ? (ActionCardImpl) this.getControlCard() : this); if (this.numberTimesAlreadyPlayed == 0 && this == actualCard) { newCard = true; this.movedToNextTurnPile = false; if (fromHand) currentPlayer.hand.remove(this); if (trashOnUse) { currentPlayer.trash(this, null, context); } else if (this instanceof DurationCard) { currentPlayer.nextTurnCards.add((DurationCard) this); } else { currentPlayer.playedCards.add(this); } } GameEvent event = new GameEvent(GameEvent.Type.PlayingAction, (MoveContext) context); event.card = this; event.newCard = newCard; game.broadcastEvent(event); // playing an action if (this == actualCard) context.actionsPlayedSoFar++; if (context.freeActionInEffect == 0) { context.actions--; } context.actions += addActions; context.buys += addBuys; context.addGold += addGold; currentPlayer.addVictoryTokens(context, addVictoryTokens); int cardsToDraw = addCards; while (cardsToDraw > 0) { cardsToDraw--; game.drawToHand(currentPlayer, this); } if (isAttack()) attackPlayed(context, game, currentPlayer); additionalCardActions(game, context, currentPlayer); event = new GameEvent(GameEvent.Type.PlayedAction, (MoveContext) context); event.card = this; game.broadcastEvent(event); } protected void additionalCardActions(Game game, MoveContext context, Player currentPlayer) { switch (this.getType()) { case BandOfMisfits: bandOfMisfits(game, context, currentPlayer); break; case MoneyLender: moneyLender(context, currentPlayer); break; case Chancellor: chancellor(game, context, currentPlayer); break; case Workshop: workshop(currentPlayer, context); break; case Feast: feast(context, currentPlayer); break; case KingsCourt: case ThroneRoom: case Procession: throneRoomKingsCourt(game, context, currentPlayer); break; case Smugglers: smugglers(context, currentPlayer); break; case PirateShip: pirateShip(game, context, currentPlayer); break; case Haven: haven(context, currentPlayer); break; case Chapel: chapel(context, currentPlayer); break; case Library: library(game, context, currentPlayer); break; case Adventurer: adventurer(game, context, currentPlayer); break; case Golem: golem(game, context, currentPlayer); break; case Bureaucrat: bureaucrat(game, context, currentPlayer); break; case SecretChamber: secretChamber(context, currentPlayer); break; case Cellar: cellar(game, context, currentPlayer); break; case Remodel: remodel(context, currentPlayer); break; case Militia: militia(game, context, currentPlayer); break; case Thief: thief(game, context, currentPlayer); break; case Conspirator: conspirator(game, context, currentPlayer); break; case Spy: case ScryingPool: spyAndScryingPool(game, context, currentPlayer); break; case Courtyard: courtyard(context, currentPlayer); break; case Baron: baron(context, currentPlayer); break; case Swindler: swindler(game, context, currentPlayer); break; case Steward: steward(game, context, currentPlayer); break; case Scout: scout(game, context, currentPlayer); break; case ShantyTown: shantyTown(game, context, currentPlayer); break; case Saboteur: saboteur(game, context, currentPlayer); break; case Pawn: pawn(game, context, currentPlayer); break; case Minion: minion(game, context, currentPlayer); break; case MiningVillage: miningVillage(context, currentPlayer); break; case Masquerade: masquerade(game, context, currentPlayer); break; case Ironworks: ironworks(game, context, currentPlayer); break; case Nobles: nobles(game, context, currentPlayer); break; case Coppersmith: copperSmith(context); break; case Tribute: tribute(game, context, currentPlayer); break; case Upgrade: upgrade(context, currentPlayer); break; case WishingWell: wishingWell(game, context, currentPlayer); break; case Bridge: context.cardCostModifier -= 1; break; case TradingPost: tradingPost(context, currentPlayer); break; case Torturer: torturer(game, context, currentPlayer); break; case Tactician: tactician(context, currentPlayer); break; case Familiar: case Witch: witchFamiliar(game, context, currentPlayer); break; case Apothecary: apothecary(game, context, currentPlayer); break; case Transmute: transmute(context, currentPlayer); break; case Apprentice: apprentice(game, context, currentPlayer); break; case University: university(context, currentPlayer); break; case Mine: mine(context, currentPlayer); break; case CouncilRoom: councilRoom(game, context); break; case SeaHag: seaHag(game, context, currentPlayer); break; case NativeVillage: nativeVillage(game, context, currentPlayer); break; case Island: island(context, currentPlayer); break; case PearlDiver: pearlDiver(context, currentPlayer); break; case Lookout: lookout(game, context, currentPlayer); break; case Navigator: navigator(game, context, currentPlayer); break; case Embargo: embargo(game, context, currentPlayer); break; case TreasureMap: treasureMap(context, currentPlayer); break; case Explorer: explorer(context, currentPlayer); break; case Cutpurse: cutpurse(game, context, currentPlayer); break; case Warehouse: warehouse(context, currentPlayer); break; case Ambassador: ambassador(game, context, currentPlayer); break; case Salvager: salvager(context, currentPlayer); break; case GhostShip: ghostShip(game, context, currentPlayer); break; case Bishop: bishop(game, context, currentPlayer); break; case City: city(game, context, currentPlayer); break; case CountingHouse: countingHouse(context, currentPlayer); break; case Expand: expand(context, currentPlayer); break; case Forge: forge(context, currentPlayer); break; case Goons: goons(game, context, currentPlayer); break; case Mint: mint(context, currentPlayer); break; case Mountebank: mountebank(game, context, currentPlayer); break; case Rabble: rabble(game, context, currentPlayer); break; case TradeRoute: tradeRoute(game, context, currentPlayer); break; case Vault: vault(game, context, currentPlayer); break; case WatchTower: watchTower(game, currentPlayer); break; case FarmingVillage: farmingVillage(game, context, currentPlayer); break; case FortuneTeller: fortuneTeller(game, context, currentPlayer); break; case Hamlet: hamlet(context, currentPlayer); break; case Harvest: harvest(game, context, currentPlayer); break; case HorseTraders: horseTraders(context, currentPlayer); break; case HuntingParty: huntingParty(game, context, currentPlayer); break; case Jester: jester(game, context, currentPlayer); break; case Menagerie: menagerie(game, context, currentPlayer); break; case Remake: remake(context, currentPlayer); break; case Tournament: tournament(game, context, currentPlayer); break; case YoungWitch: youngwitch(game, context, currentPlayer); break; case BagofGold: currentPlayer.gainNewCard(Cards.gold, this.controlCard, context); break; case Followers: followers(game, context, currentPlayer); break; case Princess: if (this.controlCard.numberTimesAlreadyPlayed == 0) { context.cardCostModifier -= 2; } break; case TrustySteed: trustySteed(game, context, currentPlayer); break; case Crossroads: crossroads(game, context, currentPlayer); break; case Duchess: duchess(game); break; case Develop: develop(context, currentPlayer); break; case Oasis: oasis(context, currentPlayer); break; case JackofallTrades: jackOfAllTrades(game, context, currentPlayer); break; case NobleBrigand: nobleBrigandAttack(context, true); break; case SpiceMerchant: spiceMerchant(game, context, currentPlayer); break; case Oracle: oracle(game, context, currentPlayer); break; case Scheme: context.schemesPlayed++; break; case Trader: trader(context, currentPlayer); break; case Cartographer: cartographer(game, context, currentPlayer); break; case Embassy: embassy(context, currentPlayer); break; case Inn: inn(context, currentPlayer); break; case Mandarin: mandarin(context, currentPlayer); break; case Margrave: margrave(game, context, currentPlayer); break; case Stables: stables(game, context, currentPlayer); break; case Possession: possession(context); break; case PoorHouse: poorHouse(context, currentPlayer); break; case Sage: sage(game, context, currentPlayer); break; case Rats: rats(context, currentPlayer); break; case Squire: squire(context, currentPlayer); break; case Armory: armory(currentPlayer, context); break; case Altar: altar(currentPlayer, context); break; case BanditCamp: banditCamp(context, currentPlayer); break; case Beggar: beggar(currentPlayer, context); break; case Catacombs: catacombs(game, currentPlayer, context); break; case Count: count(currentPlayer, context); break; case DeathCart: deathCart(currentPlayer, context); break; case Forager: forager(game, currentPlayer, context); break; case Graverobber: graverobber(game, currentPlayer, context); break; case Ironmonger: ironmonger(game, currentPlayer, context); break; case JunkDealer: junkDealer(currentPlayer, context); break; case Mystic: mystic(game, context, currentPlayer); break; case Scavenger: scavenger(game, context, currentPlayer); break; case Storeroom: storeroom(game, context, currentPlayer); break; case WanderingMinstrel: wanderingMinstrel(currentPlayer, context); break; case Rebuild: rebuild(currentPlayer, context); break; case Rogue: rogue(game, context, currentPlayer); break; case Pillage: pillage(game, context, currentPlayer); break; case Governor: governor(game, context, currentPlayer); break; case Envoy: envoy(game, context, currentPlayer); break; case Survivors: survivors(context, game, currentPlayer); break; case Cultist: cultist(context, game, currentPlayer); break; case Urchin: urchin(context, game, currentPlayer); break; case Mercenary: mercenary(context, game, currentPlayer); break; case Marauder: marauder(context, game, currentPlayer); break; case Hermit: hermit(context, game, currentPlayer); break; case Madman: madman(context, game, currentPlayer); break; case Vagrant: vagrant(context, game, currentPlayer); break; case DameAnna: dameAnna(context, currentPlayer); break; case DameNatalie: dameNatalie(context, currentPlayer); break; case SirMichael: sirMichael(context, currentPlayer); break; case DameJosephine: case DameMolly: case DameSylvia: case SirBailey: case SirDestry: case SirMartin: case SirVander: knight(context, currentPlayer); break; case Soothsayer: soothsayer(game, context, currentPlayer); break; case Taxman: taxman(game, context, currentPlayer); break; case Plaza: plaza(game, context, currentPlayer); break; case CandlestickMaker: candlestickMaker(game, context, currentPlayer); break; case Baker: baker(game, context, currentPlayer); break; case Butcher: butcher(game, context, currentPlayer); break; case Advisor: advisor(game, context, currentPlayer); break; case Journeyman: journeyman(game, context, currentPlayer); break; case StoneMason: stonemason(game, context, currentPlayer); break; case Doctor: doctor(game, context, currentPlayer); break; case Herald: herald(game, context, currentPlayer); break; default: break; } } private void warehouse(MoveContext context, Player currentPlayer) { if (currentPlayer.hand.size() == 0) { return; } Card[] cards; if (currentPlayer.hand.size() > 3) { cards = currentPlayer.controlPlayer.warehouse_cardsToDiscard(context); } else { cards = currentPlayer.getHand().toArray(); } boolean bad = false; if (cards == null) { bad = true; } else if (cards.length > 3) { bad = true; } else { ArrayList<Card> handCopy = Util.copy(currentPlayer.hand); for (Card card : cards) { if (!handCopy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(currentPlayer, "Warehouse discard error, discarding first 3 cards."); cards = new Card[3]; for (int i = 0; i < cards.length; i++) { cards[i] = currentPlayer.hand.get(i); } } for (int i = 0; i < cards.length; i++) { currentPlayer.hand.remove(cards[i]); currentPlayer.reveal(cards[i], this.controlCard, context); currentPlayer.discard(cards[i], this.controlCard, null); } } private void cutpurse(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); if (player.hand.contains(Cards.copper)) { Card card = player.hand.get(Cards.copper); player.hand.remove(Cards.copper); player.discard(card, this.controlCard, playerContext); } else { for (Card card : player.getHand()) { player.reveal(card, this.controlCard, playerContext); } } } } } private void explorer(MoveContext context, Player currentPlayer) { Card province = null; for (Card card : currentPlayer.hand) { if (card.equals(Cards.province)) { province = card; break; } } Card treasure; if (province != null && currentPlayer.controlPlayer.explorer_shouldRevealProvince(context)) { currentPlayer.reveal(province, this.controlCard, context); treasure = Cards.gold; } else { treasure = Cards.silver; } currentPlayer.gainNewCard(treasure, this.controlCard, context); } private void treasureMap(MoveContext context, Player currentPlayer) { // Check for Treasure Map in hand Card anotherMap = null; for (Card card : currentPlayer.hand) { if (card.equals(this)) { anotherMap = card; break; } } // Treasure Map still trashes extra Treasure Maps in hand on throneRoom if (this.numberTimesAlreadyPlayed == 0) { if (anotherMap != null) { // going to get the gold so trash map played and map from hand currentPlayer.trash(currentPlayer.playedCards.removeCard(this.controlCard), null, context); currentPlayer.trash(currentPlayer.hand.removeCard(anotherMap), null, context); for (int i = 0; i < 4; i++) { currentPlayer.gainNewCard(Cards.gold, this, context); } } else { // Send notification that the map played was trashed (as per rules // when a single map is played) currentPlayer.trash(currentPlayer.playedCards.removeCard(this.controlCard), null, context); } } else { if (anotherMap != null) { // trash it, but no gold currentPlayer.trash(currentPlayer.hand.removeCard(anotherMap), null, context); } } } private void embargo(Game game, MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.embargo_supplyToEmbargo(context); while (game.addEmbargo(card) == null) { Util.playerError(currentPlayer, "Embargo error, adding embargo to random card."); while (true) { card = Util.randomCard(context.getCardsInGame()); if (game.isValidEmbargoPile(card)) break; } } GameEvent event = new GameEvent(GameEvent.Type.Embargo, context); event.card = card; game.broadcastEvent(event); } private void navigator(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> topOfTheDeck = new ArrayList<Card>(); for (int i = 0; i < 5; i++) { Card card = game.draw(currentPlayer); if (card != null) { topOfTheDeck.add(card); } } if (topOfTheDeck.size() > 0) { if (currentPlayer.controlPlayer.navigator_shouldDiscardTopCards(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()]))) { while (!topOfTheDeck.isEmpty()) { currentPlayer.discard(topOfTheDeck.remove(0), this.controlCard, null); } } else { Card[] order = currentPlayer.controlPlayer.navigator_cardOrder(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()])); // Check that they returned the right cards boolean bad = false; if (order == null) { bad = true; } else { ArrayList<Card> copy = new ArrayList<Card>(); for (Card card : topOfTheDeck) { copy.add(card); } for (Card card : order) { if (!copy.remove(card)) { bad = true; break; } } if (!copy.isEmpty()) { bad = true; } } if (bad) { Util.playerError(currentPlayer, "Navigator order cards error, ignoring."); order = topOfTheDeck.toArray(new Card[topOfTheDeck.size()]); } // Put the cards back on the deck for (int i = order.length - 1; i >= 0; i--) { currentPlayer.putOnTopOfDeck(order[i]); } } } } private void lookout(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> cards = new ArrayList<Card>(); for (int i = 0; i < 3; i++) { Card card = game.draw(currentPlayer); if (card != null) { cards.add(card); } } if (cards.size() == 0) { return; } Card toTrash; if (cards.size() > 1) { toTrash = currentPlayer.controlPlayer.lookout_cardToTrash(context, cards.toArray(new Card[cards.size()])); } else { toTrash = cards.get(0); } if (toTrash == null || !cards.contains(toTrash)) { Util.playerError(currentPlayer, "Lookout trash error, just picking the first card."); toTrash = cards.get(0); } currentPlayer.trash(toTrash, this.controlCard, context); cards.remove(toTrash); if (cards.size() == 0) { return; } Card toDiscard; if (cards.size() > 1) { toDiscard = currentPlayer.controlPlayer.lookout_cardToDiscard(context, cards.toArray(new Card[cards.size()])); } else { toDiscard = cards.get(0); } if (toDiscard == null || !cards.contains(toDiscard)) { Util.playerError(currentPlayer, "Lookout discard error, just picking the first card."); toDiscard = cards.get(0); } currentPlayer.discard(toDiscard, this.controlCard, context); cards.remove(toDiscard); if (cards.size() > 0) { currentPlayer.putOnTopOfDeck(cards.get(0)); } } private void pearlDiver(MoveContext context, Player currentPlayer) { if (currentPlayer.getDeckSize() <= 1 && currentPlayer.discard.size() + currentPlayer.getDeckSize() > 1) { context.game.replenishDeck(currentPlayer); } if (currentPlayer.getDeckSize() > 1) { Card card = currentPlayer.peekAtDeckBottom(); if (currentPlayer.controlPlayer.pearlDiver_shouldMoveToTop(context, card)) { currentPlayer.removeFromDeckBottom(); currentPlayer.putOnTopOfDeck(card); } } } private void island(MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.island_cardToSetAside(context); if (card != null && !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Island set aside card error, just setting aside island."); card = null; } // Move to island mat if not already played if (this.controlCard.numberTimesAlreadyPlayed == 0) { currentPlayer.playedCards.remove(currentPlayer.playedCards.lastIndexOf((Card) this.controlCard)); currentPlayer.island.add(this.controlCard); this.controlCard.stopImpersonatingCard(); } if (card != null) { currentPlayer.hand.remove(card); currentPlayer.island.add(card); } } private void nativeVillage(Game game, MoveContext context, Player currentPlayer) { if (currentPlayer.controlPlayer.nativeVillage_takeCards(context)) { while (!currentPlayer.nativeVillage.isEmpty()) { currentPlayer.hand.add(currentPlayer.nativeVillage.remove(0)); } } else { Card draw = game.draw(currentPlayer); if (draw != null) { currentPlayer.nativeVillage.add(draw); Util.sensitiveDebug(currentPlayer, "Added to Native Village:" + draw.getName(), true); } } } private void seaHag(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); Card draw = game.draw(player); if (draw != null) { player.discard(draw, this.controlCard, playerContext); } player.gainNewCard(Cards.curse, this.controlCard, playerContext); } } } private void councilRoom(Game game, MoveContext context) { for (Player player : getAllPlayers()) { if (player != context.getPlayer()) { drawToHand(game, player, this.controlCard); } } } private void mine(MoveContext context, Player currentPlayer) { TreasureCard cardToUpgrade = currentPlayer.controlPlayer.mine_treasureFromHandToUpgrade(context); if (cardToUpgrade == null) { Card[] cards = currentPlayer.getTreasuresInHand().toArray(new Card[] {}); if (cards.length != 0) { Util.playerError(currentPlayer, "Mine card to upgrade was invalid, picking treasure from hand."); cardToUpgrade = (TreasureCard) Util.randomCard(cards); } } if (cardToUpgrade != null) { CardList hand = currentPlayer.getHand(); for (int i = 0; i < hand.size(); i++) { Card card = hand.get(i); if (cardToUpgrade.equals(card)) { Card thisCard = removeFromHand(currentPlayer, i); currentPlayer.trash(thisCard, this.controlCard, context); TreasureCard newCard = currentPlayer.controlPlayer.mine_treasureToObtain(context, card.getCost(context) + 3, card.costPotion()); if (!(newCard != null && newCard.getCost(context) <= card.getCost(context) + 3 && context.getCardsLeftInPile(newCard) > 0)) { Util.playerError(currentPlayer, "Mine treasure to obtain was invalid, picking random treasure from table."); for (Card treasureCard : context.getTreasureCardsInGame()) { if (context.getCardsLeftInPile(treasureCard) > 0 && treasureCard.getCost(context) <= card.getCost(context) + 3) if (!treasureCard.costPotion() || card.costPotion()) newCard = (TreasureCard) treasureCard; } } if (newCard != null && newCard.getCost(context) <= card.getCost(context) + 3 && context.getCardsLeftInPile(newCard) > 0) currentPlayer.gainNewCard(newCard, this.controlCard, context); break; } } } } private void university(MoveContext context, Player currentPlayer) { ActionCard cardToObtain = currentPlayer.controlPlayer.university_actionCardToObtain(context); if (cardToObtain != null && cardToObtain instanceof ActionCard && cardToObtain.getCost(context) <= 5 && !cardToObtain.costPotion()) { currentPlayer.gainNewCard(cardToObtain, this.controlCard, context); } } private void apprentice(Game game, MoveContext context, Player currentPlayer) { if(currentPlayer.hand.size() > 0) { Card cardToTrash = currentPlayer.controlPlayer.apprentice_cardToTrash(context); if (cardToTrash == null || !currentPlayer.hand.contains(cardToTrash)) { Util.playerError(currentPlayer, "Apprentice card to trash was invalid, trashing random card."); cardToTrash = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(cardToTrash); currentPlayer.trash(cardToTrash, this.controlCard, context); for (int i = 1; i <= (cardToTrash.getCost(context) + (cardToTrash.costPotion() ? 2 : 0)); i++) { game.drawToHand(currentPlayer, this.controlCard); } } } private void transmute(MoveContext context, Player currentPlayer) { Card cardToTrash = currentPlayer.controlPlayer.transmute_cardToTrash(context); if (cardToTrash == null) { Util.playerError(currentPlayer, "Transmute card to trash was null, not trashing anything."); } else if (!currentPlayer.hand.contains(cardToTrash)) { Util.playerError(currentPlayer, "Transmute card to trash is not in your hand, not trashing anything."); } else { currentPlayer.hand.remove(cardToTrash); currentPlayer.trash(cardToTrash, this.controlCard, context); if (cardToTrash instanceof ActionCard) { currentPlayer.gainNewCard(Cards.duchy, this.controlCard, context); } if (cardToTrash instanceof TreasureCard) { currentPlayer.gainNewCard(Cards.transmute, this.controlCard, context); } if (cardToTrash instanceof VictoryCard) { currentPlayer.gainNewCard(Cards.gold, this.controlCard, context); } } } private void apothecary(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> residue = new ArrayList<Card>(); for (int i = 1; i <= 4; i++) { Card card = game.draw(currentPlayer); if (card != null) { currentPlayer.reveal(card, this.controlCard, context); if (card.equals(Cards.copper) || card.equals(Cards.potion)) { currentPlayer.hand.add(card); } else { residue.add(card); } } } if (residue.size() > 0) { ArrayList<Card> playerCards = currentPlayer.controlPlayer.apothecary_cardsForDeck(context, residue); if (playerCards == null) { playerCards = residue; } for (int i = playerCards.size() - 1; i >= 0; i--) { Card card = playerCards.get(i); currentPlayer.putOnTopOfDeck(card); } } } private void witchFamiliar(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); player.gainNewCard(Cards.curse, this.controlCard, new MoveContext(game, player)); } } } private void tactician(MoveContext context, Player currentPlayer) { // throneroom has no effect since hand is already empty if (this.controlCard.numberTimesAlreadyPlayed == 0) { // Only works if at least one card discarded if (currentPlayer.hand.size() > 0) { while (!currentPlayer.hand.isEmpty()) { currentPlayer.discard(currentPlayer.hand.remove(0), this.controlCard, null); } } else { currentPlayer.nextTurnCards.remove(this.controlCard); currentPlayer.playedCards.add(this.controlCard); } } else { // reset clone count this.controlCard.cloneCount = 1; } } private void torturer(Game game, MoveContext context, Player currentPlayer) { for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, targetPlayer); Player.TorturerOption option; try { option = (targetPlayer).controlPlayer.torturer_attack_chooseOption(context); } catch (NoSuchFieldError e) { Util.playerError(targetPlayer, "'Take three cards' version of torturer attack no longer supported."); option = null; } if (option == null) { Util.playerError(targetPlayer, "Torturer option error, taking curse card."); option = Player.TorturerOption.TakeCurse; } if (option == Player.TorturerOption.TakeCurse) { targetPlayer.gainNewCard(Cards.curse, this.controlCard, playerContext); } else { ArrayList<Card> handCopy = Util.copy(targetPlayer.getHand()); Card[] cardsToDiscard = (targetPlayer).controlPlayer.torturer_attack_cardsToDiscard(context); boolean bad = false; if (cardsToDiscard == null) { bad = true; } else if (handCopy.size() < 2 && cardsToDiscard.length != handCopy.size()) { bad = true; } else if (cardsToDiscard.length != 2) { bad = true; } else { ArrayList<Card> copyForDiscard = Util.copy(targetPlayer.getHand()); for (Card cardToKeep : cardsToDiscard) { if (!copyForDiscard.remove(cardToKeep)) { bad = true; break; } } } if (bad) { if (handCopy.size() >= 2) { Util.playerError(targetPlayer, "Torturer discard error, just discarding the first 2."); } cardsToDiscard = new Card[Math.min(2, handCopy.size())]; for (int i = 0; i < cardsToDiscard.length; i++) { cardsToDiscard[i] = handCopy.get(i); } } for (Card card : cardsToDiscard) { targetPlayer.hand.remove(card); targetPlayer.discard(card, this.controlCard, playerContext); } } } } } private void scout(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> cards = new ArrayList<Card>(); for (int i = 0; i < 4; i++) { Card card = game.draw(currentPlayer); if (card == null) { break; } if (card instanceof VictoryCard ) { currentPlayer.hand.add(card); } else { cards.add(card); } } if (cards.size() == 0) { return; } for (Card card : cards) { currentPlayer.reveal(card, this.controlCard, context); } Card[] order = currentPlayer.controlPlayer.scout_orderCards(context, cards.toArray(new Card[cards.size()])); boolean bad = false; if (order == null || order.length != cards.size()) { bad = true; } else { ArrayList<Card> orderArray = new ArrayList<Card>(); for (Card card : order) { orderArray.add(card); if (!cards.contains(card)) { bad = true; } } for (Card card : cards) { if (!orderArray.contains(card)) { bad = true; } } } if (bad) { Util.playerError(currentPlayer, "Scout order cards error, ignoring."); order = cards.toArray(new Card[cards.size()]); } for (int i = order.length - 1; i >= 0; i--) { currentPlayer.putOnTopOfDeck(order[i]); } } private void ambassador(Game game, MoveContext context, Player currentPlayer) { if (currentPlayer.hand.size() == 0) { return; } Card card = currentPlayer.controlPlayer.ambassador_revealedCard(context); if (card == null) { card = Util.randomCard(currentPlayer.hand); } else if (!currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Ambassador revealed card error, picking random card."); card = Util.randomCard(currentPlayer.hand); } Card origCard = card; Card virtCard = card; AbstractCardPile pile; if (card.isKnight()) { virtCard = Cards.virtualKnight; } else if (card.isRuins()) { virtCard = Cards.virtualRuins; } pile = game.getPile(virtCard); currentPlayer.reveal(origCard, this.controlCard, context); //Util.log("Ambassador revealed card:" + origCard.getName()); int returnCount = -1; if (!pile.isSupply()) { Util.playerError(currentPlayer, "Ambassador revealed card not in supply, returning 0."); } else { returnCount = currentPlayer.controlPlayer.ambassador_returnToSupplyFromHand(context, origCard); if (returnCount > 2) { Util.playerError(currentPlayer, "Ambassador return to supply error (more than 2 cards), returning 2."); returnCount = 2; } else { int inHandCount = currentPlayer.inHandCount(origCard); if (returnCount > inHandCount) { Util.playerError(currentPlayer, "Ambassador return to supply error (more than cards in hand), returning " + inHandCount); returnCount = inHandCount; } } } for (int i = 0; i < returnCount; i++) { int idx = currentPlayer.hand.indexOf(origCard); if (idx > -1) { pile.addCard(currentPlayer.hand.remove(idx)); } else { Util.playerError(currentPlayer, "Ambassador return to supply error, just returning those available."); break; } } if (returnCount > -1) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); if (pile.getType() == AbstractCardPile.PileType.SingleCardPile || origCard.equals(pile.card()) ) { player.gainNewCard(virtCard, this.controlCard, new MoveContext(game, player)); } } } } } private void ghostShip(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); // MoveContext playerContext = new MoveContext(game, player); if (player.hand.size() >= 4) { Card[] cards = player.controlPlayer.ghostShip_attack_cardsToPutBackOnDeck(context); boolean bad = false; if (cards == null || cards.length != player.hand.size() - 3) { bad = true; } else { ArrayList<Card> copy = Util.copy(player.hand); for (Card card : cards) { if (!copy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(player, "Ghost Ship put back cards error, putting back the first " + (player.hand.size() - 3) + " cards."); cards = new Card[player.hand.size() - 3]; for (int i = 0; i < player.hand.size() - 3; i++) { cards[i] = player.hand.get(i); } } for (int i = cards.length - 1; i >= 0; i--) { player.hand.remove(cards[i]); player.putOnTopOfDeck(cards[i]); } } } } } private void salvager(MoveContext context, Player currentPlayer) { if (currentPlayer.hand.size() == 0) { return; } Card card = currentPlayer.controlPlayer.salvager_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Salvager trash error, trashing first card."); card = currentPlayer.hand.get(0); } context.addGold += card.getCost(context); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } private void bishop(Game game, MoveContext context, Player currentPlayer) { if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.bishop_cardToTrashForVictoryTokens(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Bishop trash error, trashing a random card."); card = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); currentPlayer.addVictoryTokens(context, card.getCost(context) / 2); } for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer) { Card card = (player).controlPlayer.bishop_cardToTrash(context); if (card != null && player.hand.contains(card)) { player.hand.remove(card); player.trash(card, this.controlCard, new MoveContext(game, player)); } } } } private void city(Game game, MoveContext context, Player currentPlayer) { if (game.emptyPiles() > 0) { game.drawToHand(currentPlayer, this.controlCard); } if (game.emptyPiles() > 1) { context.buys++; context.addGold++; } } private void countingHouse(MoveContext context, Player currentPlayer) { if (!currentPlayer.discard.isEmpty()) { for (Iterator<Card> it = currentPlayer.discard.iterator(); it.hasNext();) { Card card = it.next(); if (Cards.copper.equals(card)) { currentPlayer.reveal(card, this.controlCard, context); it.remove(); currentPlayer.hand.add(card); } } } } private void expand(MoveContext context, Player currentPlayer) { if (currentPlayer.getHand().size() == 0) { return; } Card card = currentPlayer.controlPlayer.expand_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Expand trash error, expanding a random card."); card = Util.randomCard(currentPlayer.hand); } int maxCost = card.getCost(context) + 3; boolean potion = card.costPotion(); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); card = currentPlayer.controlPlayer.expand_cardToObtain(context, maxCost, potion); if (card != null) { if (card.getCost(context) > maxCost) { Util.playerError(currentPlayer, "Expand error, new card costs too much."); } else if(card.costPotion() && !potion) { Util.playerError(currentPlayer, "Expand error, new card costs potion and trashed card does not."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Expand error, pile is empty or card is not in the game."); } } } } private void forge(MoveContext context, Player currentPlayer) { int totalCost = 0; Card[] cards = currentPlayer.controlPlayer.forge_cardsToTrash(context); if (cards != null) { for (Card card : cards) { if (card != null && currentPlayer.hand.contains(card)) { totalCost += card.getCost(context); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } } } Card card = currentPlayer.controlPlayer.forge_cardToObtain(context, totalCost); if (card != null) { if (card.getCost(context) != totalCost || card.costPotion() || !Cards.isSupplyCard(card) || card.equals(Cards.curse)) { Util.playerError(currentPlayer, "Forge returned invalid card, ignoring."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Forge error, pile is empty or card is not in the game."); } } } } private void throneRoomKingsCourt(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> actionCards = new ArrayList<Card>(); for (Card card : currentPlayer.hand) { if (card instanceof ActionCard) { actionCards.add(card); } } if (!actionCards.isEmpty()) { ActionCardImpl cardToPlay = null; switch (this.type) { case ThroneRoom: cardToPlay = (ActionCardImpl) currentPlayer.controlPlayer.throneRoom_cardToPlay(context); break; case KingsCourt: cardToPlay = (ActionCardImpl) currentPlayer.controlPlayer.kingsCourt_cardToPlay(context); break; case Procession: cardToPlay = (ActionCardImpl) currentPlayer.controlPlayer.procession_cardToPlay(context); break; default: break; } if (cardToPlay != null) { if(!actionCards.contains(cardToPlay)) { Util.playerError(currentPlayer, this.controlCard.name.toString() + " card selection error, ignoring"); } else { context.freeActionInEffect++; cardToPlay.cloneCount = (equals(Cards.kingsCourt) ? 3 : 2); for (int i = 0; i < cardToPlay.cloneCount;) { cardToPlay.numberTimesAlreadyPlayed = i++; cardToPlay.play(game, context, cardToPlay.numberTimesAlreadyPlayed == 0 ? true : false); } cardToPlay.numberTimesAlreadyPlayed = 0; context.freeActionInEffect--; // If the cardToPlay was a knight, and was trashed, reset clonecount if (cardToPlay.isKnight() && !currentPlayer.playedCards.contains(cardToPlay) && game.trashPile.contains(cardToPlay)) { cardToPlay.cloneCount = 1; } if (cardToPlay instanceof DurationCard && !cardToPlay.equals(Cards.tactician)) { // Need to move throning card to NextTurnCards first // (but does not play) if (!this.controlCard.movedToNextTurnPile) { this.controlCard.movedToNextTurnPile = true; int idx = currentPlayer.playedCards.lastIndexOf(this.controlCard); int ntidx = currentPlayer.nextTurnCards.size() - 1; if (idx >= 0 && ntidx >= 0) { currentPlayer.playedCards.remove(idx); currentPlayer.nextTurnCards.add(ntidx, this.controlCard); } } } } if (this.type == Cards.Type.Procession) { if (!cardToPlay.trashOnUse) { currentPlayer.trash(cardToPlay, this.controlCard, context); if (currentPlayer.playedCards.getLastCard() == cardToPlay) { currentPlayer.playedCards.remove(cardToPlay); } if (currentPlayer.nextTurnCards.contains(cardToPlay)) { ((CardImpl) cardToPlay).trashAfterPlay = true; } } Card cardToGain = currentPlayer.controlPlayer.procession_cardToGain(context, 1 + cardToPlay.getCost(context), cardToPlay.costPotion()); if ((cardToGain != null) && (cardToPlay.getCost(context) + 1) == cardToGain.getCost(context)) { currentPlayer.gainNewCard(cardToGain, this.controlCard, context); } } } } } private void mint(MoveContext context, Player currentPlayer) { TreasureCard cardToMint = currentPlayer.controlPlayer.mint_treasureToMint(context); if (cardToMint != null && !currentPlayer.hand.contains(cardToMint)) { Util.playerError(currentPlayer, "Mint treasure selection error, not minting anything."); } if (cardToMint != null) { currentPlayer.reveal(cardToMint, this.controlCard, context); currentPlayer.gainNewCard(cardToMint, this.controlCard, context); } } private void mountebank(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); Card curseCard = null; for (Card card : player.hand) { if (Cards.curse.equals(card)) { curseCard = card; break; } } if (curseCard != null && (player).controlPlayer.mountebank_attack_shouldDiscardCurse(playerContext)) { player.hand.remove(curseCard); player.discard(curseCard, this.controlCard, playerContext); } else { player.gainNewCard(Cards.curse, this.controlCard, playerContext); player.gainNewCard(Cards.copper, this.controlCard, playerContext); } } } } private void rabble(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); ArrayList<Card> topOfTheDeck = new ArrayList<Card>(); List<Card> cardToDiscard = new ArrayList<Card>(); for (int i = 0; i < 3; i++) { Card card = game.draw(player); if (card != null) { player.reveal(card, this.controlCard, playerContext); if (card instanceof TreasureCard || card instanceof ActionCard) { cardToDiscard.add(card); } else { topOfTheDeck.add(card); } } } for (Card c: cardToDiscard) { player.discard(c, this.controlCard, playerContext); } if (!topOfTheDeck.isEmpty()) { Card[] order = (player).controlPlayer.rabble_attack_cardOrder(playerContext, topOfTheDeck.toArray(new Card[topOfTheDeck.size()])); // Check that they returned the right cards boolean bad = false; if (order == null) { bad = true; } else { ArrayList<Card> copy = new ArrayList<Card>(); for (Card card : topOfTheDeck) { copy.add(card); } for (Card card : order) { if (!copy.remove(card)) { bad = true; break; } } if (!copy.isEmpty()) { bad = true; } } if (bad) { Util.playerError(currentPlayer, "Rabble order cards error, ignoring."); order = topOfTheDeck.toArray(new Card[topOfTheDeck.size()]); } // Put the cards back on the deck for (int i = order.length - 1; i >= 0; i--) { player.putOnTopOfDeck(order[i]); } } } } } private void tradeRoute(Game game, MoveContext context, Player currentPlayer) { if (!currentPlayer.hand.isEmpty()) { Card card = currentPlayer.controlPlayer.tradeRoute_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Trade Route card selection error, no card selected or card not in hand, choosing random card to trash"); card = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } context.addGold += game.tradeRouteValue; } private void vault(Game game, MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.vault_cardsToDiscardForGold(context); if (cards != null) { int numberOfCardsDiscarded = 0; for (Card card : cards) { if (currentPlayer.hand.remove(card)) { currentPlayer.discard(card, this.controlCard, context); numberOfCardsDiscarded++; } } if (numberOfCardsDiscarded != cards.length) { Util.playerError(currentPlayer, "Vault discard error, trying to discard cards not in hand, ignoring extra."); } context.addGold += numberOfCardsDiscarded; } for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer) { cards = (player).controlPlayer.vault_cardsToDiscardForCard(context); if (cards != null) { int numberOfCardsDiscarded = 0; if (cards.length > 1 || player.hand.size() == 1) { for (Card card : cards) { if (numberOfCardsDiscarded < 2 && player.hand.remove(card)) { player.discard(card, this.controlCard, null); numberOfCardsDiscarded++; } } } if (numberOfCardsDiscarded != cards.length) { if (cards.length > 2) { Util.playerError(player, "Vault discard error, trying to discard more than 2 cards, discarding first 2"); } else if (cards.length < 2) { Util.playerError(player, "Vault discard error, trying to discard only 1 card, discarding none"); } else { Util.playerError(player, "Vault discard error, trying to discard cards not in hand, ignoring extra."); } } if (numberOfCardsDiscarded == 2) { game.drawToHand(player, this.controlCard); } } } } } private void watchTower(Game game, Player currentPlayer) { while (currentPlayer.hand.size() < 6) { Card draw = game.draw(currentPlayer); if (draw == null) { break; } currentPlayer.hand.add(draw); } } private void farmingVillage(Game game, MoveContext context, Player currentPlayer) { Card draw = null; ArrayList<Card> toDiscard = new ArrayList<Card>(); while ((draw = game.draw(currentPlayer)) != null && !(draw instanceof ActionCard) && !(draw instanceof TreasureCard)) { toDiscard.add(draw); } while (!toDiscard.isEmpty()) { Card c = toDiscard.remove(0); currentPlayer.reveal(c, this.controlCard, context); currentPlayer.discard(c, this.controlCard, context); } if (draw != null) { currentPlayer.reveal(draw, this.controlCard, context); currentPlayer.hand.add(draw); } } private void fortuneTeller(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); ArrayList<Card> cardsToDiscard = new ArrayList<Card>(); Card draw = null; while ((draw = game.draw(player)) != null && !(draw instanceof VictoryCard) && !(draw instanceof CurseCard)) { player.reveal(draw, this.controlCard, playerContext); cardsToDiscard.add(draw); } if (draw != null) { player.reveal(draw, this.controlCard, playerContext); player.putOnTopOfDeck(draw); } for(Card card : cardsToDiscard) { player.discard(card, this.controlCard, playerContext); } } } } private void hamlet(MoveContext context, Player currentPlayer) { Card forAction = currentPlayer.controlPlayer.hamlet_cardToDiscardForAction(context); if (forAction != null) { currentPlayer.hand.remove(forAction); currentPlayer.discard(forAction, this.controlCard, context); context.actions++; } Card forBuy = currentPlayer.controlPlayer.hamlet_cardToDiscardForBuy(context); if (forBuy != null) { currentPlayer.hand.remove(forBuy); currentPlayer.discard(forBuy, this.controlCard, context); context.buys++; } } private void harvest(Game game, MoveContext context, Player currentPlayer) { HashSet<String> cardNames = new HashSet<String>(); List<Card> cardToDiscard = new ArrayList<Card>(); for (int i = 0; i < 4; i++) { Card draw = game.draw(currentPlayer); if (draw == null) { break; } cardNames.add(draw.getName()); currentPlayer.reveal(draw, this.controlCard, context); cardToDiscard.add(draw); } for (Card c: cardToDiscard) { currentPlayer.discard(c, this.controlCard, context); } context.addGold += cardNames.size(); } private void horseTraders(MoveContext context, Player currentPlayer) { Card[] cardsToDiscard = currentPlayer.controlPlayer.horseTraders_cardsToDiscard(context); boolean bad = false; if (cardsToDiscard == null) { bad = true; } else if (currentPlayer.hand.size() < 2 && cardsToDiscard.length != currentPlayer.hand.size()) { bad = true; } else if (cardsToDiscard.length != 2) { bad = true; } else { ArrayList<Card> copy = Util.copy(currentPlayer.hand); for (Card cardToKeep : cardsToDiscard) { if (!copy.remove(cardToKeep)) { bad = true; break; } } } if (bad) { if (currentPlayer.hand.size() >= 2) { Util.playerError(currentPlayer, "Horse Traders discard error, just discarding the first 2."); } cardsToDiscard = new Card[Math.min(2, currentPlayer.hand.size())]; for (int i = 0; i < cardsToDiscard.length; i++) { cardsToDiscard[i] = currentPlayer.hand.get(i); } } for (Card card : cardsToDiscard) { currentPlayer.hand.remove(card); currentPlayer.discard(card, this.controlCard, null); } } private void jester(Game game, MoveContext context, Player currentPlayer) { for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); Card draw = game.draw(targetPlayer); Card virtCard = draw; if (draw == null) { continue; } MoveContext targetContext = new MoveContext(game, targetPlayer); targetPlayer.reveal(draw, this.controlCard, targetContext); targetPlayer.discard(draw, this.controlCard, targetContext); if (Cards.isSupplyCard(draw)) { AbstractCardPile pile; if (draw.isKnight()) { virtCard = Cards.virtualKnight; } else if (draw.isRuins()) { virtCard = Cards.virtualRuins; } pile = game.getPile(virtCard); MoveContext toGainContext = null; if (draw instanceof VictoryCard) { targetPlayer.gainNewCard(Cards.curse, this.controlCard, targetContext); } else { if (!game.isPileEmpty(draw) && (pile.getType() == AbstractCardPile.PileType.SingleCardPile || draw.equals(pile.card()))) { JesterOption option = currentPlayer.controlPlayer.controlPlayer.jester_chooseOption(context, targetPlayer, draw); toGainContext = JesterOption.GainCopy.equals(option) ? context : targetContext; toGainContext.getPlayer().gainNewCard(draw, this.controlCard, toGainContext); } } } } } } private void huntingParty(Game game, MoveContext context, Player currentPlayer) { HashSet<String> cardNames = new HashSet<String>(); for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); cardNames.add(card.getName()); currentPlayer.revealFromHand(card, this.controlCard, context); } ArrayList<Card> toDiscard = new ArrayList<Card>(); Card draw = null; while ((draw = game.draw(currentPlayer)) != null && cardNames.contains(draw.getName())) { currentPlayer.reveal(draw, this.controlCard, context); toDiscard.add(draw); } if (draw != null) { currentPlayer.reveal(draw, this.controlCard, context); currentPlayer.hand.add(draw); } while (!toDiscard.isEmpty()) { currentPlayer.discard(toDiscard.remove(0), this.controlCard, null); } } private void crossroads(Game game, MoveContext context, Player currentPlayer) { int victoryCards = 0; for(Card c : currentPlayer.getHand()) { if(c instanceof VictoryCard) { victoryCards++; } } for(int i=0; i < victoryCards; i++) { game.drawToHand(currentPlayer, this.controlCard); } int crossroadsPlayed = this.controlCard.numberTimesAlreadyPlayed; crossroadsPlayed += context.countCardsInPlay(Cards.crossroads); if (crossroadsPlayed <= 1) { context.actions += 3; } } private void trustySteed(Game game, MoveContext context, Player currentPlayer) { TrustySteedOption[] options = currentPlayer.controlPlayer.trustySteed_chooseOptions(context); if (options == null || options.length != 2 || options[0] == options[1]) { Util.playerError(currentPlayer, "Trusty Steed options error, ignoring."); } else { // Trusty Steed must do options in the order listed. Arrays.sort(options); for (TrustySteedOption option : options) { if (option == TrustySteedOption.AddActions) { context.actions += 2; } else if (option == TrustySteedOption.AddCards) { for (int i = 0; i < 2; i++) { game.drawToHand(currentPlayer, this.controlCard); } } else if (option == TrustySteedOption.AddGold) { context.addGold += 2; } else if (option == TrustySteedOption.GainSilvers) { for (int i = 0; i < 4; i++) { if(!currentPlayer.gainNewCard(Cards.silver, this.controlCard, context)) { break; } } while (currentPlayer.getDeckSize() > 0) { currentPlayer.discard(currentPlayer.deck.remove(0), this.controlCard, null); } } } } } private void duchess(Game game) { for (Player targetPlayer : game.getPlayersInTurnOrder()) { Card c = game.draw(targetPlayer); if(c != null) { MoveContext targetPlayerContext = new MoveContext(game, targetPlayer); boolean discard = (targetPlayer).controlPlayer.duchess_shouldDiscardCardFromTopOfDeck(targetPlayerContext, c); if(discard) { targetPlayer.discard(c, this.controlCard, targetPlayerContext); } else { targetPlayer.putOnTopOfDeck(c); } } } } private void spiceMerchant(Game game, MoveContext context, Player currentPlayer) { boolean handContainsTreasure = false; for(Card c : currentPlayer.hand) { if(c instanceof TreasureCard) { handContainsTreasure = true; break; } } if(handContainsTreasure) { TreasureCard treasure = currentPlayer.controlPlayer.spiceMerchant_treasureToTrash(context); if(treasure != null) { if(!currentPlayer.hand.contains(treasure)) { Util.playerError(currentPlayer, "Spice Merchant returned invalid card to trash from hand, ignoring."); } else { currentPlayer.hand.remove(treasure); currentPlayer.trash(treasure, this.controlCard, context); SpiceMerchantOption option = currentPlayer.controlPlayer.spiceMerchant_chooseOption(context); if(option == SpiceMerchantOption.AddCardsAndAction) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); context.actions += 1; } else { context.addGold += 2; context.buys += 1; } } } } } private void trader(MoveContext context, Player currentPlayer) { if(currentPlayer.hand.size() > 0) { Card card = currentPlayer.controlPlayer.trader_cardToTrash(context); if(card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Trader card to trash invalid, picking one"); card = currentPlayer.hand.get(0); } int cost = card.getCost(context); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); for(int i=0; i < cost; i++) { if(!currentPlayer.gainNewCard(Cards.silver, this.controlCard, context)) { break; } } } } private void cartographer(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> topOfTheDeck = new ArrayList<Card>(); for (int i = 0; i < 4; i++) { Card card = game.draw(currentPlayer); if (card != null) { topOfTheDeck.add(card); } } if (topOfTheDeck.size() > 0) { Card[] cardsToDiscard = currentPlayer.controlPlayer.cartographer_cardsFromTopOfDeckToDiscard(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()])); if(cardsToDiscard != null) { for(Card toDiscard : cardsToDiscard) { if(topOfTheDeck.remove(toDiscard)) { currentPlayer.discard(toDiscard, this.controlCard, null); } else { Util.playerError(currentPlayer, "Cartographer returned invalid card to discard, ignoring"); } } } if (topOfTheDeck.size() > 0) { Card[] order; if(topOfTheDeck.size() == 1) { order = topOfTheDeck.toArray(new Card[topOfTheDeck.size()]); } else { order = currentPlayer.controlPlayer.cartographer_cardOrder(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()])); // Check that they returned the right cards boolean bad = false; if (order == null) { bad = true; } else { ArrayList<Card> copy = new ArrayList<Card>(); for (Card card : topOfTheDeck) { copy.add(card); } for (Card card : order) { if (!copy.remove(card)) { bad = true; break; } } if (!copy.isEmpty()) { bad = true; } } if (bad) { Util.playerError(currentPlayer, "Cartographer order cards error, ignoring."); order = topOfTheDeck.toArray(new Card[topOfTheDeck.size()]); } } // Put the cards back on the deck for (int i = order.length - 1; i >= 0; i--) { currentPlayer.putOnTopOfDeck(order[i]); } } } } private void embassy(MoveContext context, Player currentPlayer) { if (currentPlayer.hand.size() == 0) { return; } Card[] cards; if (currentPlayer.hand.size() > 3) { cards = currentPlayer.controlPlayer.embassy_cardsToDiscard(context); } else { cards = currentPlayer.getHand().toArray(); } boolean bad = false; if (cards == null) { bad = true; } else if (cards.length > 3) { bad = true; } else { ArrayList<Card> handCopy = Util.copy(currentPlayer.hand); for (Card card : cards) { if (!handCopy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(currentPlayer, "Embassy discard error, discarding first 3 cards."); cards = new Card[3]; for (int i = 0; i < cards.length; i++) { cards[i] = currentPlayer.hand.get(i); } } for (int i = 0; i < cards.length; i++) { currentPlayer.hand.remove(cards[i]); currentPlayer.reveal(cards[i], this.controlCard, context); currentPlayer.discard(cards[i], this.controlCard, null); } } private void inn(MoveContext context, Player currentPlayer) { Card[] cards; if (currentPlayer.hand.size() > 2) { cards = currentPlayer.controlPlayer.inn_cardsToDiscard(context); } else { cards = currentPlayer.getHand().toArray(); } boolean bad = false; if (cards == null) { bad = true; } else if (cards.length > 2) { bad = true; } else { ArrayList<Card> handCopy = Util.copy(currentPlayer.hand); for (Card card : cards) { if (!handCopy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(currentPlayer, "Inn discard error, discarding first 2 cards."); cards = new Card[2]; for (int i = 0; i < cards.length; i++) { cards[i] = currentPlayer.hand.get(i); } } for (int i = 0; i < cards.length; i++) { currentPlayer.hand.remove(cards[i]); currentPlayer.reveal(cards[i], this.controlCard, context); currentPlayer.discard(cards[i], this.controlCard, null); } } private void mandarin(MoveContext context, Player currentPlayer) { if(currentPlayer.hand.size() > 0) { Card toTopOfDeck = currentPlayer.controlPlayer.mandarin_cardToReplace(context); if (toTopOfDeck == null) { Util.playerError(currentPlayer, "No card selected for Mandarin, returning random card to the top of the deck."); toTopOfDeck = Util.randomCard(currentPlayer.hand); } currentPlayer.reveal(toTopOfDeck, this.controlCard, context); currentPlayer.hand.remove(toTopOfDeck); currentPlayer.putOnTopOfDeck(toTopOfDeck); } } private void possession(MoveContext context) { // TODO: Temp hack to prevent AI from playing possession, even though human player can, since it only half works // (AI will make decisions while possessed, but will try to make "good" ones) if (context.player.isPossessed()) { context.game.nextPossessionsToProcess++; context.game.nextPossessingPlayer = context.player; } else { context.game.possessionsToProcess++; context.game.possessingPlayer = context.player; } } private void steward(Game game, MoveContext context, Player currentPlayer) { Player.StewardOption option = currentPlayer.controlPlayer.steward_chooseOption(context); if (option == null) { Util.playerError(currentPlayer, "Steward option error, ignoring."); } else { if (option == Player.StewardOption.AddGold) { context.addGold += 2; } else if (option == Player.StewardOption.AddCards) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); } else if (option == Player.StewardOption.TrashCards) { CardList hand = currentPlayer.getHand(); if (hand.size() == 0) { return; } Card[] cards = currentPlayer.controlPlayer.steward_cardsToTrash(context); boolean bad = false; if (cards == null) { bad = true; } else if (cards.length != 2) { if (hand.size() >= 2 || cards.length != hand.size()) { bad = true; } } else { ArrayList<Card> copy = Util.copy(currentPlayer.hand); for (Card card : cards) { if (!copy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(currentPlayer, "Steward trash error, picking first two cards."); if (hand.size() >= 2) { cards = new Card[2]; } else { cards = new Card[hand.size()]; } for (int i = 0; i < cards.length; i++) { cards[i] = hand.get(i); } } for (Card card : cards) { currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } } } } private void thief(Game game, MoveContext context, Player currentPlayer) { ArrayList<TreasureCard> trashed = new ArrayList<TreasureCard>(); for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext targetContext = new MoveContext(game, targetPlayer); ArrayList<TreasureCard> treasures = new ArrayList<TreasureCard>(); List<Card> cardsToDiscard = new ArrayList<Card>(); for (int i = 0; i < 2; i++) { Card card = game.draw(targetPlayer); if (card != null) { targetPlayer.reveal(card, this.controlCard, targetContext); if (card instanceof TreasureCard) { treasures.add((TreasureCard) card); } else { cardsToDiscard.add(card); } } } for (Card c: cardsToDiscard) { targetPlayer.discard(c, this.controlCard, targetContext); } TreasureCard cardToTrash = null; if (treasures.size() == 1) { cardToTrash = treasures.get(0); } else if (treasures.size() == 2) { if (treasures.get(0).equals(treasures.get(1))) { cardToTrash = treasures.get(0); targetPlayer.discard(treasures.remove(1), this.controlCard, targetContext); } else { cardToTrash = currentPlayer.controlPlayer.thief_treasureToTrash(context, treasures.toArray(new TreasureCard[] {})); } for (TreasureCard treasure : treasures) { if (!treasure.equals(cardToTrash)) { targetPlayer.discard(treasure, this.controlCard, targetContext); } } } if (cardToTrash != null) { targetPlayer.trash(cardToTrash, this.controlCard, targetContext); trashed.add(cardToTrash); } } } if (trashed.size() > 0) { TreasureCard[] treasuresToGain = currentPlayer.controlPlayer.thief_treasuresToGain(context, trashed.toArray(new TreasureCard[] {})); if (treasuresToGain != null) { for (TreasureCard treasure : treasuresToGain) { currentPlayer.gainCardAlreadyInPlay(treasure, this.controlCard, context); game.trashPile.remove(treasure); } } } } private void militia(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.militia_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } private void remodel(MoveContext context, Player currentPlayer) { if(currentPlayer.getHand().size() > 0) { Card cardToTrash = currentPlayer.controlPlayer.remodel_cardToTrash(context); if (cardToTrash == null) { Util.playerError(currentPlayer, "Remodel did not return a card to trash, trashing random card."); cardToTrash = Util.randomCard(currentPlayer.getHand()); } int cost = -1; boolean potion = false; for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(cardToTrash)) { cost = playersCard.getCost(context); potion = playersCard.costPotion(); playersCard = currentPlayer.hand.remove(i); currentPlayer.trash(playersCard, this.controlCard, context); break; } } if (cost == -1) { Util.playerError(currentPlayer, "Remodel returned invalid card, ignoring."); return; } cost += 2; Card card = currentPlayer.controlPlayer.remodel_cardToObtain(context, cost, potion); if (card != null) { // check cost if (card.getCost(context) > cost) { Util.playerError(currentPlayer, "Remodel new card costs too much, ignoring."); } else if (card.costPotion() && !potion) { Util.playerError(currentPlayer, "Remodel new card costs potion, ignoring."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Remodel new card is invalid, ignoring."); } } } } } private void remake(MoveContext context, Player currentPlayer) { for (int i = 0; i < 2; i++) { if (currentPlayer.getHand().size() == 0) { return; } Card card = currentPlayer.controlPlayer.remake_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Remake trash error, remaking a random card."); card = Util.randomCard(currentPlayer.hand); } int value = card.getCost(context) + 1; boolean potion = card.costPotion(); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); card = currentPlayer.controlPlayer.remake_cardToObtain(context, value, potion); if (card != null) { if (card.getCost(context) != value || card.costPotion() != potion) { Util.playerError(currentPlayer, "Remake error, new card must cost exactly " + value + "."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Remake error, pile is empty or card is not in the game."); } } } } } private void menagerie(Game game, MoveContext context, Player currentPlayer) { HashSet<String> cardNames = new HashSet<String>(); boolean distinct = true; for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); currentPlayer.reveal(card, this.controlCard, context); distinct &= cardNames.add(card.getName()); } int numCards = distinct ? 3 : 1; for (int i = 0; i < numCards; i++) { Card draw = game.draw(currentPlayer); if (draw != null) { currentPlayer.hand.add(draw); } } } private void tournament(Game game, MoveContext context, Player currentPlayer) { boolean opponentsRevealedProvince = false; boolean currentPlayerRevealed = false; for (Player player : game.getPlayersInTurnOrder()) { Card province = null; for (Card card : player.hand) { if (card.equals(Cards.province)) { province = card; break; } } if (province != null) { if (player == currentPlayer) { currentPlayerRevealed = currentPlayer.controlPlayer.tournament_shouldRevealProvince(context); if (currentPlayerRevealed) { currentPlayer.reveal(province, this.controlCard, context); player.hand.remove(province); currentPlayer.discard(province, this.controlCard, context); } } else { if (player.controlPlayer.tournament_shouldRevealProvince(context)) { player.reveal(province, this.controlCard, new MoveContext(game, player)); opponentsRevealedProvince = true; } } } } if (currentPlayerRevealed) { TournamentOption option = currentPlayer.controlPlayer.tournament_chooseOption(context); if (option == TournamentOption.GainPrize) { Card prize = currentPlayer.controlPlayer.tournament_choosePrize(context); if (prize != null && prize.isPrize()) { currentPlayer.gainNewCard(prize, this.controlCard, context); } else { Util.playerError(currentPlayer, "Tournament error, invalid prize"); } } else if (option == TournamentOption.GainDuchy) { currentPlayer.gainNewCard(Cards.duchy, this.controlCard, context); } } if (!opponentsRevealedProvince) { Card draw = game.draw(currentPlayer); if (draw != null) { currentPlayer.hand.add(draw); } context.addGold++; } } private void tradingPost(MoveContext context, Player currentPlayer) { if (currentPlayer.getHand().size() == 0) { return; } ArrayList<Card> handCopy = Util.copy(currentPlayer.getHand()); Card[] cardsToTrash = currentPlayer.controlPlayer.tradingPost_cardsToTrash(context); // Trash forced, pick cards randomly if not selected boolean bad = false; if (cardsToTrash == null) { bad = true; } else if (handCopy.size() < 2 && cardsToTrash.length != handCopy.size()) { bad = true; } else if (handCopy.size() >= 2 && cardsToTrash.length != 2) { bad = true; } else { ArrayList<Card> copyForTrash = Util.copy(currentPlayer.getHand()); for (Card cardToKeep : cardsToTrash) { if (!copyForTrash.remove(cardToKeep)) { bad = true; break; } } } if (bad) { if (handCopy.size() >= 2) { Util.playerError(currentPlayer, "TradingPost trash error, just trashing the first 2."); } cardsToTrash = new Card[Math.min(2, handCopy.size())]; for (int i = 0; i < cardsToTrash.length; i++) { cardsToTrash[i] = handCopy.get(i); } } for (int i = cardsToTrash.length - 1; i >= 0 ; i--) { currentPlayer.hand.remove(cardsToTrash[i]); currentPlayer.trash(cardsToTrash[i], this.controlCard, context); } if (cardsToTrash.length == 2) { currentPlayer.gainNewCard(Cards.silver, this.controlCard, context); } } private void masquerade(Game game, MoveContext context, Player currentPlayer) { Card[] passedCards = new Card[Game.players.length]; for (int i = 0; i < Game.players.length; i++) { Player player = Game.players[i]; if (player.getHand().size() == 0) { continue; } Card card = player.controlPlayer.masquerade_cardToPass(new MoveContext(game, player)); if (card == null || !(player).hand.contains(card)) { Util.playerError(player, "Masquerade pass card error, picking random card to pass."); card = Util.randomCard(player.getHand()); } // TODO Should this.controlCard send some new type of event, not trashed, but passed maybe? if (card != null) { (player).hand.remove(card); passedCards[i] = card; } } for (int i = 0; i < Game.players.length; i++) { int next = i + 1; if (next >= Game.players.length) { next = 0; } Player nextPlayer = Game.players[next]; Card card = passedCards[i]; if (card != null) { nextPlayer.hand.add(card); if (nextPlayer instanceof GameEventListener) { GameEvent event = new GameEvent(GameEvent.Type.CardObtained, new MoveContext(game, nextPlayer)); event.card = card; event.responsible = this.controlCard; event.newCard = false; ((GameEventListener) nextPlayer).gameEvent(event); } // nextPlayer.gainCardAlreadyInPlay(card, this.controlCard, new MoveContext(game, nextPlayer)); } } Card toTrash = currentPlayer.controlPlayer.masquerade_cardToTrash(context); if (toTrash != null) { if (currentPlayer.hand.contains(toTrash)) { currentPlayer.hand.remove(toTrash); currentPlayer.trash(toTrash, this.controlCard, context); } else { Util.playerError(currentPlayer, "Masquerade trash error, card not in hand, ignoring."); } } } private void miningVillage(MoveContext context, Player currentPlayer) { if (!this.controlCard.movedToNextTurnPile) { if (currentPlayer.controlPlayer.miningVillage_shouldTrashMiningVillage(context)) { context.addGold += 2; this.controlCard.movedToNextTurnPile = true; currentPlayer.trash(this.controlCard, null, context); currentPlayer.playedCards.remove(currentPlayer.playedCards.lastIndexOf(this.controlCard)); } } } private void minion(Game game, MoveContext context, Player currentPlayer) { ArrayList<Player> playersToAttack = new ArrayList<Player>(); for (Player player : game.getPlayersInTurnOrder()) { if (player == currentPlayer || !Util.isDefendedFromAttack(game, player, this.controlCard)) { playersToAttack.add(player); if (player != currentPlayer) { player.attacked(this.controlCard, context); } } } Player.MinionOption option = currentPlayer.controlPlayer.minion_chooseOption(context); if (option == null) { Util.playerError(currentPlayer, "Minion option error, choosing to add gold."); option = Player.MinionOption.AddGold; } if (option == Player.MinionOption.AddGold) { context.addGold += 2; } else if (option == Player.MinionOption.RolloverCards) { for (Player player : playersToAttack) { if (player == currentPlayer || player.hand.size() >= 5) { MoveContext targetContext = new MoveContext(game, player); while (!player.hand.isEmpty()) { player.discard(player.hand.remove(0), this.controlCard, targetContext); } game.drawToHand(player, this.controlCard); game.drawToHand(player, this.controlCard); game.drawToHand(player, this.controlCard); game.drawToHand(player, this.controlCard); } } } } private void pawn(Game game, MoveContext context, Player currentPlayer) { Player.PawnOption[] options = currentPlayer.controlPlayer.pawn_chooseOptions(context); if (options == null || options.length != 2 || options[0] == options[1]) { Util.playerError(currentPlayer, "Pawn options error, ignoring."); } else { for (Player.PawnOption option : options) { if (option == Player.PawnOption.AddAction) { context.actions++; } else if (option == Player.PawnOption.AddBuy) { context.buys++; } else if (option == Player.PawnOption.AddCard) { game.drawToHand(currentPlayer, this.controlCard); } else if (option == Player.PawnOption.AddGold) { context.addGold++; } } } } private void saboteur(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); playerContext.cardCostModifier = context.cardCostModifier; ArrayList<Card> toDiscard = new ArrayList<Card>(); Card draw; while ((draw = game.draw(player)) != null) { if (draw.getCost(context) >= 3) { int value = draw.getCost(context); value -= 2; if (value < 0) { value = 0; } boolean potion = draw.costPotion(); player.trash(draw, this.controlCard, playerContext); Card card = (player).controlPlayer.saboteur_cardToObtain(playerContext, value, potion); if (card != null) { if (card.getCost(context) > value || (card.costPotion() && !potion) || !Cards.isSupplyCard(card)) { Util.playerError(currentPlayer, "Saboteur obtain error, ignoring."); } else { if(!player.gainNewCard(card, this.controlCard, playerContext)) { Util.playerError(currentPlayer, "Saboteur obtain error, ignoring."); } } } break; } else { player.reveal(draw, this.controlCard, playerContext); toDiscard.add(draw); } } while (!toDiscard.isEmpty()) { player.discard(toDiscard.remove(0), this.controlCard, null); } } } } private void shantyTown(Game game, MoveContext context, Player currentPlayer) { boolean actions = false; for (Card card : currentPlayer.hand) { currentPlayer.reveal(card, this.controlCard, context); if (card instanceof ActionCard) { actions = true; } } if (!actions) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); } } private void baron(MoveContext context, Player currentPlayer) { boolean discard = false; for (Card cardToCheck : currentPlayer.hand) { if (cardToCheck.equals(Cards.estate)) { discard = currentPlayer.controlPlayer.baron_shouldDiscardEstate(context); break; } } if (discard) { Card card = currentPlayer.hand.get(Cards.estate); currentPlayer.hand.remove(Cards.estate); currentPlayer.discard(card, this.controlCard, null); context.addGold += 4; } else { currentPlayer.gainNewCard(Cards.estate, this.controlCard, context); } } private void courtyard(MoveContext context, Player currentPlayer) { // TODO do this.controlCard check at the top of the block for EVERY Util... if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.courtyard_cardToPutBackOnDeck(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Courtyard error, just putting back a random card."); card = Util.randomCard(currentPlayer.hand); } currentPlayer.putOnTopOfDeck(currentPlayer.hand.remove(currentPlayer.hand.indexOf(card))); } } private void spyAndScryingPool(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { // Note that this.controlCard is the opposite check of other attacks, the spy/scrying pool lets // the current player look at their own deck which is a good thing, so always // allow that if (player == currentPlayer || (!Util.isDefendedFromAttack(game, player, this.controlCard))) { if (player != currentPlayer) { player.attacked(this.controlCard, context); } MoveContext playerContext = new MoveContext(game, player); Card card = game.draw(player); if (card != null) { player.reveal(card, this.controlCard, playerContext); boolean discard = false; if(equals(Cards.spy)) { discard = currentPlayer.controlPlayer.spy_shouldDiscard(context, player, card); } else if (equals(Cards.scryingPool)) { discard = currentPlayer.controlPlayer.scryingPool_shouldDiscard(context, player, card); } if (discard) { player.discard(card, this.controlCard, playerContext); } else { // put it back player.putOnTopOfDeck(card); } } } } if(equals(Cards.scryingPool)) { ArrayList<Card> cardsToPutInHand = new ArrayList<Card>(); Card draw = null; while ((draw = game.draw(currentPlayer)) != null) { currentPlayer.reveal(draw, this.controlCard, new MoveContext(game, currentPlayer)); cardsToPutInHand.add(draw); if(!(draw instanceof ActionCard)) { break; } } for(Card card : cardsToPutInHand) { currentPlayer.hand.add(card); } } } private void conspirator(Game game, MoveContext context, Player currentPlayer) { if (context.actionsPlayedSoFar >= 3) { context.actions++; game.drawToHand(currentPlayer, this.controlCard); } } private void wishingWell(Game game, MoveContext context, Player currentPlayer) { if (currentPlayer.deck.size() > 0 || currentPlayer.discard.size() > 0) { // Only allow a guess if there are cards in the deck or discard pile // Create a list of possible cards to guess, using the player's hand, discard pile, and deck // (even though the player could technically name a card he doesn't have) ArrayList<Card> options = currentPlayer.getAllCards(); Collections.sort(options, new Util.CardNameComparator()); if (!options.isEmpty()) { Card card = currentPlayer.controlPlayer.wishingWell_cardGuess(context, options); currentPlayer.controlPlayer.namedCard(card, this.controlCard, context); Card draw = game.draw(currentPlayer); if (card != null && draw != null) { currentPlayer.reveal(draw, this.controlCard, context); if (card.equals(draw)) { currentPlayer.hand.add(draw); } else { currentPlayer.putOnTopOfDeck(draw); } } } } } private void upgrade(MoveContext context, Player currentPlayer) { if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.upgrade_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Upgrade trash error, upgrading a random card."); card = Util.randomCard(currentPlayer.hand); } int value = card.getCost(context) + 1; boolean potion = card.costPotion(); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); card = currentPlayer.controlPlayer.upgrade_cardToObtain(context, value, potion); if (card != null) { if (card.getCost(context) != value || card.costPotion() != potion) { Util.playerError(currentPlayer, "Upgrade error, new card does not cost value of the old card +1."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Upgrade error, pile is empty or card is not in the game."); } } } } } private void ironworks(Game game, MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.ironworks_cardToObtain(context); if (card != null && card.getCost(context) <= 4 && !card.costPotion()) { if (currentPlayer.gainNewCard(card, this.controlCard, context)) { if (card instanceof ActionCard) { context.actions++; } if (card instanceof TreasureCard) { context.addGold++; } if (card instanceof VictoryCard) { game.drawToHand(currentPlayer, this.controlCard); } } } } private void nobles(Game game, MoveContext context, Player currentPlayer) { Player.NoblesOption option = currentPlayer.controlPlayer.nobles_chooseOptions(context); if (option == null) { Util.playerError(currentPlayer, "Nobles option error, ignoring."); } else { if (option == Player.NoblesOption.AddActions) { context.actions += 2; } else if (option == Player.NoblesOption.AddCards) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); } } } private void tribute(Game game, MoveContext context, Player currentPlayer) { Card[] revealedCards = new Card[2]; Player nextPlayer = game.getNextPlayer(); revealedCards[0] = game.draw(nextPlayer); revealedCards[1] = game.draw(nextPlayer); if (revealedCards[0] != null) { nextPlayer.reveal(revealedCards[0], this.controlCard, new MoveContext(game, nextPlayer)); (nextPlayer).discard(revealedCards[0], this.controlCard, null); } if (revealedCards[1] != null) { nextPlayer.reveal(revealedCards[1], this.controlCard, new MoveContext(game, nextPlayer)); (nextPlayer).discard(revealedCards[1], this.controlCard, null); } // "For each differently named card revealed..." if (revealedCards[0] != null && revealedCards[0].equals(revealedCards[1])) { revealedCards[1] = null; } for (Card card : revealedCards) { if (card != null && !card.equals(Cards.curse)) { if (card instanceof ActionCard) { context.actions += 2; } if (card instanceof TreasureCard) { context.addGold += 2; } if (card instanceof VictoryCard) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); } } } } private void copperSmith(MoveContext context) { context.coppersmithsPlayed++; } private void swindler(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); Card draw = game.draw(player); if (draw != null) { player.trash(draw, this.controlCard, playerContext); Card card = currentPlayer.controlPlayer.swindler_cardToSwitch(context, draw.getCost(context), draw.costPotion()); boolean bad = false; if (card == null) { // Check that there are no cards that are possible to trade for... for (Card thisCard : context.getCardsInGame()) { if (Cards.isSupplyCard(thisCard) && !game.isPileEmpty(thisCard) && thisCard.getCost(context) == draw.getCost(context) && thisCard.costPotion() == draw.costPotion()) { bad = true; break; } } } else if (!Cards.isSupplyCard(card) || game.isPileEmpty(card) || card.getCost(context) != draw.getCost(context) || card.costPotion() != draw.costPotion()) { bad = true; } if (bad) { Util.playerError(currentPlayer, "Swindler swap card error, picking a random card."); ArrayList<Card> possible = new ArrayList<Card>(); for (Card thisCard : context.getCardsInGame()) { if (Cards.isSupplyCard(thisCard) && !game.isPileEmpty(thisCard) && thisCard.getCost(context) == draw.getCost(context) && thisCard.costPotion() == draw.costPotion()) { possible.add(thisCard); } } card = Util.randomCard(possible); } if (card != null) { player.gainNewCard(card, this.controlCard, playerContext); } } } } } private void goons(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.goons_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } private void youngwitch(Game game, MoveContext context, Player currentPlayer) { Card[] cardsToDiscard = currentPlayer.controlPlayer.youngWitch_cardsToDiscard(context); boolean bad = false; if (cardsToDiscard == null) { bad = true; } else if (currentPlayer.hand.size() < 2 && cardsToDiscard.length != currentPlayer.hand.size()) { bad = true; } else if (cardsToDiscard.length != 2) { bad = true; } else { ArrayList<Card> copy = Util.copy(currentPlayer.hand); for (Card cardToKeep : cardsToDiscard) { if (!copy.remove(cardToKeep)) { bad = true; break; } } } if (bad) { if (currentPlayer.hand.size() >= 2) { Util.playerError(currentPlayer, "Young Witch discard error, just discarding the first 2."); } cardsToDiscard = new Card[Math.min(2, currentPlayer.hand.size())]; for (int i = 0; i < cardsToDiscard.length; i++) { cardsToDiscard[i] = currentPlayer.hand.get(i); } } for (Card card : cardsToDiscard) { currentPlayer.discard(card, this.controlCard, null); currentPlayer.hand.remove(card); } for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !isDefendedFromAttack(game, targetPlayer, this.controlCard)) { if (targetPlayer.hand.contains(game.baneCard) && game.pileSize(Cards.curse) > 0 && targetPlayer.revealBane(context)) { targetPlayer.reveal(game.baneCard, this.controlCard, new MoveContext(game, targetPlayer)); } else { targetPlayer.attacked(this.controlCard, context); targetPlayer.gainNewCard(Cards.curse, this.controlCard, new MoveContext(game, targetPlayer)); } } } } private void followers(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainNewCard(Cards.estate, this.controlCard, context); for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); player.gainNewCard(Cards.curse, this.controlCard, playerContext); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.followers_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } private void stables(Game game, MoveContext context, Player currentPlayer) { boolean valid = false; for(Card c : currentPlayer.hand) { if(c instanceof TreasureCard) { valid = true; } } if(valid) { TreasureCard toDiscard = currentPlayer.controlPlayer.stables_treasureToDiscard(context); // this.controlCard is optional, so ignore it if it's null or invalid if (toDiscard != null && currentPlayer.hand.contains(toDiscard)) { currentPlayer.hand.remove(toDiscard); currentPlayer.reveal(toDiscard, this.controlCard, context); currentPlayer.discard(toDiscard, this.controlCard, context); context.actions++; for (int i = 0; i < 3; i++) { if(!game.drawToHand(currentPlayer, this.controlCard)) { break; } } } } } private void margrave(Game game, MoveContext context, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); game.drawToHand(player, this.controlCard); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.margrave_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } private void oracle(Game game, MoveContext context, Player currentPlayer) { for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer == currentPlayer || !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext targetContext = new MoveContext(game, targetPlayer); ArrayList<Card> cards = new ArrayList<Card>(); for(int i=0; i < 2; i++) { Card c = game.draw(targetPlayer); if(c == null) { break; } cards.add(c); } if(cards.size() > 0) { if(currentPlayer.controlPlayer.oracle_shouldDiscard(context, targetPlayer, cards)) { for(Card c : cards) { targetPlayer.discard(c, this.controlCard, targetContext); } } else { Card[] order = (targetPlayer).controlPlayer.oracle_orderCards(context, cards.toArray(new Card[cards.size()])); boolean bad = false; if (order == null || order.length != cards.size()) { bad = true; } else { ArrayList<Card> orderArray = new ArrayList<Card>(); for (Card card : order) { orderArray.add(card); if (!cards.contains(card)) { bad = true; } } for (Card card : cards) { if (!orderArray.contains(card)) { bad = true; } } } if (bad) { Util.playerError(targetPlayer, "Oracle order cards error, ignoring."); order = cards.toArray(new Card[cards.size()]); } for (int i = order.length - 1; i >= 0; i--) { targetPlayer.putOnTopOfDeck(order[i]); } } } } } for(int i=0; i < 2; i++) { game.drawToHand(currentPlayer, this.controlCard); } } private void oasis(MoveContext context, Player currentPlayer) { if(currentPlayer.hand.size() > 0) { Card cardToDiscard = currentPlayer.controlPlayer.oasis_cardToDiscard(context); if(cardToDiscard == null || !currentPlayer.hand.contains(cardToDiscard)) { Util.playerError(currentPlayer, "Returned an invalid card to discard with Oasis, picking one for you."); cardToDiscard = currentPlayer.hand.get(0); } currentPlayer.hand.remove(cardToDiscard); currentPlayer.reveal(cardToDiscard, this.controlCard, context); currentPlayer.discard(cardToDiscard, this.controlCard, null); } } private void jackOfAllTrades(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainNewCard(Cards.silver, this.controlCard, context); Card c = game.draw(currentPlayer); if(c != null) { boolean discard = currentPlayer.controlPlayer.jackOfAllTrades_shouldDiscardCardFromTopOfDeck(context, c); if(discard) { currentPlayer.discard(c, this.controlCard, context); } else { currentPlayer.putOnTopOfDeck(c); } } while(currentPlayer.hand.size() < 5) { if(!game.drawToHand(currentPlayer, this.controlCard)) { break; } } Card cardToTrash = currentPlayer.controlPlayer.jackOfAllTrades_nonTreasureToTrash(context); if(cardToTrash != null) { if(!currentPlayer.hand.contains(cardToTrash) || cardToTrash instanceof TreasureCard) { Util.playerError(currentPlayer, "Jack of All Trades returned invalid card to trash from hand, ignoring."); } else { currentPlayer.hand.remove(cardToTrash); currentPlayer.trash(cardToTrash, this.controlCard, context); } } } private void develop(MoveContext context, Player currentPlayer) { if(currentPlayer.hand.size() > 0) { Card cardToTrash = currentPlayer.controlPlayer.develop_cardToTrash(context); if(!currentPlayer.hand.contains(cardToTrash)) { Util.playerError(currentPlayer, "Returned an invalid card to trash with Develop, picking one for you."); cardToTrash = currentPlayer.hand.get(0); } int trashedCardCost = cardToTrash.getCost(context); boolean trashedCardPotion = cardToTrash.costPotion(); Card lowCardToGain = null; Card highCardToGain = null; if(context.isNewCardAvailable(trashedCardCost - 1, trashedCardPotion)) { lowCardToGain = currentPlayer.controlPlayer.develop_lowCardToGain(context, trashedCardCost - 1, trashedCardPotion); if (lowCardToGain == null) { lowCardToGain = Util.randomCard(context.getAvailableCards(trashedCardCost - 1, trashedCardPotion)); } } if(context.isNewCardAvailable(trashedCardCost + 1, trashedCardPotion)) { highCardToGain = currentPlayer.controlPlayer.develop_highCardToGain(context, trashedCardCost + 1, trashedCardPotion); if (highCardToGain == null) { highCardToGain = Util.randomCard(context.getAvailableCards(trashedCardCost + 1, trashedCardPotion)); } } ArrayList<Card> cards = new ArrayList<Card>(); if(lowCardToGain != null) { cards.add(lowCardToGain); } if(highCardToGain != null) { cards.add(highCardToGain); } Card[] cardsToGain = null; if(cards.size() > 0) { cardsToGain = cards.toArray(new Card[cards.size()]); if(cards.size() > 1) { cardsToGain = currentPlayer.controlPlayer.develop_orderCards(context, cardsToGain); } } if(cardsToGain == null) { cardsToGain = new Card[0]; } currentPlayer.hand.remove(cardToTrash); currentPlayer.trash(cardToTrash, this.controlCard, context); boolean bad = false; if(cardsToGain.length == 0) { for(Card c : context.getCardsInGame()) { if((c.getCost(context) == trashedCardCost - 1 || c.getCost(context) == trashedCardCost + 1) && context.getCardsLeftInPile(c) > 0) { bad = true; } } } else if(cardsToGain.length == 1) { if(cardsToGain[0].getCost(context) != trashedCardCost -1 && cardsToGain[0].getCost(context) != trashedCardCost + 1) { bad = true; } else { int costToCheck; if(cardsToGain[0].getCost(context) == trashedCardCost - 1) { costToCheck = trashedCardCost + 1; } else { costToCheck = trashedCardCost - 1; } for(Card c : context.getCardsInGame()) { if(c.getCost(context) == costToCheck && context.getCardsLeftInPile(c) > 0) { bad = true; } } } } else if(cardsToGain.length == 2) { Card lowCard = (cardsToGain[0].getCost(context) <= cardsToGain[1].getCost(context))?cardsToGain[0]:cardsToGain[1]; Card highCard = (cardsToGain[0].getCost(context) > cardsToGain[1].getCost(context))?cardsToGain[0]:cardsToGain[1]; if(lowCard.getCost(context) != trashedCardCost -1 && highCard.getCost(context) != trashedCardCost + 1) { bad = true; } } else { bad = true; } for(Card c : cardsToGain) { if(context.getCardsLeftInPile(c) == 0) { bad = true; } } if(bad) { //TODO: should just gain random cards, if there are any valid ones Util.playerError(currentPlayer, "Returned invalid cards to gain with Develop, doing nothing."); } else { for (int i = cardsToGain.length - 1; i >= 0; i--) { Card c = cardsToGain[i]; currentPlayer.gainNewCard(c, this.controlCard, context); } } } } private void cellar(Game game, MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.cellar_cardsToDiscard(context); if (cards != null) { int numberOfCards = 0; for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { currentPlayer.discard(currentPlayer.hand.remove(i), this.controlCard, context); numberOfCards++; break; } } } if (numberOfCards != cards.length) { Util.playerError(currentPlayer, "Cellar discard error, trying to discard cards not in hand, ignoring extra."); } while (numberOfCards > 0) { numberOfCards--; game.drawToHand(currentPlayer, this.controlCard); } } } private void secretChamber(MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.secretChamber_cardsToDiscard(context); if (cards != null) { int numberOfCardsDiscarded = 0; for (Card card : cards) { if (currentPlayer.hand.remove(card)) { currentPlayer.discard(card, this.controlCard, null); numberOfCardsDiscarded++; } } if (numberOfCardsDiscarded != cards.length) { Util.playerError(currentPlayer, "Secret chamber discard error, trying to discard cards not in hand, ignoring extra."); } context.addGold += numberOfCardsDiscarded; } } private void bureaucrat(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainNewCard(Cards.silver, this.controlCard, context); for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); ArrayList<VictoryCard> victoryCards = new ArrayList<VictoryCard>(); for (Card card : player.hand) { if (card instanceof VictoryCard) { victoryCards.add((VictoryCard) card); } } if (victoryCards.size() == 0) { for (int i = 0; i < player.hand.size(); i++) { Card card = player.hand.get(i); player.reveal(card, this.controlCard, playerContext); } } else { VictoryCard toTopOfDeck = null; if (victoryCards.size() == 1) { toTopOfDeck = victoryCards.get(0); } else { toTopOfDeck = (player).controlPlayer.bureaucrat_cardToReplace(playerContext); if (toTopOfDeck == null) { Util.playerError(player, "No Victory Card selected for Bureaucrat, using first Victory Card in hand"); toTopOfDeck = victoryCards.get(0); } } player.reveal(toTopOfDeck, this.controlCard, playerContext); player.hand.remove(toTopOfDeck); player.putOnTopOfDeck(toTopOfDeck); } } } } private void golem(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> toDiscard = new ArrayList<Card>(); ArrayList<Card> toOrder = new ArrayList<Card>(); while (toOrder.size() < 2) { Card draw = game.draw(currentPlayer); if (draw == null) { break; } currentPlayer.reveal(draw, this.controlCard, context); if (draw instanceof ActionCard && !draw.equals(Cards.golem)) { toOrder.add(draw); // currentPlayer.hand.add(draw); } else { toDiscard.add(draw); } } while (!toDiscard.isEmpty()) { currentPlayer.discard(toDiscard.remove(0), this.controlCard, null); } if (!toOrder.isEmpty()) { Card[] toPlay; if (toOrder.size() == 1) { toPlay = toOrder.toArray(new Card[toOrder.size()]); } else { ActionCard[] playOrder = currentPlayer.controlPlayer.golem_cardOrder(context, toOrder.toArray(new ActionCard[toOrder.size()])); if (playOrder == null) { Util.playerError(currentPlayer, "Nothing back from golem_cardOrder, using order found"); toPlay = toOrder.toArray(new Card[toOrder.size()]); } else if (playOrder.length != toOrder.size()) { Util.playerError(currentPlayer, "Bad playOrder size (" + playOrder.length + " != " + toOrder.size() + " from golem_cardOrder, using order found"); toPlay = toOrder.toArray(new Card[toOrder.size()]); } else { boolean bad = false; for (int i = 0; i < toOrder.size(); i++) { Card candidate = playOrder[i]; if (!toOrder.contains(candidate)) { Util.playerError(currentPlayer, candidate + " wasn't in your toOrder list (" + toOrder.toString() + ")"); bad = true; } } toPlay = bad ? toOrder.toArray(new Card[toOrder.size()]) : playOrder; } } context.freeActionInEffect++;context.golemInEffect++; for (Card card : toPlay) { ((ActionCardImpl) card).play(game, context, false); } context.freeActionInEffect--;context.golemInEffect--; } } private void adventurer(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> toDiscard = new ArrayList<Card>(); int treasureCardsRevealed = 0; while (treasureCardsRevealed < 2) { Card draw = game.draw(currentPlayer); if (draw == null) { break; } currentPlayer.reveal(draw, this.controlCard, context); if (draw instanceof TreasureCard) { treasureCardsRevealed++; currentPlayer.hand.add(draw); } else { toDiscard.add(draw); } } while (!toDiscard.isEmpty()) { currentPlayer.discard(toDiscard.remove(0), this.controlCard, context); } } private void library(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> toDiscard = new ArrayList<Card>(); while (currentPlayer.hand.size() < 7) { Card draw = game.draw(currentPlayer); if (draw == null) { break; } boolean shouldKeep = true; if (draw instanceof ActionCard) { shouldKeep = currentPlayer.controlPlayer.library_shouldKeepAction(context, (ActionCard) draw); } if (shouldKeep) { currentPlayer.hand.add(draw); } else { toDiscard.add(draw); } } while (!toDiscard.isEmpty()) { currentPlayer.discard(toDiscard.remove(0), this.controlCard, null); } } private void chapel(MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.chapel_cardsToTrash(context); if (cards != null) { if (cards.length > 4) { Util.playerError(currentPlayer, "Chapel trash error, trying to trash too many cards, ignoring."); } else { for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { Card thisCard = currentPlayer.hand.remove(i, false); currentPlayer.trash(thisCard, this.controlCard, context); break; } } } } } } private void haven(MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.haven_cardToSetAside(context); if ((card == null && hand(currentPlayer).size() > 0) || (card != null && !hand(currentPlayer).contains(card))) { Util.playerError(currentPlayer, "Haven set aside card error, setting aside the first card in hand."); card = hand(currentPlayer).get(0); } if (card != null) { hand(currentPlayer).remove(card); haven(currentPlayer).add(card); } else if (this.controlCard.cloneCount == 1) { currentPlayer.nextTurnCards.remove(this.controlCard); currentPlayer.playedCards.add(this.controlCard); } } private void pirateShip(Game game, MoveContext context, Player currentPlayer) { ArrayList<Player> playersToAttack = new ArrayList<Player>(); for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { playersToAttack.add(targetPlayer); targetPlayer.attacked(this.controlCard, context); } } if (currentPlayer.controlPlayer.pirateShip_takeTreasure(context)) { takePirateShipTreasure(context); } else { boolean treasureFound = false; for (Player targetPlayer : playersToAttack) { MoveContext targetContext = new MoveContext(game, targetPlayer); ArrayList<TreasureCard> treasures = new ArrayList<TreasureCard>(); List<Card> cardToDiscard = new ArrayList<Card>(); for (int i = 0; i < 2; i++) { Card card = game.draw(targetPlayer); if (card != null) { targetPlayer.reveal(card, this.controlCard, targetContext); if (card instanceof TreasureCard) { treasures.add((TreasureCard) card); } else { cardToDiscard.add(card); } } } for (Card c: cardToDiscard) { targetPlayer.discard(c, this.controlCard, targetContext); } TreasureCard cardToTrash = null; if (treasures.size() == 1) { cardToTrash = treasures.get(0); } else if (treasures.size() == 2) { if (treasures.get(0).equals(treasures.get(1))) { cardToTrash = treasures.get(0); targetPlayer.discard(treasures.get(1), this.controlCard, targetContext); } else { cardToTrash = currentPlayer.controlPlayer.pirateShip_treasureToTrash(context, treasures.toArray(new TreasureCard[] {})); } for (TreasureCard treasure : treasures) { if (!treasure.equals(cardToTrash)) { targetPlayer.discard(treasure, this.controlCard, targetContext); } } } if (cardToTrash != null) { targetPlayer.trash(cardToTrash, this.controlCard, targetContext); treasureFound = true; } } if (treasureFound) { increasePirateShipTreasure(currentPlayer); } } } private void smugglers(MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.smugglers_cardToObtain(context); if (card != null) { if (card.getCost(context) > 6 || !Cards.isSupplyCard(card) || card.costPotion()) { Util.playerError(currentPlayer, "Smugglers card error, ignoring."); card = null; } else { boolean found = false; for (Card cardToCheck : context.getCardsObtainedByLastPlayer()) { if (cardToCheck == card) { found = true; break; } } if (!found) { Util.playerError(currentPlayer, "Smugglers card error, ignoring."); card = null; } } } if (card != null) { if (!currentPlayer.gainNewCard(card, this.controlCard, context)) { // TODO do this.controlCard error output everywhere Util.playerError(currentPlayer, "Smugglers card error, no more cards left of that type, ignoring."); } } } private void feast(MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.feast_cardToObtain(context); if (card != null) { // check cost if (card.getCost(context) <= 5) { currentPlayer.gainNewCard(card, this.controlCard, context); } } } private void chancellor(Game game, MoveContext context, Player currentPlayer) { boolean discard = currentPlayer.controlPlayer.chancellor_shouldDiscardDeck(context); if (discard) { while (currentPlayer.getDeckSize() > 0) { currentPlayer.discard(game.draw(currentPlayer), this.controlCard, null, false); } } } private void moneyLender(MoveContext context, Player currentPlayer) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); if (card.equals(Cards.copper)) { Card thisCard = currentPlayer.hand.remove(i); context.addGold += 3; currentPlayer.trash(thisCard, this.controlCard, context); break; } } } Player getNextPlayer(Player player) { int next = -1; for (int i = 0; i < Game.players.length; i++) { if (player == Game.players[i]) { next = i + 1; if (next >= Game.players.length) { next = 0; } break; } } if (next == -1) { Util.log("ERROR:getNextPlayer() could not find current player:" + player.getPlayerName()); return null; } return Game.players[next]; } @Override public String getStats() { StringBuilder sb = new StringBuilder(); sb.append(super.getStats()); if (addActions > 0 || addBuys > 0 || addCards > 0 || addGold > 0) { sb.append(" "); boolean start = true; if (addActions > 0) { if (start) { start = false; } else { sb.append(", "); } sb.append("+" + addActions + " Action"); if (addActions > 1) { sb.append("s"); } } if (addBuys > 0) { if (start) { start = false; } else { sb.append(", "); } sb.append("+" + addBuys + " Buy"); if (addBuys > 1) { sb.append("s"); } } if (addGold > 0) { if (start) { start = false; } else { sb.append(", "); } sb.append("+" + addGold + " Coin"); } if (addCards > 0) { if (start) { start = false; } else { sb.append(", "); } sb.append("+" + addCards + " Card"); if (addCards > 1) { sb.append("s"); } } } return sb.toString(); } protected boolean isDefendedFromAttack(Game game, Player player, Card responsible) { return Util.isDefendedFromAttack(game, player, responsible); } protected void increasePirateShipTreasure(Player player) { (player).pirateShipTreasure++; } protected void takePirateShipTreasure(MoveContext context) { ((MoveContext) context).addGold += (context.getPlayer()).pirateShipTreasure; } // TODO better way to do, possible security hole protected CardList hand(Player player) { return player.hand; } protected CardList haven(Player player) { return player.haven; } protected void drawToHand(Game game, Player player, Card responsible) { game.drawToHand(player, responsible); } protected void addToHand(Player player, Card card) { player.hand.add(card); } protected Card draw(Game game, Player player) { return game.draw(player); } protected Player[] getAllPlayers() { return Game.players; } protected Card removeFromHand(Player player, int i) { return player.hand.remove(i); } public void workshop(Player currentPlayer, MoveContext context) { Card card = currentPlayer.controlPlayer.workshop_cardToObtain(context); if (card != null) { // check cost if (card.getCost(context) <= 4) { currentPlayer.gainNewCard(card, this.controlCard, context); } } } @Override public void isBought(MoveContext context) { switch (this.controlCard.getType()) { case NobleBrigand: nobleBrigandAttack(context, false); break; case Mint: for (Iterator<Card> it = context.player.playedCards.iterator(); it.hasNext();) { Card playedCard = it.next(); if (playedCard instanceof TreasureCard) { context.player.trash(playedCard, this.controlCard, context); it.remove(); } } break; case StoneMason: stoneMasonOverpay(context); break; case Doctor: doctorOverpay(context); break; case Herald: heraldOverpay(context, context.getPlayer()); break; default: break; } } public void nobleBrigandAttack(MoveContext moveContext, boolean defensible) { MoveContext context = moveContext; Player player = context.getPlayer(); ArrayList<TreasureCard> trashed = new ArrayList<TreasureCard>(); boolean[] gainCopper = new boolean[context.game.getPlayersInTurnOrder().length]; int i = 0; for (Player targetPlayer : context.game.getPlayersInTurnOrder()) { // Hinterlands card details in the rules states that noble brigand is not defensible when triggered from a buy if (targetPlayer != player && (!defensible || !Util.isDefendedFromAttack(context.game, targetPlayer, this.controlCard))) { targetPlayer.attacked(this.controlCard, moveContext); MoveContext targetContext = new MoveContext(context.game, targetPlayer); boolean treasureRevealed = false; ArrayList<TreasureCard> silverOrGold = new ArrayList<TreasureCard>(); List<Card> cardsToDiscard = new ArrayList<Card>(); for (int j = 0; j < 2; j++) { Card card = context.game.draw(targetPlayer); if(card == null) { break; } targetPlayer.reveal(card, this.controlCard, targetContext); if (card instanceof TreasureCard) { treasureRevealed = true; } if(card.equals(Cards.silver) || card.equals(Cards.gold)) { silverOrGold.add((TreasureCard) card); } else { cardsToDiscard.add(card); } } for (Card c: cardsToDiscard) { targetPlayer.discard(c, this.controlCard, targetContext); } if(!treasureRevealed) { gainCopper[i] = true; } TreasureCard cardToTrash = null; if (silverOrGold.size() == 1) { cardToTrash = silverOrGold.get(0); } else if (silverOrGold.size() == 2) { if (silverOrGold.get(0).equals(silverOrGold.get(1))) { cardToTrash = silverOrGold.get(0); targetPlayer.discard(silverOrGold.get(1), this.controlCard, targetContext); } else { cardToTrash = (player).controlPlayer.nobleBrigand_silverOrGoldToTrash(moveContext, silverOrGold.toArray(new TreasureCard[] {})); for (TreasureCard c : silverOrGold) { if (!c.equals(cardToTrash)) { targetPlayer.discard(c, this.controlCard, targetContext); } } } } if (cardToTrash != null) { targetPlayer.trash(cardToTrash, this.controlCard, targetContext); trashed.add(cardToTrash); } } i++; } i = 0; for(Player targetPlayer : context.game.getPlayersInTurnOrder()) { if(gainCopper[i]) { MoveContext targetContext = new MoveContext(context.game, targetPlayer); targetPlayer.gainNewCard(Cards.copper, this.controlCard, targetContext); } i++; } if (trashed.size() > 0) { for (Card c : trashed) { player.controlPlayer.gainCardAlreadyInPlay(c, this.controlCard, moveContext); context.game.trashPile.remove(c); } } } private void poorHouse(MoveContext context, Player currentPlayer) { int treasures = 0; for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); currentPlayer.reveal(card, this.controlCard, context); if (card instanceof TreasureCard) { treasures++; } } context.addGold = Math.max(0, context.addGold-treasures); } private void sage(Game game, MoveContext context, Player currentPlayer) { HashSet<String> cardNames = new HashSet<String>(); for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); cardNames.add(card.getName()); //currentPlayer.reveal(card, this.controlCard, context); } ArrayList<Card> toDiscard = new ArrayList<Card>(); Card draw = null; while ((draw = game.draw(currentPlayer)) != null && draw.getCost(context) < 3) { currentPlayer.reveal(draw, this.controlCard, context); toDiscard.add(draw); } if (draw != null) { currentPlayer.reveal(draw, this.controlCard, context); currentPlayer.hand.add(draw); } while (!toDiscard.isEmpty()) { currentPlayer.discard(toDiscard.remove(0), this.controlCard, null); } } private void rats(MoveContext context, Player currentPlayer) { currentPlayer.gainNewCard(Cards.rats, this.controlCard, context); if(currentPlayer.hand.size() > 0) { boolean hasother = false; for (Card c : currentPlayer.hand) { if (!c.equals(Cards.rats)) { hasother = true; } } if (!hasother) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card card = currentPlayer.hand.get(i); currentPlayer.reveal(card, this.controlCard, context); } } else { Card card = currentPlayer.controlPlayer.rats_cardToTrash(context); if(card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Rats card to trash invalid, picking one"); card = currentPlayer.hand.get(0); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } } } private void squire(MoveContext context, Player currentPlayer) { Player.SquireOption option = currentPlayer.controlPlayer.squire_chooseOption(context); if (option == null) { Util.playerError(currentPlayer, "Squire option error, ignoring."); } else { if (option == Player.SquireOption.AddActions) { context.actions += 2; } else if (option == Player.SquireOption.AddBuys) { context.buys += 2; } else if (option == Player.SquireOption.GainSilver) { currentPlayer.gainNewCard(Cards.silver, this.controlCard, context); } } } public void armory(Player currentPlayer, MoveContext context) { Card card = currentPlayer.controlPlayer.armory_cardToObtain(context); if (card != null) { // check cost if (card.getCost(context) <= 5) { currentPlayer.gainNewCard(card, this.controlCard, context); //currentPlayer.discard.remove(Cards.armory); //currentPlayer.putOnTopOfDeck(Cards.armory); } } } private void altar(Player currentPlayer, MoveContext context) { if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.altar_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Altar trash error, trashing a random card."); card = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } Card card = currentPlayer.controlPlayer.altar_cardToObtain(context); if (card != null) { // check cost if (card.getCost(context) <= 5) { currentPlayer.gainNewCard(card, this.controlCard, context); } } } private void banditCamp(MoveContext context, Player currentPlayer) { // Gain a Spoils from the Spoils pile currentPlayer.gainNewCard(Cards.spoils, this.controlCard, context); } public void beggar(Player currentPlayer, MoveContext context) { currentPlayer.gainNewCard(Cards.copper, this.controlCard, context); currentPlayer.gainNewCard(Cards.copper, this.controlCard, context); currentPlayer.gainNewCard(Cards.copper, this.controlCard, context); } public void catacombs(Game game, Player currentPlayer, MoveContext context) { ArrayList<Card> topOfTheDeck = new ArrayList<Card>(); for (int i = 0; i < 3; i++) { Card card = game.draw(currentPlayer); if (card != null) { topOfTheDeck.add(card); } } if (topOfTheDeck.size() > 0) { if (currentPlayer.controlPlayer.catacombs_shouldDiscardTopCards(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()]))) { while (!topOfTheDeck.isEmpty()) { currentPlayer.discard(topOfTheDeck.remove(0), this.controlCard, null); } game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); } else { // Put the cards in hand for (Card c : topOfTheDeck) { currentPlayer.hand.add(c); } } } } private void count(Player currentPlayer, MoveContext context) { Player.CountFirstOption option1 = currentPlayer.controlPlayer.count_chooseFirstOption(context); if (option1 == null) { Util.playerError(currentPlayer, "Count first option error, ignoring."); } else { switch (option1) { case Discard: Card[] cards; if (currentPlayer.hand.size() > 2) { cards = currentPlayer.controlPlayer.count_cardsToDiscard(context); } else { cards = currentPlayer.getHand().toArray(); } boolean bad = false; if (cards == null) { bad = true; } else if (cards.length > 2) { bad = true; } else { ArrayList<Card> handCopy = Util.copy(currentPlayer.hand); for (Card card : cards) { if (!handCopy.remove(card)) { bad = true; break; } } } if (bad) { Util.playerError(currentPlayer, "Count discard error, discarding first 2 cards."); cards = new Card[2]; for (int i = 0; i < cards.length; i++) { cards[i] = currentPlayer.hand.get(i); } } for (int i = 0; i < cards.length; i++) { currentPlayer.hand.remove(cards[i]); currentPlayer.reveal(cards[i], this.controlCard, context); currentPlayer.discard(cards[i], this.controlCard, null); } break; case PutOnDeck: if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.count_cardToPutBackOnDeck(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Count error, just putting back a random card."); card = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(card); currentPlayer.putOnTopOfDeck(card); } break; case GainCopper: currentPlayer.gainNewCard(Cards.copper, this.controlCard, context); break; } } Player.CountSecondOption option2 = currentPlayer.controlPlayer.count_chooseSecondOption(context); if (option2 == null) { Util.playerError(currentPlayer, "Count second option error, ignoring."); } else { switch (option2) { case Coins: context.addGold += 3; break; case TrashHand: if (currentPlayer.hand.size() > 0) { Card[] temp = currentPlayer.hand.toArray(); for (Card c : temp) { currentPlayer.hand.remove(c); currentPlayer.trash(c, this.controlCard, context); } } break; case GainDuchy: currentPlayer.gainNewCard(Cards.duchy, this.controlCard, context); break; } } } private void deathCart(Player currentPlayer, MoveContext context) { Card actionCardToTrash = currentPlayer.controlPlayer.deathCart_actionToTrash(context); if (actionCardToTrash != null) { currentPlayer.hand.remove(actionCardToTrash); currentPlayer.trash(actionCardToTrash, this.controlCard, context); } else if (!this.controlCard.movedToNextTurnPile) { currentPlayer.playedCards.remove(this.controlCard); currentPlayer.trash(this.controlCard, this.controlCard, context); this.controlCard.movedToNextTurnPile = true; } } private void forager(Game game, Player currentPlayer, MoveContext context) { if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.forager_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Forager trash error, trashing a random card."); card = Util.randomCard(currentPlayer.hand); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } HashSet<String> cardNames = new HashSet<String>(); for (Card card : game.trashPile) { if (card == null) { break; } if (card instanceof TreasureCard) { cardNames.add(card.getName()); } } context.addGold += cardNames.size(); } private void graverobber(Game game, Player currentPlayer, MoveContext context) { Player.GraverobberOption option = currentPlayer.controlPlayer.graverobber_chooseOption(context); if (option == null) { Util.playerError(currentPlayer, "Graverobber option error, choosing automatically"); option = Player.GraverobberOption.GainFromTrash; } Card toGain; switch (option) { case GainFromTrash: toGain = currentPlayer.controlPlayer.graverobber_cardToGainFromTrash(context); if (toGain == null || toGain.costPotion() || toGain.getCost(context) < 3 || toGain.getCost(context) > 6) { Util.playerError(currentPlayer, "Graverobber gain card choice error, gaining nothing"); return; } toGain = game.trashPile.remove(game.trashPile.indexOf(toGain)); currentPlayer.gainCardAlreadyInPlay(toGain, this.controlCard, context); break; case TrashActionCard: Card toTrash = currentPlayer.controlPlayer.graverobber_cardToTrash(context); if (toTrash == null || !currentPlayer.hand.contains(toTrash) || !(toTrash instanceof ActionCard)) { Util.playerError(currentPlayer, "Graverobber trash error, trashing nothing."); return; } currentPlayer.hand.remove(toTrash); currentPlayer.trash(toTrash, this.controlCard, context); toGain = currentPlayer.controlPlayer.graverobber_cardToReplace(context, 3 + toTrash.getCost(context), toTrash.costPotion()); if (toGain != null) { currentPlayer.gainNewCard(toGain, this.controlCard, context); } break; } } private void ironmonger(Game game, Player currentPlayer, MoveContext context) { Card card = game.draw(currentPlayer); if (card != null) { if (currentPlayer.controlPlayer.ironmonger_shouldDiscard(context, card)) { currentPlayer.discard(card, this.controlCard, context); } else { currentPlayer.putOnTopOfDeck(card); } if (card instanceof ActionCard) { context.actions += 1; } if (card instanceof TreasureCard) { context.addGold++; } if (card instanceof VictoryCard) { game.drawToHand(currentPlayer, this.controlCard); } } } private void junkDealer(Player currentPlayer, MoveContext context) { if (!currentPlayer.hand.isEmpty()) { Card card = currentPlayer.controlPlayer.junkDealer_cardToTrash(context); if(card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Junk Dealer card to trash invalid, picking one"); card = currentPlayer.hand.get(0); } currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); } } private void mystic(Game game, MoveContext context, Player currentPlayer) { if (currentPlayer.deck.size() > 0 || currentPlayer.discard.size() > 0) { // Only allow a guess if there are cards in the deck or discard pile // Create a list of all possible cards to guess, using the player's hand, discard pile, and deck // (even though the player could technically name a card he doesn't have) ArrayList<Card> options = currentPlayer.getAllCards(); Collections.sort(options, new Util.CardNameComparator()); if (options.size() > 0) { Card toName = currentPlayer.controlPlayer.mystic_cardGuess(context, options); currentPlayer.controlPlayer.namedCard(toName, this.controlCard, context); Card draw = game.draw(currentPlayer); if (toName != null && draw != null) { currentPlayer.reveal(draw, this.controlCard, context); if (toName.equals(draw)) { currentPlayer.hand.add(draw); } else { currentPlayer.putOnTopOfDeck(draw); } } } } } private void scavenger(Game game, MoveContext context, Player currentPlayer) { boolean discard = currentPlayer.controlPlayer.scavenger_shouldDiscardDeck(context); // Discard the entire deck if the player chose to do so if (discard) { while (currentPlayer.getDeckSize() > 0) { currentPlayer.discard(game.draw(currentPlayer), this.controlCard, null, false); } } // Prompt to add a card from the discard pile back onto the deck, but only if at least one is available if (currentPlayer.getDiscardSize() > 0) { Card card = currentPlayer.controlPlayer.scavenger_cardToPutBackOnDeck(context); if (card != null) { currentPlayer.discard.remove(card); currentPlayer.putOnTopOfDeck(card); } } } private void storeroom(Game game, MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.storeroom_cardsToDiscardForCards(context); if (cards != null) { int numberOfCards = 0; for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { currentPlayer.discard(currentPlayer.hand.remove(i), this.controlCard, context); numberOfCards++; break; } } } if (numberOfCards != cards.length) { Util.playerError(currentPlayer, "Storeroom discard error, trying to discard cards not in hand, ignoring extra."); } while (numberOfCards > 0) { numberOfCards--; game.drawToHand(currentPlayer, this.controlCard); } } cards = currentPlayer.controlPlayer.storeroom_cardsToDiscardForCoins(context); if (cards != null) { int numberOfCards = 0; for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { currentPlayer.discard(currentPlayer.hand.remove(i), this.controlCard, context); numberOfCards++; break; } } } if (numberOfCards != cards.length) { Util.playerError(currentPlayer, "Storeroom discard error, trying to discard cards not in hand, ignoring extra."); } while (numberOfCards > 0) { numberOfCards--; context.addGold++; } } } private void wanderingMinstrel(Player currentPlayer, MoveContext context) { ArrayList<Card> cards = new ArrayList<Card>(); for (int i = 0; i < 3; i++) { Card card = context.game.draw(currentPlayer); if (card == null) { break; } if (!(card instanceof ActionCard) ) { currentPlayer.discard(card, this.controlCard, context); } else { currentPlayer.reveal(card, this.controlCard, context); cards.add(card); } } if (cards.size() == 0) { return; } Card[] orderedCards = currentPlayer.controlPlayer.topOfDeck_orderCards(context, cards.toArray(new Card[0])); for (int i = orderedCards.length - 1; i >= 0; i--) { currentPlayer.putOnTopOfDeck(orderedCards[i], context, true); } } private void rebuild(Player currentPlayer, MoveContext context) { Card named = currentPlayer.controlPlayer.rebuild_cardToPick(context); currentPlayer.controlPlayer.namedCard(named, this.controlCard, context); ArrayList<Card> cards = new ArrayList<Card>(); Card last = null; // search for first Victory card that was not named while ((last = context.game.draw(currentPlayer)) != null) { if (last instanceof VictoryCard && !last.equals(named)) break; cards.add(last); currentPlayer.reveal(last, this.controlCard, context); } // Discard all other revealed cards for (Card c : cards) { currentPlayer.discard(c, this.controlCard, context); } if (last != null) { // Trash the found Victory card currentPlayer.trash(last, this.controlCard, context); // Gain Victory card that cost up to 3 more coins Card toGain = currentPlayer.controlPlayer.rebuild_cardToGain(context, 3 + last.getCost(context), last.costPotion()); if (toGain != null) { currentPlayer.gainNewCard(toGain, this.controlCard, context); } } } private void rogue(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> options = new ArrayList<Card>(); for (Card c : game.trashPile) { if (c.getCost(context) >= 3 && c.getCost(context) <= 6) { options.add(c); } } if (options.size() > 0) { // gain a card Card toGain = currentPlayer.controlPlayer.rogue_cardToGain(context); if (toGain == null) { Util.playerError(currentPlayer, "Rogue error, no card to gain selected, picking random"); toGain = Util.randomCard(options); } game.trashPile.remove(toGain); currentPlayer.gainCardAlreadyInPlay(toGain, this.controlCard, context); } else { // Other players trash a card for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext targetContext = new MoveContext(game, targetPlayer); ArrayList<Card> canTrash = new ArrayList<Card>(); List<Card> cardsToDiscard = new ArrayList<Card>(); for (int i = 0; i < 2; i++) { Card card = game.draw(targetPlayer); if (card != null) { targetPlayer.reveal(card, this.controlCard, targetContext); int cardCost = card.getCost(context); if (cardCost >= 3 && cardCost <= 6) { canTrash.add(card); } else { cardsToDiscard.add(card); } } } for (Card c: cardsToDiscard) { targetPlayer.discard(c, this.controlCard, targetContext); } Card cardToTrash = null; if (canTrash.size() == 1) { cardToTrash = canTrash.get(0); } else if (canTrash.size() == 2) { if (canTrash.get(0).equals(canTrash.get(1))) { cardToTrash = canTrash.get(0); targetPlayer.discard(canTrash.remove(1), this.controlCard, targetContext); } else { cardToTrash = targetPlayer.controlPlayer.rogue_cardToTrash(context, canTrash); } for (Card card : canTrash) { if (!card.equals(cardToTrash)) { targetPlayer.discard(card, this.controlCard, targetContext); } } } if (cardToTrash != null) { targetPlayer.trash(cardToTrash, this.controlCard, targetContext); } } } } } private void pillage(Game game, MoveContext context, Player currentPlayer) { // Each other player with 5 cards in hand reveals his hand and discards a card that you choose. for (Player targetPlayer : game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && targetPlayer.getHand().size() >= 5 && !Util.isDefendedFromAttack(game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext targetContext = new MoveContext(game, targetPlayer); targetContext.attackedPlayer = targetPlayer; ArrayList<Card> cardsInHand = new ArrayList<Card>(); for (Card card : targetPlayer.getHand()) { cardsInHand.add(card); targetPlayer.reveal(card, this.controlCard, targetContext); } Card cardToDiscard = currentPlayer.controlPlayer.pillage_opponentCardToDiscard(targetContext, cardsInHand); if (cardToDiscard != null) { targetPlayer.hand.remove(cardToDiscard); targetPlayer.discard(cardToDiscard, this.controlCard, targetContext); } } } // Gain 2 Spoils from the Spoils pile currentPlayer.gainNewCard(Cards.spoils, this.controlCard, context); currentPlayer.gainNewCard(Cards.spoils, this.controlCard, context); } private void governor(Game game, MoveContext context, Player currentPlayer) { Player.GovernorOption option = currentPlayer.controlPlayer.governor_chooseOption(context); if (option == null) { Util.playerError(currentPlayer, "Governor option error, ignoring."); } else { if (option == Player.GovernorOption.AddCards) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); for (Player player : getAllPlayers()) { if (player != context.getPlayer()) { drawToHand(game, player, this.controlCard); } } } else if (option == Player.GovernorOption.GainTreasure) { currentPlayer.gainNewCard(Cards.gold, this.controlCard, context); for (Player player : getAllPlayers()) { if (player != context.getPlayer()) { player.gainNewCard(Cards.silver, this.controlCard, new MoveContext(game, player)); } } } else if (option == Player.GovernorOption.Upgrade) { if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.governor_cardToTrash(context); if (card == null || !currentPlayer.hand.contains(card)) { Util.playerError(currentPlayer, "Governor trash error, upgrading a random card."); card = Util.randomCard(currentPlayer.hand); } int value = card.getCost(context) + 2; boolean potion = card.costPotion(); currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); card = currentPlayer.controlPlayer.governor_cardToObtain(context, value, potion); if (card != null) { if (card.getCost(context) != value || card.costPotion() != potion) { Util.playerError(currentPlayer, "Governor error, new card does not cost value of the old card +2."); } else { if(!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Governor error, pile is empty or card is not in the game."); } } } } for (Player player : getAllPlayers()) { if (player != context.getPlayer()) { MoveContext playerContext = new MoveContext(game, player); if (player.getHand().size() > 0) { Card card = player.controlPlayer.governor_cardToTrash(playerContext); if (card == null || !player.hand.contains(card)) { Util.playerError(player, "Governor trash error, upgrading a random card."); card = Util.randomCard(player.hand); } int value = card.getCost(playerContext) + 1; boolean potion = card.costPotion(); player.hand.remove(card); player.trash(card, this.controlCard, playerContext); card = player.controlPlayer.governor_cardToObtain(playerContext, value, potion); if (card != null) { if (card.getCost(playerContext) != value || card.costPotion() != potion) { Util.playerError(player, "Governor error, new card does not cost value of the old card +1."); } else { if(!player.gainNewCard(card, this.controlCard, playerContext)) { Util.playerError(player, "Governor error, pile is empty or card is not in the game."); } } } } } } } } } private void envoy(Game game, MoveContext context, Player currentPlayer) { ArrayList<Card> cards = new ArrayList<Card>(); Player nextPlayer = game.getNextPlayer(); for (int i = 0; i < 5; i++) { Card card = game.draw(currentPlayer); if (card != null) { cards.add(card); currentPlayer.reveal(card, this.controlCard, context); } } if (cards.size() == 0) { return; } Card toDiscard; if (cards.size() > 1) { toDiscard = nextPlayer.controlPlayer.envoy_cardToDiscard(context, cards.toArray(new Card[cards.size()])); } else { toDiscard = cards.get(0); } if (toDiscard == null || !cards.contains(toDiscard)) { Util.playerError(currentPlayer, "Envoy discard error, just picking the first card."); toDiscard = cards.get(0); } currentPlayer.discard(toDiscard, this.controlCard, context); cards.remove(toDiscard); GameEvent event = new GameEvent(GameEvent.Type.CardDiscarded, (MoveContext) context); event.card = toDiscard; event.responsible = this.controlCard; game.broadcastEvent(event); if (cards.size() > 0) { for(Card c : cards) { currentPlayer.hand.add(c); } } } @Override public void isTrashed(MoveContext context) { switch (this.controlCard.behaveAsCard().getType()) { case Rats: context.game.drawToHand(context.player, this.controlCard, true); break; case Squire: // Need to ensure that there is at least one Attack card that can be gained, // otherwise this.controlCard choice should be bypassed. boolean attackCardAvailable = false; for (Card c : context.game.getCardsInGame()) { if (c instanceof ActionCard) { if (((ActionCard)c).isAttack() && context.game.getPile(c).getCount() > 0) { attackCardAvailable = true; break; } } } if (attackCardAvailable) { Card s = context.player.controlPlayer.squire_cardToObtain(context); if (s != null) { context.player.controlPlayer.gainNewCard(s, this.controlCard, context); } } break; case Catacombs: Card c = context.player.controlPlayer.catacombs_cardToObtain(context); if (c != null) { context.player.controlPlayer.gainNewCard(c, this.controlCard, context); } break; case HuntingGrounds: // Make sure there are Estates and/or Duchies available when trashing Hunting Grounds. // If there isn't a choice to be made, the player gets the option that is still available, // or nothing at all if both piles are empty int duchyCount = context.game.getPile(Cards.duchy).getCount(); int estateCount = context.game.getPile(Cards.estate).getCount(); boolean gainDuchy = false; boolean gainEstates = false; if (duchyCount > 0 && estateCount > 0) { Player.HuntingGroundsOption option = context.player.controlPlayer.huntingGrounds_chooseOption(context); if (option != null) { switch (option) { case GainDuchy: gainDuchy = true; break; case GainEstates: gainEstates = true; break; default: break; } } } else if (duchyCount > 0) { gainDuchy = true; } else if (estateCount > 0) { gainEstates = true; } if (gainDuchy) { context.player.controlPlayer.gainNewCard(Cards.duchy, this.controlCard, context); } else if (gainEstates) { context.player.controlPlayer.gainNewCard(Cards.estate, this.controlCard, context); context.player.controlPlayer.gainNewCard(Cards.estate, this.controlCard, context); context.player.controlPlayer.gainNewCard(Cards.estate, this.controlCard, context); } break; case Fortress: context.game.trashPile.remove(this.controlCard); context.player.hand.add(this.controlCard); break; case Cultist: context.game.drawToHand(context.player, this.controlCard, false); context.game.drawToHand(context.player, this.controlCard, false); context.game.drawToHand(context.player, this.controlCard, false); break; case SirVander: context.player.controlPlayer.gainNewCard(Cards.gold, this.controlCard, context); break; default: break; } // card left play - stop any impersonations this.controlCard.stopImpersonatingCard(); } /*@Override public void isGained(MoveContext context) { super.isGained(context); switch (this.controlCard.type) { case DeathCart: context.player.controlPlayer.gainNewCard(Cards.virtualRuins, this.controlCard, context); context.player.controlPlayer.gainNewCard(Cards.virtualRuins, this.controlCard, context); break; default: break; } }*/ @Override public boolean isRuins() { switch (this.controlCard.getType()) { case AbandonedMine: case RuinedLibrary: case RuinedMarket: case RuinedVillage: case Survivors: return true; default: return false; } } @Override public boolean isKnight() { return isKnight; } @Override public boolean isLooter() { switch (this.controlCard.getType()) { case Cultist: case DeathCart: case Marauder: return true; default: return false; } } private void survivors(MoveContext context, Game game, Player currentPlayer) { ArrayList<Card> topOfTheDeck = new ArrayList<Card>(); for (int i = 0; i < 2; i++) { Card card = game.draw(currentPlayer); if (card != null) { topOfTheDeck.add(card); } } if (topOfTheDeck.size() > 0) { if (currentPlayer.controlPlayer.survivors_shouldDiscardTopCards(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()]))) { while (!topOfTheDeck.isEmpty()) { currentPlayer.discard(topOfTheDeck.remove(0), this.controlCard, null); } } else { Card[] order = currentPlayer.controlPlayer.survivors_cardOrder(context, topOfTheDeck.toArray(new Card[topOfTheDeck.size()])); // Check that they returned the right cards boolean bad = false; if (order == null) { bad = true; } else { ArrayList<Card> copy = new ArrayList<Card>(); for (Card card : topOfTheDeck) { copy.add(card); } for (Card card : order) { if (!copy.remove(card)) { bad = true; break; } } if (!copy.isEmpty()) { bad = true; } } if (bad) { Util.playerError(currentPlayer, "Survivors order cards error, ignoring."); order = topOfTheDeck.toArray(new Card[topOfTheDeck.size()]); } // Put the cards back on the deck for (int i = order.length - 1; i >= 0; i--) { currentPlayer.putOnTopOfDeck(order[i]); } } } } private void cultist(MoveContext context, Game game, Player currentPlayer) { for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); player.gainNewCard(Cards.virtualRuins, this.controlCard, playerContext); } } if (currentPlayer.hand.contains(Cards.cultist) && currentPlayer.controlPlayer.cultist_shouldPlayNext(context)) { ActionCardImpl next = (ActionCardImpl) currentPlayer.hand.get(Cards.cultist); if (next != null) { context.freeActionInEffect++; next.play(game, context, true); context.freeActionInEffect--; } } } private void urchin(MoveContext context, Game game, Player currentPlayer) { for (Player player : context.game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(context.game, player, this.controlCard)) { player.attacked(this.controlCard, context); int keepCardCount = 4; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.urchin_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } private void attackPlayed(MoveContext context, Game game, Player currentPlayer) { // If an Urchin has been played, offer the player the option to trash it for a Mercenary for (int i = currentPlayer.playedCards.size() - 1; i > 0 ; ) { Card c = currentPlayer.playedCards.get(--i); if (c.behaveAsCard().getType() == Cards.Type.Urchin && currentPlayer.controlPlayer.urchin_shouldTrashForMercenary(context)) { currentPlayer.trash(c.getControlCard(), this, context); currentPlayer.gainNewCard(Cards.mercenary, this, context); currentPlayer.playedCards.remove(i); } } } private void mercenary(MoveContext context, Game game, Player currentPlayer) { int cardsTrashedCount = 0; Card[] cards = currentPlayer.controlPlayer.mercenary_cardsToTrash(context); if (cards != null) { if (cards.length > 2) { Util.playerError(currentPlayer, "Mercenary trash error, trying to trash too many cards, ignoring."); } else { for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { Card thisCard = currentPlayer.hand.remove(i); currentPlayer.trash(thisCard, this.controlCard, context); ++cardsTrashedCount; break; } } } } } if (cardsTrashedCount == 2) { game.drawToHand(currentPlayer, this.controlCard); game.drawToHand(currentPlayer, this.controlCard); context.addGold += 2; for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.mercenary_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } } } private void marauder(MoveContext context, Game game, Player currentPlayer) { currentPlayer.gainNewCard(Cards.spoils, this.controlCard, context); for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); player.gainNewCard(Cards.virtualRuins, this.controlCard, playerContext); } } } private void hermit(MoveContext context, Game game, Player currentPlayer) { ArrayList<Card> options = new ArrayList<Card>(); int nonTreasureCountInDiscard = 0; for (Card c : currentPlayer.discard) { if (!(c instanceof TreasureCard)) { options.add(c); ++nonTreasureCountInDiscard; // Keep track of which cards are in the discard pile so that the player // can tell if they are trashing from discard or hand } } if (!options.isEmpty()) Collections.sort(options, new Util.CardNameComparator()); ArrayList<Card> options2 = new ArrayList<Card>(); for (Card c: currentPlayer.hand) { if (!(c instanceof TreasureCard)) { options2.add(c); } } if (!options2.isEmpty()) { Collections.sort(options2, new Util.CardNameComparator()); options.addAll(options2); } if (!options.isEmpty()) { // Offer the option to trash a non-treasure card context.hermitTrashCardPile = PileSelection.ANY; Card toTrash = currentPlayer.controlPlayer.hermit_cardToTrash(context, options, nonTreasureCountInDiscard); if (toTrash != null) { if (currentPlayer.discard.contains(toTrash) && (context.hermitTrashCardPile == PileSelection.ANY || context.hermitTrashCardPile == PileSelection.DISCARD)) { currentPlayer.discard.remove(toTrash); currentPlayer.trash(toTrash, this.controlCard, context); } else if (currentPlayer.hand.contains(toTrash) && (context.hermitTrashCardPile == PileSelection.ANY || context.hermitTrashCardPile == PileSelection.HAND)) { currentPlayer.hand.remove(toTrash); currentPlayer.trash(toTrash, this.controlCard, context); } else { Util.playerError(currentPlayer, "Hermit trash error, chosen card to trash not in hand or discard, ignoring."); } } } // Gain a card costing up to 3 coins (no potion) Card c = currentPlayer.controlPlayer.hermit_cardToGain(context); if (c != null) { if (c.getCost(context, false) > 3 || c.costPotion()) { Util.playerError(currentPlayer, "Hermit card selection error, picking card from table."); c = (context.getCardsLeftInPile(Cards.silver) > 0) ? Cards.silver : Cards.copper; } currentPlayer.controlPlayer.gainNewCard(c, this.controlCard, context); } } private void madman(MoveContext context, Game game, Player currentPlayer) { if (currentPlayer.playedCards.contains(this.controlCard)) { // Return to the Madman pile currentPlayer.playedCards.remove(this.controlCard); SingleCardPile pile = (SingleCardPile) game.getPile(this.controlCard); pile.addCard(this.controlCard); int handSize = currentPlayer.hand.size(); for (int i = 0; i < handSize; ++i) { game.drawToHand(currentPlayer, this.controlCard); } } } private void vagrant(MoveContext context, Game game, Player currentPlayer) { Card c = game.draw(currentPlayer); if (c != null) { currentPlayer.reveal(c, this.controlCard, context); if (c.getType() == Cards.Type.Curse || c.isShelter() || (c instanceof VictoryCard) || (c.isRuins())) { currentPlayer.hand.add(c); } else { currentPlayer.putOnTopOfDeck(c); } } } private void knight(MoveContext context, Player currentPlayer) { for (Player targetPlayer : context.game.getPlayersInTurnOrder()) { if (targetPlayer != currentPlayer && !Util.isDefendedFromAttack(context.game, targetPlayer, this.controlCard)) { targetPlayer.attacked(this.controlCard, context); MoveContext targetContext = new MoveContext(context.game, targetPlayer); ArrayList<Card> canTrash = new ArrayList<Card>(); List<Card> cardsToDiscard = new ArrayList<Card>(); for (int i = 0; i < 2; i++) { Card card = context.game.draw(targetPlayer); if (card != null) { targetPlayer.reveal(card, this.controlCard, targetContext); int cardCost = card.getCost(context); if (!card.costPotion() && cardCost >= 3 && cardCost <= 6) { canTrash.add(card); } else { cardsToDiscard.add(card); } } } for (Card c: cardsToDiscard) { targetPlayer.discard(c, this.controlCard, targetContext); } Card cardToTrash = null; if (canTrash.size() == 1) { cardToTrash = canTrash.get(0); } else if (canTrash.size() == 2) { if (canTrash.get(0).equals(canTrash.get(1))) { cardToTrash = canTrash.get(0); targetPlayer.discard(canTrash.remove(1), this.controlCard, targetContext); } else { cardToTrash = targetPlayer.knight_cardToTrash(context, canTrash); } for (Card card : canTrash) { if (!card.equals(cardToTrash)) { targetPlayer.discard(card, this.controlCard, targetContext); } } } if (cardToTrash != null) { targetPlayer.trash(cardToTrash, this.controlCard, targetContext); // If the card trashed was a knight, the attacking knight should be trashed as well if (cardToTrash.isKnight() && currentPlayer.playedCards.contains(this.controlCard) && currentPlayer.playedCards.getLastCard() == this.controlCard) { currentPlayer.trash(currentPlayer.playedCards.removeLastCard(), cardToTrash, context); } } } } } private void dameAnna(MoveContext context, Player currentPlayer) { Card[] cards = currentPlayer.controlPlayer.dameAnna_cardsToTrash(context); if (cards != null) { if (cards.length > 2) { Util.playerError(currentPlayer, "Dame Anna trash error, trying to trash too many cards, ignoring."); } else { for (Card card : cards) { for (int i = 0; i < currentPlayer.hand.size(); i++) { Card playersCard = currentPlayer.hand.get(i); if (playersCard.equals(card)) { Card thisCard = currentPlayer.hand.remove(i); currentPlayer.trash(thisCard, this.controlCard, context); break; } } } } } knight(context, currentPlayer); } private void sirMichael(MoveContext context, Player currentPlayer) { for (Player player : context.game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(context.game, player, this.controlCard)) { player.attacked(this.controlCard, context); int keepCardCount = 3; if (player.hand.size() > keepCardCount) { Card[] cardsToKeep = player.controlPlayer.sirMichael_attack_cardsToKeep(context); player.discardRemainingCardsFromHand(context, cardsToKeep, this.controlCard, keepCardCount); } } } knight(context, currentPlayer); } private void dameNatalie(MoveContext context, Player currentPlayer) { Card card = currentPlayer.controlPlayer.dameNatalie_cardToObtain(context); if (card != null) { // check cost if (card.getCost(context) <= 3) { currentPlayer.gainNewCard(card, this.controlCard, context); } else { Util.playerError(currentPlayer, "Dame Natalie error: chosen card that costs more then 3"); } } knight(context, currentPlayer); } private void bandOfMisfits(Game game, MoveContext context, Player currentPlayer) { // Already impersonating another card? if (!this.isImpersonatingAnotherCard()) { // Get card to impersonate ActionCard cardToImpersonate = currentPlayer.controlPlayer.bandOfMisfits_actionCardToImpersonate(context); if (cardToImpersonate != null && !game.isPileEmpty(cardToImpersonate) && cardToImpersonate instanceof ActionCard && cardToImpersonate.getCost(context) < this.controlCard.getCost(context) && (context.golemInEffect == 0 || cardToImpersonate != Cards.golem)) { GameEvent event = new GameEvent(GameEvent.Type.CardNamed, (MoveContext) context); event.card = cardToImpersonate; event.responsible = this; game.broadcastEvent(event); this.startImpersonatingCard(cardToImpersonate.getTemplateCard().instantiate()); } else { Card[] cards = game.getActionsInGame(); if (cards.length != 0 && cardToImpersonate != null) { Util.playerError(currentPlayer, "Band of Misfits returned invalid card (" + cardToImpersonate.getName() + "), ignoring."); } return; } } // Play the impersonated card ActionCardImpl cardToPlay = (ActionCardImpl) this.impersonatingCard; context.freeActionInEffect++; cardToPlay.play(game, context, false); context.freeActionInEffect--; // impersonated card stays in play until next turn? if (cardToPlay.trashOnUse) { int idx = currentPlayer.playedCards.lastIndexOf(this); if (idx >= 0) currentPlayer.playedCards.remove(idx); currentPlayer.trash(this, null, context); } else if (cardToPlay instanceof DurationCard && !cardToPlay.equals(Cards.outpost)) { if (!this.controlCard.movedToNextTurnPile) { this.controlCard.movedToNextTurnPile = true; int idx = currentPlayer.playedCards.lastIndexOf(this); if (idx >= 0) { currentPlayer.playedCards.remove(idx); currentPlayer.nextTurnCards.add(this); } } } } private void sendGuildsTokenObtainedEvent(Game game, MoveContext context) { GameEvent event = new GameEvent(GameEvent.Type.GuildsTokenObtained, context); game.broadcastEvent(event); } private void soothsayer(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainNewCard(Cards.gold, this.controlCard, context); for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); boolean curseGained = player.gainNewCard(Cards.curse, this.controlCard, playerContext); if (curseGained) { drawToHand(game, player, this.controlCard); } } } } private void taxman(Game game, MoveContext context, Player currentPlayer) { /*You may trash a Treasure from your hand. * Each other player with 5 or more cards in hand discards a copy of it (or reveals a hand without it). Gain a Treasure card costing up to $3 more than the trashed card, putting it on top of your deck.*/ if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.taxman_treasureToTrash(context); if (card != null) { currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); for (Player player : game.getPlayersInTurnOrder()) { if (player != currentPlayer && !Util.isDefendedFromAttack(game, player, this.controlCard)) { player.attacked(this.controlCard, context); MoveContext playerContext = new MoveContext(game, player); if (player.hand.size() >= 5) { if (player.hand.contains(card)) { Card c2 = player.hand.get(card); player.hand.remove(c2); player.discard(c2, this.controlCard, playerContext); } else { for (Card c : player.getHand()) { player.reveal(c, this.controlCard, playerContext); } } } } } TreasureCard newCard = currentPlayer.controlPlayer.taxman_treasureToObtain(context, card.getCost(context) + 3); if (newCard != null && newCard.getCost(context) <= card.getCost(context) + 3 && context.getCardsLeftInPile(newCard) > 0) { //context.player.putOnTopOfDeck(newCard); currentPlayer.gainNewCard(newCard, this.controlCard, context); } } } } private void plaza(Game game, MoveContext context, Player currentPlayer) { boolean valid = false; for (Card c : currentPlayer.hand) { if (c instanceof TreasureCard) { valid = true; } } if (valid) { TreasureCard toDiscard = currentPlayer.controlPlayer.plaza_treasureToDiscard(context); if (toDiscard != null && currentPlayer.hand.contains(toDiscard)) { currentPlayer.hand.remove(toDiscard); currentPlayer.reveal(toDiscard, this.controlCard, context); currentPlayer.discard(toDiscard, this.controlCard, context); currentPlayer.gainGuildsCoinTokens(1); sendGuildsTokenObtainedEvent(game, context); Util.debug(currentPlayer, "Gained a Guild coin token via Plaza"); } } } private void candlestickMaker(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainGuildsCoinTokens(1); sendGuildsTokenObtainedEvent(game, context); } private void baker(Game game, MoveContext context, Player currentPlayer) { currentPlayer.gainGuildsCoinTokens(1); sendGuildsTokenObtainedEvent(game, context); } private void advisor(Game game, MoveContext context, Player currentPlayer) { /*Reveal the top 3 cards of your deck. * The player to your left chooses one of them. * Discard that card. * Put the other cards into your hand.*/ ArrayList<Card> cards = new ArrayList<Card>(); Player nextPlayer = game.getNextPlayer(); for (int i = 0; i < 3; ++i) { Card card = game.draw(currentPlayer); if (card != null) { cards.add(card); currentPlayer.reveal(card, this.controlCard, context); } } if (cards.size() == 0) { return; } Card toDiscard; if (cards.size() > 1) { toDiscard = nextPlayer.controlPlayer.advisor_cardToDiscard(context, cards.toArray(new Card[cards.size()])); } else { toDiscard = cards.get(0); } if (toDiscard == null || !cards.contains(toDiscard)) { Util.playerError(currentPlayer, "Advisor discard error, just picking the first card."); toDiscard = cards.get(0); } currentPlayer.discard(toDiscard, this.controlCard, context); cards.remove(toDiscard); GameEvent event = new GameEvent(GameEvent.Type.CardDiscarded, (MoveContext) context); event.card = toDiscard; event.responsible = this.controlCard; game.broadcastEvent(event); if (cards.size() > 0) { for(Card c : cards) { currentPlayer.hand.add(c); } } } private void butcher(Game game, MoveContext context, Player currentPlayer) { /*Take 2 Coin tokens. * You may trash a card from your hand and then pay any number of Coin tokens. * If you did trash a card, gain a card with a cost of up to the cost of the trashed card plus the number of Coin tokens you paid. */ currentPlayer.gainGuildsCoinTokens(2); sendGuildsTokenObtainedEvent(game, context); if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.butcher_cardToTrash(context); if (card != null) { currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); int value = card.getCost(context); boolean potion = card.costPotion(); if (currentPlayer.getGuildsCoinTokenCount() > 0) { // Offer the player the option of "spending" Guilds coin tokens prior to gaining a card int numTokensToSpend = currentPlayer.numGuildsCoinTokensToSpend(context); if (numTokensToSpend > 0 && numTokensToSpend <= currentPlayer.getGuildsCoinTokenCount()) { currentPlayer.spendGuildsCoinTokens(numTokensToSpend); value += numTokensToSpend; } card = currentPlayer.controlPlayer.butcher_cardToObtain(context, value, potion); if (card != null) { if (card.getCost(context) > value) { Util.playerError(currentPlayer, "Butcher error, new card does not have appropriate cost"); } else { if (!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Butcher error, pile is empty or card is not in the game."); } } } } } } } private void journeyman(Game game, MoveContext context, Player currentPlayer) { Card named = currentPlayer.controlPlayer.journeyman_cardToPick(context); currentPlayer.controlPlayer.namedCard(named, this.controlCard, context); ArrayList<Card> cardsToKeep = new ArrayList<Card>(); ArrayList<Card> cardsToDiscard = new ArrayList<Card>(); Card last = null; int diffCardsFound = 0; // search for the first 3 cards that were not named while (diffCardsFound < 3 && (last = context.game.draw(currentPlayer)) != null) { if (!last.equals(named)) { ++diffCardsFound; cardsToKeep.add(last); } else { cardsToDiscard.add(last); } currentPlayer.reveal(last, this.controlCard, context); } // Discard all matches for (Card c : cardsToDiscard) { currentPlayer.discard(c, this.controlCard, context); } // Place all other cards into the Player's hand for (Card c : cardsToKeep) { currentPlayer.hand.add(c); } } private void stonemason(Game game, MoveContext context, Player currentPlayer) { /*Trash a card from your hand, Gain 2 cards each costing less than it. When you buy this, you may overpay for it. If you do, gain 2 Action cards each costing the amount you overpaid. */ if (currentPlayer.getHand().size() > 0) { Card card = currentPlayer.controlPlayer.stonemason_cardToTrash(context); if (card != null) { currentPlayer.hand.remove(card); currentPlayer.trash(card, this.controlCard, context); int value = card.getCost(context) - 1; boolean potion = card.costPotion(); boolean cardGainError = false; if (value >= 0) { card = currentPlayer.controlPlayer.stonemason_cardToGain(context, value, potion); if (card != null) { if (!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Stone Mason card gain #1 error, pile is empty or card is not in the game."); cardGainError = true; } } if (!cardGainError) { card = currentPlayer.controlPlayer.stonemason_cardToGain(context, value, potion); if (card != null) { if (!currentPlayer.gainNewCard(card, this.controlCard, context)) { Util.playerError(currentPlayer, "Stone Mason card gain #2 error, pile is empty or card is not in the game."); } } } } } } } public void stoneMasonOverpay(MoveContext context) { if (context.overpayAmount > 0 || context.overpayPotions > 0) { // Gain two action cards each costing the amount overpaid Card c = context.player.stonemason_cardToGainOverpay(context, context.overpayAmount, (context.overpayPotions > 0 ? true : false)); if (c != null) { if (!context.player.gainNewCard(c, this.controlCard, context)) { Util.playerError(context.player, "Stone Mason overpay gain #1 error, pile is empty or card is not in the game."); } c = context.player.stonemason_cardToGainOverpay(context, context.overpayAmount, (context.overpayPotions > 0 ? true : false)); if (c != null) { if (!context.player.gainNewCard(c, this.controlCard, context)) { Util.playerError(context.player, "Stone Mason overpay gain #2 error, pile is empty or card is not in the game."); } } } } } private void doctor(Game game, MoveContext context, Player currentPlayer) { /*Name a card. * Reveal the top 3 cards of your deck. * Trash the matches. * Put the rest back on top in any order. When you buy this, you may overpay for it. For each $1 you overpaid, look at the top card of your deck; trash it, discard it, or put it back.*/ Card named = currentPlayer.controlPlayer.doctor_cardToPick(context); ArrayList<Card> revealedCards = new ArrayList<Card>(); for (int i = 0; i < 3; ++i) { Card card = game.draw(currentPlayer); if (card != null) { currentPlayer.reveal(card, this.controlCard, context); if (card.equals(named)) { currentPlayer.trash(card, this.controlCard, context); } else { revealedCards.add(card); } } } if (revealedCards.size() > 0) { ArrayList<Card> orderedCards = currentPlayer.controlPlayer.doctor_cardsForDeck(context, revealedCards); for (Card c : orderedCards) { currentPlayer.putOnTopOfDeck(c); } } } public void doctorOverpay(MoveContext context) { for (int i = 0; i < context.overpayAmount; ++i) { Card card = context.game.draw(context.player); Player.DoctorOverpayOption doo = context.player.doctor_chooseOption(context, card); switch(doo) { case TrashIt: context.player.trash(card, this.controlCard, context); break; case DiscardIt: context.player.discard(card, this.controlCard, context); break; case PutItBack: context.player.putOnTopOfDeck(card); break; default: break; } } } private void herald(Game game, MoveContext context, Player currentPlayer) { Card draw = game.draw(currentPlayer); if (draw != null) { currentPlayer.reveal(draw, this.controlCard, context); if (draw instanceof ActionCard) { context.freeActionInEffect++; ((ActionCardImpl) draw).play(game, context, false); context.freeActionInEffect--; } else { context.player.putOnTopOfDeck(draw); } } } public void heraldOverpay(MoveContext context, Player currentPlayer) { for (int i = 0; i < context.overpayAmount; ++i) { // Only allow a choice if there are cards in the discard pile if (currentPlayer.discard.size() > 0) { // Create a list of all cards in the player's discard pile ArrayList<Card> options = currentPlayer.getDiscard().toArrayList(); Collections.sort(options, new Util.CardNameComparator()); if (options.size() > 0) { Card cardToTopDeck = context.player.herald_cardTopDeck(context, options.toArray(new Card[options.size()])); if (cardToTopDeck != null) { currentPlayer.discard.remove(cardToTopDeck); currentPlayer.putOnTopOfDeck(cardToTopDeck); } } } } } }