package com.vdom.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Random; import com.vdom.api.ActionCard; import com.vdom.api.Card; import com.vdom.api.CardCostComparator; import com.vdom.api.CardValueComparator; import com.vdom.api.CurseCard; import com.vdom.api.GameEvent; import com.vdom.api.GameEventListener; import com.vdom.api.TreasureCard; import com.vdom.api.VictoryCard; import com.vdom.comms.SelectCardOptions; import com.vdom.comms.SelectCardOptions.ActionType; import com.vdom.core.Player.DoctorOverpayOption; public abstract class BasePlayer extends Player implements GameEventListener { protected static final Card[] EARLY_TRASH_CARDS = new Card[] { Cards.curse, Cards.estate, Cards.overgrownEstate, Cards.hovel, Cards.abandonedMine, Cards.ruinedLibrary, Cards.ruinedMarket, Cards.ruinedVillage, Cards.survivors, Cards.virtualRuins }; protected static final Card[] LATE_TRASH_CARDS = new Card[] { Cards.curse, Cards.copper, Cards.estate, Cards.overgrownEstate, Cards.abandonedMine, Cards.ruinedLibrary, Cards.ruinedMarket, Cards.ruinedVillage, Cards.survivors, Cards.virtualRuins }; protected Random rand = new Random(System.currentTimeMillis()); protected static final int COST_MAX = 11; protected int actionCardCount = 0; protected int throneRoomAndKingsCourtCount = 0; protected int potionCount = 0; protected int midGame; protected HashSet<Card> reactedSet = new HashSet<Card>(); @Override public void newGame(MoveContext context) { // When multiple games are played in one session, the same Player object // is used, so reset any fields in this method. turnCount = 0; throneRoomAndKingsCourtCount = 0; potionCount = 0; actionCardCount = 0; // All GameListeners are removed after every game, so we have to add ourselves back. Not very // Java-like, but does prevent a player from keeping around listeners that should be gone. context.addGameListener(this); midGame = 12; } public void gameEvent(GameEvent event) { // There are quite a few event types, found in the GameEvent.Type enum, that // are broadcast. if (event.getType() == GameEvent.Type.PlayingAction) { reactedSet.clear(); } if(event.getPlayer() == this && (event.getType() == GameEvent.Type.CardObtained || event.getType() == GameEvent.Type.BuyingCard)) { if(event.getCard() instanceof ActionCard) { actionCardCount++; } if(event.getCard().equals(Cards.throneRoom) || event.getCard().equals(Cards.kingsCourt)) { throneRoomAndKingsCourtCount++; } if(event.getCard().equals(Cards.potion)) { potionCount++; } } } public Card[] actionCardsToPlayInOrder(MoveContext context) { // Should never be called return null; } @Override public abstract Card doAction(MoveContext context); @Override public abstract Card doBuy(MoveContext context); @Override public Card getAttackReaction(MoveContext context, Card responsible, boolean defended, Card lastCard) { Card[] reactionCards = getReactionCards(defended); for (Card c : reactionCards) { if (!c.equals(Cards.marketSquare) && !c.equals(Cards.watchTower)) { if (c.equals(Cards.secretChamber) || c.equals(Cards.moat) || c.equals(Cards.horseTraders) || c.equals(Cards.beggar)) { if (!reactedSet.contains(c)) { reactedSet.add(c); return c; } } else { return c; } } } return null; } // //////////////////////// // Helper Methods // //////////////////////// protected Card bestCardInPlay(MoveContext context, int maxCost) { return bestCardInPlay(context, maxCost, false, false); } protected Card bestCardInPlay(MoveContext context, int maxCost, boolean exactCost) { return bestCardInPlay(context, maxCost, exactCost, false); } protected Card bestCardInPlay(MoveContext context, int maxCost, boolean exactCost, boolean potion) { return bestCardInPlay(context, maxCost, exactCost, potion, false, true); } protected Card bestCardInPlay(MoveContext context, int maxCost, boolean exactCost, boolean potion, boolean actionOnly, boolean victoryCardAllowed) { return bestCardInPlay(context, maxCost, exactCost, potion, actionOnly, victoryCardAllowed, maxCost); } protected Card bestCardInPlay(MoveContext context, int maxCost, boolean exactCost, boolean potion, boolean actionOnly, boolean victoryCardAllowed, int maxCostWithoutPotion) { boolean isBuy = (maxCost == -1); if (isBuy) { maxCost = maxCostWithoutPotion = COST_MAX; } Card[] cards = context.getCardsInGame(); ArrayList<Card> cardList = new ArrayList<Card>(); for (int i = 0; i < cards.length; i++) { Card card = cards[i]; if (card.equals(Cards.curse) || card.isShelter() || !Cards.isSupplyCard(card) || isTrashCard(card) || (card.equals(Cards.potion) && !shouldBuyPotion()) || (actionOnly && !(card instanceof ActionCard)) || (!victoryCardAllowed && (card instanceof VictoryCard)) ) { } else { cardList.add(card); } } if (cardList.isEmpty()) { return null; } int cost = maxCostWithoutPotion; int highestCost = 0; ArrayList<Card> randList = new ArrayList<Card>(); while (cost >= 0) { for (Card card : cardList) { int cardCost = card.getCost(context); if (cardCost == cost && context.getCardsLeftInPile(card) > 0) { if ((!exactCost && potion) || (card.costPotion() && potion) || (!card.costPotion() && !potion)) { if ((cardCost <= maxCostWithoutPotion && !card.costPotion()) || (cardCost <= maxCost)) { if (!isBuy || context.canBuy(card)) { if (highestCost == 0) { highestCost = cardCost; } } randList.add(card); } } } } if(exactCost) { break; } // We return cards within 1 cost to add variety... if(--cost < highestCost - 1) { break; } } if (randList.size() > 0) { return randList.get(this.rand.nextInt(randList.size())); } return null; } public Card lowestCard(MoveContext context, CardList cards, boolean anyVictory) { Card[] ret = lowestCards(context, cards, 1, anyVictory); if(ret == null) { return null; } return ret[0]; } public Card[] lowestCards(MoveContext context, CardList cards, int num, boolean anyVictory) { return lowestCards(context, cards.toArrayList(), num, anyVictory); } public Card[] lowestCards(MoveContext context, ArrayList<Card> cards, int num, boolean anyVictory) { if (cards == null) { return null; } if (cards.size() == 0) { return null; } if (cards.size() <= num) { return cards.toArray(new Card[0]); } ArrayList<Card> cardArray = new ArrayList<Card>(); for(Card c : cards) { cardArray.add(c); } ArrayList<Card> ret = new ArrayList<Card>(); for (int i = cardArray.size() - 1; i >= 0; i--) { Card card = cardArray.get(i); if(isTrashCard(card)) { if(card instanceof TreasureCard && anyVictory) { // Add trash treasure cards (copper) later after checking for victory cards below continue; } ret.add(card); cardArray.remove(i); if(ret.size() == num) { return ret.toArray(new Card[ret.size()]); } } } if(anyVictory) { // Add in the estate cards... for (int i = cardArray.size() - 1; i >= 0; i--) { Card card = cardArray.get(i); if (isOnlyVictory(card)) { ret.add(card); cardArray.remove(i); if(ret.size() == num) { return ret.toArray(new Card[ret.size()]); } } } for (int i = cardArray.size() - 1; i >= 0; i--) { Card card = cardArray.get(i); if (isTrashCard(card)) { if(card instanceof TreasureCard) { ret.add(card); cardArray.remove(i); if(ret.size() == num) { return ret.toArray(new Card[ret.size()]); } } } } } // By cost... int cost = 0; while(cost <= COST_MAX) { for (int i = cardArray.size() - 1; i >= 0; i--) { Card card = cardArray.get(i); if (card.getCost(context) == cost) { ret.add(card); cardArray.remove(i); if(ret.size() == num) { return ret.toArray(new Card[ret.size()]); } } } cost++; } // Add all... for (int i = cardArray.size() - 1; i >= 0; i--) { Card card = cardArray.get(i); ret.add(card); cardArray.remove(i); if(ret.size() == num) { return ret.toArray(new Card[ret.size()]); } } // Should never get here, but just in case... return ret.toArray(new Card[0]); } public Card pickOutCard(CardList cards, Card[] cardsToMatch) { Card[] ret = pickOutCards(cards, 1, cardsToMatch); if(ret == null) { return null; } return ret[0]; } public Card[] pickOutCards(CardList cards, int num, Card[] cardsToMatch) { if(cards == null) { return null; } if(cards.size() == 0) { return null; } ArrayList<Card> ret = new ArrayList<Card>(); for(Card match : cardsToMatch) { for(Card c : cards) { if(c.equals(match)) { ret.add(c); } if(ret.size() == num) { return ret.toArray(new Card[0]); } } } if(ret.size() == 0) { return null; } return ret.toArray(new Card[0]); } public Card[] getTrashCards() { if(turnCount < midGame) { return EARLY_TRASH_CARDS; } else { return LATE_TRASH_CARDS; } } protected Card[] getReactionCards(boolean defended) { ArrayList<Card> reactionCards = new ArrayList<Card>(); boolean moatSelected = false; boolean secretChamberSelected = false; for (Card c : getHand()) { if (c.equals(Cards.moat) && !defended && !moatSelected) { reactionCards.add(c); moatSelected = true; } else if (c.equals(Cards.secretChamber) && !secretChamberSelected) { reactionCards.add(c); secretChamberSelected = true; } else if (c.equals(Cards.horseTraders) || c.equals(Cards.watchTower) || c.equals(Cards.beggar) || c.equals(Cards.marketSquare)) { reactionCards.add(c); } } return reactionCards.toArray(new Card[0]); } @Override public Card[] topOfDeck_orderCards(MoveContext context, Card[] cards) { return cards; } // ////////////////// // Card interactions // ////////////////// @Override public Card workshop_cardToObtain(MoveContext context) { return bestCardInPlay(context, 4); } @Override public Card feast_cardToObtain(MoveContext context) { return bestCardInPlay(context, 5); } @Override public Card remodel_cardToTrash(MoveContext context) { //TODO: better logic if (context.getPlayer().getHand().size() == 0) { return null; } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c)) { return c; } } for(int i=0; i < 3; i++) { Card c = Util.randomCard(context.getPlayer().getHand()); if(!(c instanceof VictoryCard)) { return c; } } return Util.randomCard(context.getPlayer().getHand()); } @Override public Card remodel_cardToObtain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card[] militia_attack_cardsToKeep(MoveContext context) { ArrayList<Card> keepers = new ArrayList<Card>(); ArrayList<Card> discards = new ArrayList<Card>(); // Just add in the non-victory cards... for (Card card : context.attackedPlayer.getHand()) { if (!shouldDiscard(card)) { keepers.add(card); } else { discards.add(card); } } while (keepers.size() < 3) { keepers.add(discards.remove(0)); } // Still more than 3? Remove all but one action... while (keepers.size() > 3) { int bestAction = -1; boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i) instanceof ActionCard) { if (bestAction == -1) { bestAction = i; } else { if(keepers.get(i).getCost(context) > keepers.get(bestAction).getCost(context)) { keepers.remove(bestAction); bestAction = i; } else { keepers.remove(i); } removed = true; break; } } } if (!removed) { break; } } // Still more than 3? Start removing copper... while (keepers.size() > 3) { boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i).equals(Cards.copper)) { keepers.remove(i); removed = true; break; } } if (!removed) { break; } } // Still more than 3? Start removing silver... while (keepers.size() > 3) { boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i).equals(Cards.silver)) { keepers.remove(i); removed = true; break; } } if (!removed) { break; } } while (keepers.size() > 3) { keepers.remove(0); } return keepers.toArray(new Card[0]); } @Override public boolean chancellor_shouldDiscardDeck(MoveContext context) { return true; } @Override public TreasureCard mine_treasureFromHandToUpgrade(MoveContext context) { ArrayList<TreasureCard> handCards = context.getPlayer().getTreasuresInHand(); Collections.sort(handCards, new CardValueComparator()); HashSet<Integer> treasureCardValues = new HashSet<Integer>(); for (Card card : context.getTreasureCardsInGame()) { if (context.getCardsLeftInPile(card) > 0) treasureCardValues.add(card.getCost(context)); } for (int i = 0; i < handCards.size(); i++) { TreasureCard card = handCards.get(i); if (treasureCardValues.contains(card.getCost(context) + 3)) return card; } if (handCards.size() > 0) return handCards.get(0); return null; } @Override public TreasureCard mine_treasureToObtain(MoveContext context, int cost, boolean potion) { TreasureCard newCard = null; int newCost = -1; for (Card card : context.getTreasureCardsInGame()) { if (context.getCardsLeftInPile(card) > 0 && card.getCost(context) <= cost && card.getCost(context) >= newCost) { if (potion || (!potion && !card.costPotion())) { newCard = (TreasureCard) card; newCost = card.getCost(context); } } } return newCard; } @Override public Card[] chapel_cardsToTrash(MoveContext context) { return pickOutCards(context.getPlayer().getHand(), 4, getTrashCards()); } @Override public Card[] cellar_cardsToDiscard(MoveContext context) { ArrayList<Card> cards = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if ((!(card instanceof ActionCard) && !(card instanceof TreasureCard)) || card.equals(Cards.cellar) || card.equals(Cards.copper)) { cards.add(card); } } if (context.getActionsLeft() == 0) { for (Card c : context.getPlayer().getHand()) { if ((c instanceof ActionCard)) { cards.add(c); } } } return cards.toArray(new Card[0]); } @Override public boolean library_shouldKeepAction(MoveContext context, ActionCard action) { if (context.getActionsLeft() == 0) { return false; } for (Card card : context.getPlayer().getHand()) { if (card instanceof ActionCard) { return false; } } return true; } @Override public boolean spy_shouldDiscard(MoveContext context, Player targetPlayer, Card card) { boolean ret; if (isOnlyVictory(card) || card.equals(Cards.copper) || card.equals(Cards.curse)) ret = false; else { ret = true; } if (targetPlayer == this) { ret = !ret; } return ret; } @Override public boolean scryingPool_shouldDiscard(MoveContext context, Player targetPlayer, Card card) { return controlPlayer.spy_shouldDiscard(context, targetPlayer, card); } // //////////////////////////////////////////// // Card interactions - cards from the Intrigue // //////////////////////////////////////////// @Override public Card[] secretChamber_cardsToDiscard(MoveContext context) { // Discard all victory cards ArrayList<Card> cards = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if (shouldDiscard(card)) { cards.add(card); } } return cards.toArray(new Card[0]); } @Override public Card[] secretChamber_cardsToPutOnDeck(MoveContext context) { if (context.getPlayer().getHand().size() <= 2) { return context.getPlayer().getHand().toArray(); } // Just putting back the first two cards, could be quite a bit smarter here... Card[] cards = new Card[2]; cards[0] = context.getPlayer().getHand().get(0); cards[1] = context.getPlayer().getHand().get(1); return cards; } @Override public PawnOption[] pawn_chooseOptions(MoveContext context) { return new PawnOption[] { PawnOption.AddAction, PawnOption.AddGold }; } @Override public TorturerOption torturer_attack_chooseOption(MoveContext context) { if (game.pileSize(Cards.curse) <= 0) { return Player.TorturerOption.TakeCurse; } CardList h = context.attackedPlayer.getHand(); for (Card c : h) { if(c.equals(Cards.watchTower) || c.equals(Cards.trader)) { return Player.TorturerOption.TakeCurse; } } if (h.size() < 5) { int count = 0; for (Card c : h) { if (shouldDiscard(c) || c.equals(Cards.copper)) { count++; } } if (count >= 2) { return Player.TorturerOption.DiscardTwoCards; } return Player.TorturerOption.TakeCurse; } else { int count = 0; for (Card c : h) { if (shouldDiscard(c)) { count++; } } if (count >= 2) { return Player.TorturerOption.DiscardTwoCards; } if(context.getCoinAvailableForBuy() >= 8) { return Player.TorturerOption.TakeCurse; } return Player.TorturerOption.DiscardTwoCards; } } @Override public StewardOption steward_chooseOption(MoveContext context) { return StewardOption.AddGold; } @Override public Card[] steward_cardsToTrash(MoveContext context) { // This would normally need to return two cards, but since we always return AddGold for the // steward_chooseOption(), this should never be called return null; } @Override public Card swindler_cardToSwitch(MoveContext context, int cost, boolean potion) { Card[] cards = context.getCardsInGame(); ArrayList<Card> changeList = new ArrayList<Card>(); for (Card card : cards) { if (Cards.isSupplyCard(card) && card.getCost(context) == cost && context.getCardsLeftInPile(card) > 0 && card.costPotion() == potion) { changeList.add(card); } } boolean latest = game.isColonyInGame()? context.getCardsLeftInPile(Cards.province) < Game.numPlayers || context.getCardsLeftInPile(Cards.colony) < Game.numPlayers : context.getCardsLeftInPile(Cards.province) < Game.numPlayers; if (changeList.contains(Cards.curse)) { return Cards.curse; } else if (changeList.contains(Cards.estate) && !latest) { return Cards.estate; } else if (changeList.contains(Cards.duchy) && !latest) { return Cards.duchy; } else if (changeList.contains(Cards.peddler)) { return Cards.peddler; } else if (changeList.contains(Cards.potion)) { return Cards.potion; } if (changeList.size() > 0) { return changeList.get(rand.nextInt(changeList.size())); } return null; } @Override public Card[] torturer_attack_cardsToDiscard(MoveContext context) { return lowestCards(context, context.attackedPlayer.getHand(), 2, true); } public Card courtyard_cardToPutBackOnDeck(MoveContext context) { return context.getPlayer().getHand().get(0); } @Override public boolean baron_shouldDiscardEstate(MoveContext context) { return true; } @Override public Card ironworks_cardToObtain(MoveContext context) { return bestCardInPlay(context, 4); } @Override public Card masquerade_cardToPass(MoveContext context) { return lowestCard(context, context.getPlayer().getHand(), false); } @Override public Card masquerade_cardToTrash(MoveContext context) { return pickOutCard(context.getPlayer().getHand(), getTrashCards()); } @Override public boolean miningVillage_shouldTrashMiningVillage(MoveContext context) { if(turnCount >= midGame || context.getCoinAvailableForBuy() >= 6) { return true; } return false; } @Override public Card saboteur_cardToObtain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card[] scout_orderCards(MoveContext context, Card[] cards) { return cards; } @Override public Card[] mandarin_orderCards(MoveContext context, Card[] cards) { return cards; } @Override public NoblesOption nobles_chooseOptions(MoveContext context) { int actionCards = 0; for (Card card : context.getPlayer().getHand()) { if ((card instanceof ActionCard)) { actionCards++; } } if ((context.getActionsLeft() == 0) && (actionCards > 0)) { return Player.NoblesOption.AddActions; } return Player.NoblesOption.AddCards; } // Must return two cards if possible. @Override public Card[] tradingPost_cardsToTrash(MoveContext context) { return lowestCards(context, context.getPlayer().getHand(), 2, true); } @Override public Card wishingWell_cardGuess(MoveContext context, ArrayList<Card> cardList) { return Cards.silver; } @Override public Card upgrade_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } if (context.getPlayer().getHand().size() == 1) { return context.getPlayer().getHand().get(0); } for (Card c : context.getPlayer().getHand()) { if(c.equals(Cards.curse)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(c instanceof VictoryCard) { continue; } for(Card avail : context.getCardsInGame()) { if(avail.getCost(context) == c.getCost(context) + 1 && context.getCardsLeftInPile(avail) > 0 && context.getEmbargos(avail) == 0 && !avail.costPotion()) { return c; } } } return null; } @Override public Card upgrade_cardToObtain(MoveContext context, int exactCost, boolean potion) { return bestCardInPlay(context, exactCost, true, potion); } @Override public MinionOption minion_chooseOption(MoveContext context) { if (context.getCoinAvailableForBuy() >= 5) { return Player.MinionOption.AddGold; } if (context.getPlayer().getHand().size() <= 3) { return Player.MinionOption.RolloverCards; } return Player.MinionOption.AddGold; } // //////////////////////////////////////////// // Card interactions - cards from the Seaside // //////////////////////////////////////////// @Override public Card[] ghostShip_attack_cardsToPutBackOnDeck(MoveContext context) { ArrayList<Card> cards = new ArrayList<Card>(); for (int i = 0; i < context.attackedPlayer.getHand().size() - 3; i++) { cards.add(context.attackedPlayer.getHand().get(i)); } return cards.toArray(new Card[0]); } @Override public Card[] warehouse_cardsToDiscard(MoveContext context) { ArrayList<Card> cardsToDiscard = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if (shouldDiscard(card)) { cardsToDiscard.add(card); } if (cardsToDiscard.size() == 3) { break; } } if (cardsToDiscard.size() < 3) { ArrayList<Card> handCopy = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { handCopy.add(card); } for (Card card : cardsToDiscard) { handCopy.remove(card); } while (cardsToDiscard.size() < 3) { cardsToDiscard.add(handCopy.remove(0)); // Card c = pickACard(context, "Warehouse:Card " + (cardsToDiscard.size() + 1) + // " to discard", handCopy.toArray(new Card[0]), false); // handCopy.remove(c); // cardsToDiscard.add(c); } } return cardsToDiscard.toArray(new Card[0]); } @Override public Card salvager_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } return lowestCard(context, context.getPlayer().getHand(), false); } @Override public boolean pirateShip_takeTreasure(MoveContext context) { if (getPirateShipTreasure() == 0) { return false; } if (context.getCoinAvailableForBuy() >= 8) { return false; } return this.rand.nextFloat() < getPirateShipTreasure() / 5f; } public boolean nativeVillage_takeCards(MoveContext context) { if (getNativeVillage().size() == 0) { return false; } // Half the time take the cards, half the time add one return rand.nextBoolean(); } @Override public Card smugglers_cardToObtain(MoveContext context) { // Find the most expensive card that is still 6 or less Card bestCard = null; for (Card card : context.getCardsObtainedByLastPlayer()) { if (context.getCardsLeftInPile(card) > 0 && card.getCost(context) < 7 && (card.getCost(context) < 6 || !card.costPotion())) { if (bestCard == null || card.getCost(context) > bestCard.getCost(context)) { bestCard = card; } } } return bestCard; } @Override public Card island_cardToSetAside(MoveContext context) { for (Card card : context.getPlayer().getHand()) { if (isOnlyVictory(card)) { return card; } } return lowestCard(context, context.getPlayer().getHand(), true); } @Override public Card haven_cardToSetAside(MoveContext context) { //TODO: better logic if (context.getPlayer().getHand().size() == 0) { return null; } return context.getPlayer().getHand().get(0); } @Override public boolean navigator_shouldDiscardTopCards(MoveContext context, Card[] cards) { // Discard them if there is more than 2 victory cards int victoryCount = 0; for (Card card : cards) { if (shouldDiscard(card)) { victoryCount++; } } return (victoryCount > 2); } @Override public Card[] navigator_cardOrder(MoveContext context, Card[] cards) { return cards; } @Override public Card embargo_supplyToEmbargo(MoveContext context) { // Embargo a random card Card card; ArrayList<Card> cardList = new ArrayList<Card> (Arrays.asList(context.getCardsInGame())); do { card = cardList.remove(rand.nextInt(cardList.size() - 1)); } while (!game.isValidEmbargoPile(card)); return card; } public Card lookout_cardToTrash(MoveContext context, Card[] cards) { CardList cl = new CardList(context.getPlayer(), context.getPlayer().getPlayerName()); for(Card c : cards) cl.add(c); return lowestCard(context, cl, false); } public Card lookout_cardToDiscard(MoveContext context, Card[] cards) { CardList cl = new CardList(context.getPlayer(), context.getPlayer().getPlayerName()); for(Card c : cards) cl.add(c); return lowestCard(context, cl, false); } @Override public Card ambassador_revealedCard(MoveContext context) { return pickOutCard(context.getPlayer().getHand(), getTrashCards()); } @Override public int ambassador_returnToSupplyFromHand(MoveContext context, Card card) { // Return as many as possible int count = 0; for (Card cardInHand : context.getPlayer().getHand()) { if (cardInHand.equals(card)) { count++; } if (count == 2) { break; } } return count; } @Override public boolean pearlDiver_shouldMoveToTop(MoveContext context, Card card) { if (isOnlyVictory(card) || card.equals(Cards.curse) || card.equals(Cards.copper)) { return false; } return true; } @Override public boolean explorer_shouldRevealProvince(MoveContext context) { return true; } @Override public ActionCard university_actionCardToObtain(MoveContext context) { //TODO: better logic return (ActionCard) bestCardInPlay(context, 5, false, false, true, false); } @Override public Card apprentice_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card transmute_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public boolean alchemist_backOnDeck(MoveContext context) { return true; } @Override public TreasureCard herbalist_backOnDeck(MoveContext context, TreasureCard[] cards) { if(cards == null || cards.length == 0) { return null; } int index = 0; int cost = cards[0].getCost(context); for(int i=1; i < cards.length; i++) { if(cards[i].getCost(context) > cost) { index = i; cost = cards[i].getCost(context); } } return cards[index]; } @Override public ArrayList<Card> apothecary_cardsForDeck(MoveContext context, ArrayList<Card> cards) { return cards; } @Override public Card bishop_cardToTrashForVictoryTokens(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card bishop_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card expand_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card expand_cardToObtain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card[] forge_cardsToTrash(MoveContext context) { return pickOutCards(context.getPlayer().getHand(), context.getPlayer().getHand().size(), getTrashCards()); } @Override public Card forge_cardToObtain(MoveContext context, int exactCost) { return bestCardInPlay(context, exactCost, true); } @Override public Card[] goons_attack_cardsToKeep(MoveContext context) { return controlPlayer.militia_attack_cardsToKeep(context); } @Override public TreasureCard mint_treasureToMint(MoveContext context) { Card cardToMint = null; int cost = -1; for (Card c : context.getPlayer().getTreasuresInHand()) { if (c instanceof TreasureCard && context.game.pileSize(c) > 0) { if(c.getCost(context) > cost) { cardToMint = c; cost = c.getCost(context); } } } return (TreasureCard) cardToMint; } @Override public boolean mountebank_attack_shouldDiscardCurse(MoveContext context) { return true; } @Override public Card[] rabble_attack_cardOrder(MoveContext context, Card[] cards) { return cards; } @Override public Card tradeRoute_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card[] vault_cardsToDiscardForGold(MoveContext context) { // TODO:: Finish prosperity ArrayList<Card> discardCards = context.getPlayer().getHand().toArrayList(); for (Iterator<Card> it = discardCards.iterator(); it.hasNext();) { Card card = it.next(); if (card instanceof TreasureCard && !card.equals(Cards.copper)) it.remove(); } return discardCards.toArray(new Card[0]); } @Override public Card[] vault_cardsToDiscardForCard(MoveContext context) { // TODO:: Finish prosperity return pickOutCards(context.getPlayer().getHand(), 2, getTrashCards()); } @Override public Card contraband_cardPlayerCantBuy(MoveContext context) { ArrayList<Card> cantBuy = context.getCantBuy(); if (game.isColonyInGame() && turnCount > midGame && !cantBuy.contains(Cards.colony)) { return Cards.colony; } else if (game.isColonyInGame() && turnCount < midGame && game.pileSize(Cards.platinum) > 0 && !cantBuy.contains(Cards.platinum)) { return Cards.platinum; } else if (turnCount > midGame && !cantBuy.contains(Cards.province)) { return Cards.province; } else if (game.isColonyInGame() && game.pileSize(Cards.platinum) > 0 && !cantBuy.contains(Cards.platinum)) { return Cards.platinum; } else if (turnCount > midGame && game.pileSize(Cards.duchy) > 0 && !cantBuy.contains(Cards.duchy)) { return Cards.duchy; } else if (game.pileSize(Cards.gold) > 0 && !cantBuy.contains(Cards.gold)) { return Cards.gold; } else if (turnCount > midGame && !cantBuy.contains(Cards.duchy)) { return Cards.duchy; } else { return Cards.silver; } } @Override public ActionCard kingsCourt_cardToPlay(MoveContext context) { //TODO better logic for (Card c : context.getPlayer().getHand()) { if(c instanceof ActionCard) { return (ActionCard) c; } } return null; } @Override public ActionCard throneRoom_cardToPlay(MoveContext context) { return controlPlayer.kingsCourt_cardToPlay(context); } @Override public boolean loan_shouldTrashTreasure(MoveContext context, TreasureCard treasure) { // TODO:: Finish prosperity int money = getCurrencyTotal(context); for (Card trash : getTrashCards()) { if (trash.equals(treasure) && money >= 4) { return true; } } return false; } @Override public boolean royalSeal_shouldPutCardOnDeck(MoveContext context, Card card) { if(isOnlyVictory(card)) { return false; } return true; } @Override public WatchTowerOption watchTower_chooseOption(MoveContext context, Card card) { if(isTrashCard(card)) { return WatchTowerOption.Trash; } if(isOnlyVictory(card) || card.equals(Cards.copper)) { return WatchTowerOption.Normal; } return WatchTowerOption.TopOfDeck; } @Override public ArrayList<TreasureCard> treasureCardsToPlayInOrder(MoveContext context) { ArrayList<TreasureCard> ret = new ArrayList<TreasureCard>(); ArrayList<TreasureCard> cardArray = new ArrayList<TreasureCard>(); for (Card c : context.getPlayer().getHand()) { if(c instanceof TreasureCard) { cardArray.add((TreasureCard) c); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(card.equals(Cards.contraband)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if (card.equals(Cards.royalSeal)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(card.equals(Cards.counterfeit)) { ret.add(0, cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(!card.equals(Cards.bank) && !card.equals(Cards.venture) && !card.equals(Cards.hornOfPlenty) && !card.equals(Cards.illGottenGains)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if (card.equals(Cards.illGottenGains)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(card.equals(Cards.venture)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(card.equals(Cards.hornOfPlenty)) { ret.add(cardArray.remove(i)); } } for (int i = cardArray.size() - 1; i >= 0; i--) { TreasureCard card = cardArray.get(i); if(card.equals(Cards.bank)) { ret.add(cardArray.remove(i)); } } return ret; } @Override public ActionCard[] golem_cardOrder(MoveContext context, ActionCard[] cards) { return cards; } @Override public Card hamlet_cardToDiscardForAction(MoveContext context) { int actionCards = 0; for (Card c : context.getPlayer().getHand()) { if(c instanceof ActionCard) { actionCards++; } } if(actionCards == 0) { return null; } for (Card c : context.getPlayer().getHand()) { if(c.equals(Cards.curse)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c) && isOnlyVictory(c)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c)) { return c; } } return null; } @Override public Card hamlet_cardToDiscardForBuy(MoveContext context) { return null; } @Override public Card hornOfPlenty_cardToObtain(MoveContext context, int maxCost) { return bestCardInPlay(context, maxCost); } @Override public Card[] horseTraders_cardsToDiscard(MoveContext context) { return lowestCards(context, context.getPlayer().getHand(), 2, true); } @Override public JesterOption jester_chooseOption(MoveContext context, Player targetPlayer, Card card) { if(card.getCost(context) > 2) { return JesterOption.GainCopy; } return JesterOption.GiveCopy; } @Override public Card remake_cardToTrash(MoveContext context) { Card c = pickOutCard(context.getPlayer().getHand(), new Card[] { Cards.curse, Cards.estate }); if(c == null) { for (Card check : context.getPlayer().getHand()) { if((check.getCost(context) == 7 && context.canBuy(Cards.province)) || (turnCount >= midGame && check.getCost(context) == 4 && context.canBuy(Cards.duchy))){ c = check; break; } } } if(c == null) { for (Card check : context.getPlayer().getHand()) { if(isOnlyVictory(check)) continue; Card best = bestCardInPlay(context, check.getCost(context) + 1); if(best != null) { c = check; break; } } } if (c == null) { c = Util.randomCard(context.getPlayer().getHand()); } return c; } @Override public Card remake_cardToObtain(MoveContext context, int exactCost, boolean potion) { return bestCardInPlay(context, exactCost, true, potion); } @Override public boolean tournament_shouldRevealProvince(MoveContext context) { return true; } @Override public TournamentOption tournament_chooseOption(MoveContext context) { for(Card c : context.getCardsInGame()) { if(c.isPrize() && context.getPileSize(c) > 0) { return TournamentOption.GainPrize; } } return TournamentOption.GainDuchy; } @Override public Card tournament_choosePrize(MoveContext context) { for(Card c : context.getCardsInGame()) { if(c.isPrize() && context.getPileSize(c) > 0) { return c; } } return null; } @Override public Card[] youngWitch_cardsToDiscard(MoveContext context) { return lowestCards(context, context.getPlayer().getHand(), 2, true); } @Override public Card[] followers_attack_cardsToKeep(MoveContext context) { return controlPlayer.militia_attack_cardsToKeep(context); } @Override public TrustySteedOption[] trustySteed_chooseOptions(MoveContext context) { TrustySteedOption[] ret; do { ret = new TrustySteedOption[]{ TrustySteedOption.values()[rand.nextInt(TrustySteedOption.values().length)], TrustySteedOption.values()[rand.nextInt(TrustySteedOption.values().length)], }; } while( ret[0] == ret[1] ); return ret; } @Override public VictoryCard bureaucrat_cardToReplace(MoveContext context) { // Not sure on this logic... Card[] cards = getVictoryInHand().toArray(new Card[] {}); if (cards.length == 0) return null; int actions = 0; for (Card card : cards) { if (card instanceof ActionCard) { actions++; } } if(actions > 1) { for (Card card : cards) { if (card instanceof VictoryCard && !isOnlyVictory(card)) { return (VictoryCard) card; } } } for (Card card : cards) { if (card instanceof VictoryCard && isOnlyVictory(card)) { return (VictoryCard) card; } } for (Card card : cards) { if (card instanceof VictoryCard) { return (VictoryCard) card; } } return null; } @Override public TreasureCard thief_treasureToTrash(MoveContext context, TreasureCard[] treasures) { return getBestTreasureCard(context, treasures); } @Override public TreasureCard[] thief_treasuresToGain(MoveContext context, TreasureCard[] treasures) { ArrayList<TreasureCard> cards = new ArrayList<TreasureCard>(); for(TreasureCard c : treasures) { if(!isTrashCard(c)) { cards.add(c); } } return cards.toArray(new TreasureCard[0]); } @Override public TreasureCard pirateShip_treasureToTrash(MoveContext context, TreasureCard[] treasures) { return getBestTreasureCard(context, treasures); } public TreasureCard getBestTreasureCard(MoveContext context, TreasureCard[] treasures) { if(treasures == null) { return null; } if(treasures.length == 1) { return treasures[0]; } int index = 0; int cost = treasures[0].getCost(context); for(int i=1; i < treasures.length; i++) { if(treasures[i].getCost(context) > cost) { index = i; cost = treasures[i].getCost(context); } } return treasures[index]; } public VictoryCard getBestVictoryCard(MoveContext context) { ArrayList<VictoryCard> cards = new ArrayList<VictoryCard>(); for (Card c : context.getVictoryCardsInGame()) { cards.add((VictoryCard) c); } return getBestVictoryCard(context, cards.toArray(new VictoryCard[0])); } public VictoryCard getBestVictoryCard(MoveContext context, VictoryCard[] cards) { if(cards == null) { return null; } if(cards.length == 1) { return cards[0]; } int index = 0; int vp = cards[0].getVictoryPoints(); for(int i=1; i < cards.length; i++) { if(cards[i].getVictoryPoints() > vp) { index = i; vp = cards[i].getVictoryPoints(); } } return cards[index]; } public boolean isOnlyTreasure(Card card) { if(!(card instanceof TreasureCard)) { return false; } if(card instanceof ActionCard || card instanceof VictoryCard) { return false; } return true; } public boolean isTrashCard(Card card) { for(Card trash : getTrashCards()) { if(trash.equals(card)) { return true; } } return false; } public boolean isAttackCard(Card card) { if(card instanceof ActionCard ) { ActionCard aCard = (ActionCard) card; return aCard.isAttack(); } return false; } public boolean isOnlyVictory(Card card) { if(!(card instanceof VictoryCard)) { return false; } if(card instanceof ActionCard || card instanceof TreasureCard) { return false; } return true; } public boolean isCurse(Card card) { return card instanceof CurseCard; } public boolean shouldDiscard(Card card) { return isCurse(card) || isOnlyVictory(card); } public boolean shouldBuyPotion() { if(potionCount > 2) { return false; } else if(potionCount > 1 && rand.nextInt(5) > 0) { return false; } else if(potionCount > 0 && rand.nextInt(3) > 0) { return false; } return true; } @Override public boolean duchess_shouldDiscardCardFromTopOfDeck(MoveContext context, Card card) { if(isTrashCard(card)) { return true; } if(isOnlyVictory(card)) { return true; } return false; } @Override public Card oasis_cardToDiscard(MoveContext context) { return lowestCard(context, context.getPlayer().getHand(), true); } @Override public Card develop_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card develop_lowCardToGain(MoveContext context, int cost, boolean potion) { return bestCardInPlay(context, cost, true, potion); } @Override public Card develop_highCardToGain(MoveContext context, int cost, boolean potion) { return bestCardInPlay(context, cost, true, potion); } @Override public Card[] develop_orderCards(MoveContext context, Card[] cards) { return cards; } @Override public boolean foolsGold_shouldTrash(MoveContext context) { return (game.pileSize(Cards.gold) > 0); } @Override public boolean duchess_shouldGainBecauseOfDuchy(MoveContext context) { return true; } @Override public TreasureCard nobleBrigand_silverOrGoldToTrash(MoveContext context, TreasureCard[] silverOrGoldCards) { if(silverOrGoldCards[0].getCost(context) >= silverOrGoldCards[1].getCost(context)) { return silverOrGoldCards[0]; } return silverOrGoldCards[1]; } @Override public boolean jackOfAllTrades_shouldDiscardCardFromTopOfDeck(MoveContext context, Card card) { if(isTrashCard(card) || isOnlyVictory(card)) { return true; } return false; } @Override public Card jackOfAllTrades_nonTreasureToTrash(MoveContext context) { for (Card card : context.getPlayer().getHand()) { if(isTrashCard(card) && !(card instanceof TreasureCard)) { return card; } } return null; } @Override public TreasureCard spiceMerchant_treasureToTrash(MoveContext context) { for (Card card : context.getPlayer().getHand()) { for(Card trash : getTrashCards()) { if(trash.equals(card) && (card instanceof TreasureCard)) { return (TreasureCard) card; } } } return null; } @Override public SpiceMerchantOption spiceMerchant_chooseOption(MoveContext context) { //TODO: better logic if(rand.nextBoolean()) return SpiceMerchantOption.AddGoldAndBuy; else return SpiceMerchantOption.AddCardsAndAction; } @Override public Card[] embassy_cardsToDiscard(MoveContext context) { return controlPlayer.warehouse_cardsToDiscard(context); } @Override public Card[] cartographer_cardsFromTopOfDeckToDiscard(MoveContext context, Card[] cards) { ArrayList<Card> cardsToDiscard = new ArrayList<Card>(); for(Card card : cards) { if(isTrashCard(card) || isOnlyVictory(card)) { cardsToDiscard.add(card); } } return cardsToDiscard.toArray(new Card[0]); } @Override public Card[] cartographer_cardOrder(MoveContext context, Card[] cards) { return cards; } @Override public boolean tunnel_shouldReveal(MoveContext context) { return true; } @Override public ActionCard scheme_actionToPutOnTopOfDeck(MoveContext context, ActionCard[] actions) { if(actions == null || actions.length == 0) { return null; } int i = 0; int cost = actions[0].getCost(context); for(int index = 1; index < actions.length; index++) { if(actions[index].getCost(context) >= cost) { cost = actions[index].getCost(context); i = index; } } return actions[i]; } @Override public boolean trader_shouldGainSilverInstead(MoveContext context, Card card) { if(isTrashCard(card)) { return true; } return false; } @Override public Card trader_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card != null) return card; return context.getPlayer().getHand().get(0); } @Override public boolean oracle_shouldDiscard(MoveContext context, Player player, ArrayList<Card> cards) { boolean discard = true; for(Card c : cards) { if(isTrashCard(c) || isOnlyVictory(c)) { discard = false; break; } } if(player == this) { discard = !discard; } return discard; } @Override public Card[] oracle_orderCards(MoveContext context, Card[] cards) { return cards; } @Override public boolean illGottenGains_gainCopper(MoveContext context) { return false; } @Override public Card haggler_cardToObtain(MoveContext context, int maxCost, boolean potion) { if (maxCost < 0) return null; return bestCardInPlay(context, maxCost, false, potion, false, false, potion ? maxCost + 1 : maxCost); } @Override public Card[] inn_cardsToDiscard(MoveContext context) { ArrayList<Card> cardsToDiscard = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if (shouldDiscard(card)) { cardsToDiscard.add(card); } if (cardsToDiscard.size() == 2) { break; } } if (cardsToDiscard.size() < 2) { ArrayList<Card> handCopy = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { handCopy.add(card); } for (Card card : cardsToDiscard) { handCopy.remove(card); } while (cardsToDiscard.size() < 2) { cardsToDiscard.add(handCopy.remove(0)); } } return cardsToDiscard.toArray(new Card[0]); } @Override public boolean inn_shuffleCardBackIntoDeck(MoveContext context, ActionCard card) { return true; } @Override public Card borderVillage_cardToObtain(MoveContext context) { return bestCardInPlay(context, Cards.borderVillage.getCost(context) - 1); } @Override public Card farmland_cardToTrash(MoveContext context) { return controlPlayer.remodel_cardToTrash(context); } @Override public Card farmland_cardToObtain(MoveContext context, int exactCost, boolean potion) { return bestCardInPlay(context, exactCost, true, potion); } @Override public TreasureCard stables_treasureToDiscard(MoveContext context) { for (Card card : context.getPlayer().getHand()) { for(Card trash : getTrashCards()) { if(trash.equals(card) && (card instanceof TreasureCard)) { return (TreasureCard) card; } } } if (Game.rand.nextBoolean() && context.getPlayer().getHand().contains(Cards.silver)) { return (TreasureCard) context.getPlayer().fromHand(Cards.silver); } return null; } @Override public Card mandarin_cardToReplace(MoveContext context) { if (context.getActionsLeft() == 0) { for (Card card : context.getPlayer().getHand()) { if (card instanceof ActionCard) { return card; } } } //TODO: better logic return Util.randomCard(context.getPlayer().getHand()); } @Override public Card[] margrave_attack_cardsToKeep(MoveContext context) { return controlPlayer.militia_attack_cardsToKeep(context); } @Override public Card rats_cardToTrash(MoveContext context) { ArrayList<Card> nonRatsChoices = new ArrayList<Card>(); // Look for the low hanging fruit -- cards generally considered trash for (Card c : context.getPlayer().getHand()) { if (isTrashCard(c)) { return c; } else if (c.getType() != Cards.Type.Rats) { // Build a list of the cards we can trash nonRatsChoices.add(c); } } if (nonRatsChoices.size() > 1) { Collections.sort(nonRatsChoices, new CardCostComparator()); } return nonRatsChoices.get(0); } @Override public boolean revealBane(MoveContext context) { return true; } @Override public PutBackOption selectPutBackOption(MoveContext context, List<PutBackOption> options) { Collections.sort(options); return options.get(0); } @Override public SquireOption squire_chooseOption(MoveContext context) { return SquireOption.AddActions; } @Override public Card altar_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card altar_cardToObtain(MoveContext context) { return bestCardInPlay(context, 5); } @Override public boolean beggar_shouldDiscard(MoveContext context) { return true; } @Override public Card armory_cardToObtain(MoveContext context) { return bestCardInPlay(context, 4); } @Override public Card squire_cardToObtain(MoveContext context) { ArrayList<Card> options = new ArrayList<Card>(); for (AbstractCardPile pile : game.piles.values()) { if ((pile.card() instanceof ActionCard) && (pile.getCount() > 0)) { ActionCard ac = (ActionCard) pile.card(); if (ac.isAttack()) { options.add(pile.card()); } } } if (options.size() > 0) { return Util.randomCard(options); } else { return null; } } @Override public boolean catacombs_shouldDiscardTopCards(MoveContext context, Card[] array) { int discards = 0; for (Card c : array) if (shouldDiscard(c)) discards++; return (discards > 1); } @Override public Card catacombs_cardToObtain(MoveContext context) { return bestCardInPlay(context, Math.max(0, game.getPile(Cards.catacombs).card().getCost(context) - 1)); } @Override public CountFirstOption count_chooseFirstOption(MoveContext context) { if (getCurrencyTotal(context) < 6) return Player.CountFirstOption.GainCopper; return Player.CountFirstOption.PutOnDeck; } @Override public CountSecondOption count_chooseSecondOption(MoveContext context) { if (game.pileSize(Cards.colony) > 3 || game.pileSize(Cards.province) > 3) return Player.CountSecondOption.Coins; else return Player.CountSecondOption.GainDuchy; } @Override public Card[] count_cardsToDiscard(MoveContext context) { return lowestCards(context, hand, 2, true); } @Override public Card count_cardToPutBackOnDeck(MoveContext context) { return Util.randomCard(hand); } @Override public Card forager_cardToTrash(MoveContext context) { Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card == null) { card = lowestCard(context, context.getPlayer().getHand(), false); } return card; } @Override public Card deathCart_actionToTrash(MoveContext context) { Card ac = null; for (Card c : context.player.hand) { if (c instanceof ActionCard) { ac = c; break; } } return ac; } @Override public GraverobberOption graverobber_chooseOption(MoveContext context) { boolean trashContainsValidCard = false; for (Card c : context.game.trashPile) { if (c.getCost(context) >= 3 && c.getCost(context) <= 6) { trashContainsValidCard = true; break; } } if (trashContainsValidCard) { return GraverobberOption.GainFromTrash; } else { return GraverobberOption.TrashActionCard; } } @Override public Card graverobber_cardToGainFromTrash(MoveContext context) { ArrayList<Card> options = new ArrayList<Card>(); for (Card c : game.trashPile) { if (c.getCost(context) >= 3 && c.getCost(context) <= 6) { options.add(c); } } return Util.randomCard(options); } @Override public Card graverobber_cardToTrash(MoveContext context) { CardList ac = new CardList(controlPlayer, getPlayerName(false)); for (Card c : hand) { if (c instanceof ActionCard) { ac.add(c); } } if (ac.size() > 0) { Card card = pickOutCard(ac, getTrashCards()); if (card == null) { card = lowestCard(context, ac, false); } return card; } return null; } @Override public Card graverobber_cardToReplace(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public HuntingGroundsOption huntingGrounds_chooseOption(MoveContext context) { return HuntingGroundsOption.GainDuchy; } @Override public boolean ironmonger_shouldDiscard(MoveContext context, Card card) { return this.shouldDiscard(card); } @Override public Card junkDealer_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } Card card = pickOutCard(context.getPlayer().getHand(), getTrashCards()); if (card != null) return card; return context.getPlayer().getHand().get(0); } @Override public boolean marketSquare_shouldDiscard(MoveContext context) { return true; } @Override public Card mystic_cardGuess(MoveContext context, ArrayList<Card> cardList) { return Cards.silver; } @Override public boolean scavenger_shouldDiscardDeck(MoveContext context) { return true; } @Override public Card scavenger_cardToPutBackOnDeck(MoveContext context) { return Util.randomCard(discard); } @Override public Card[] storeroom_cardsToDiscardForCards(MoveContext context) { ArrayList<Card> cards = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if ((!(card instanceof ActionCard) && !(card instanceof TreasureCard)) || card.equals(Cards.cellar) || card.equals(Cards.storeroom) || card.equals(Cards.copper)) { cards.add(card); } } if (context.getActionsLeft() == 0) { for (Card c : context.getPlayer().getHand()) { if ((c instanceof ActionCard)) { cards.add(c); } } } return cards.toArray(new Card[0]); } @Override public Card[] storeroom_cardsToDiscardForCoins(MoveContext context) { ArrayList<Card> cards = new ArrayList<Card>(); for (Card card : context.getPlayer().getHand()) { if ((!(card instanceof ActionCard) && !(card instanceof TreasureCard)) || card.equals(Cards.cellar) || card.equals(Cards.storeroom) || card.equals(Cards.copper)) { cards.add(card); } } if (context.getActionsLeft() == 0) { for (Card c : context.getPlayer().getHand()) { if ((c instanceof ActionCard)) { cards.add(c); } } } return cards.toArray(new Card[0]); } @Override public ActionCard procession_cardToPlay(MoveContext context) { return controlPlayer.kingsCourt_cardToPlay(context); } @Override public Card procession_cardToGain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card rebuild_cardToPick(MoveContext context) { if (context.game.isColonyInGame()) return Cards.colony; else return Cards.province; } @Override public Card rebuild_cardToGain(MoveContext context, int maxCost, boolean costPotion) { ArrayList<VictoryCard> cards = new ArrayList<VictoryCard>(); for (Card c: context.getVictoryCardsInGame()) { if (c.getCost(context) <= maxCost && !game.isPileEmpty(c)) { cards.add((VictoryCard) c); } } return (cards.size() == 0) ? null : this.getBestVictoryCard(context, cards.toArray(new VictoryCard[0])); } @Override public Card rogue_cardToGain(MoveContext context) { ArrayList<Card> options = new ArrayList<Card>(); for (Card c : game.trashPile) { if (c.getCost(context) >= 3 && c.getCost(context) <= 6) { options.add(c); } } return Util.randomCard(options); } @Override public Card rogue_cardToTrash(MoveContext context, ArrayList<Card> canTrash) { return this.lowestCards(context, canTrash, 1, false)[0]; } @Override public TreasureCard counterfeit_cardToPlay(MoveContext context) { return null; } @Override public Card pillage_opponentCardToDiscard(MoveContext context, ArrayList<Card> handCards) { Card cardToDiscard = null; ArrayList<Card> goodCards = new ArrayList<Card>(); for (Card c : handCards) if (c instanceof TreasureCard || c instanceof ActionCard) goodCards.add(c); if (goodCards.size() > 0) { cardToDiscard = Util.getMostExpensiveCard(goodCards.toArray(new Card[0])); } if (cardToDiscard == null) { cardToDiscard = Util.randomCard(handCards); } return cardToDiscard; } @Override public boolean hovel_shouldTrash(MoveContext context) { return true; } @Override public boolean walledVillage_backOnDeck(MoveContext context) { return true; } @Override public GovernorOption governor_chooseOption(MoveContext context) { return GovernorOption.AddCards; } @Override public Card governor_cardToTrash(MoveContext context) { return controlPlayer.upgrade_cardToTrash(context); } @Override public Card governor_cardToObtain(MoveContext context, int exactCost, boolean potion) { return controlPlayer.upgrade_cardToObtain(context, exactCost, potion); } @Override public Card envoy_cardToDiscard(MoveContext context, Card[] cards) { // build list of "good" cards CardList cl = new CardList(context.getPlayer(), context.getPlayer().getPlayerName()); for(Card card : cards) { if (!isOnlyVictory(card) && !isCurse(card) && !isTrashCard(card)) { cl.add(card); } } // not enough to choose from if (cl.size() == 0) { return cards[0]; } else if (cl.size() == 1) { return cl.get(0); } cards = cl.sort(new Util.CardCostComparatorDesc()); // pick out mean cards and big treasure if (cl.contains(Cards.saboteur)) { return cl.get(Cards.saboteur); } else if (cl.contains(Cards.platinum)) { return cl.get(Cards.platinum); } else if (game.pileSize(Cards.curse) > 0 && !this.hand.contains(Cards.moat) && !this.hand.contains(Cards.watchTower) && (cl.contains(Cards.witch) || cl.contains(Cards.seaHag) || cl.contains(Cards.torturer))) { if (cl.contains(Cards.witch)) return cl.get(Cards.witch); if (cl.contains(Cards.seaHag)) return cl.get(Cards.seaHag); if (cl.contains(Cards.torturer)) return cl.get(Cards.torturer); } if (!this.hand.contains(Cards.moat)) { for (Card card : cl) { if (isAttackCard(card)) { return card; } } } if (cl.contains(Cards.gold)) { return cl.get(Cards.gold); } Card[] left = cl.sort(new Util.CardCostComparatorDesc()); return left[0]; } @Override public boolean survivors_shouldDiscardTopCards(MoveContext context, Card[] array) { // Discard them if there is more than 1 victory card int victoryCount = 0; for (Card card : array) { if (shouldDiscard(card)) { victoryCount++; } } return (victoryCount > 1); } @Override public Card[] survivors_cardOrder(MoveContext context, Card[] array) { return array; } @Override public boolean cultist_shouldPlayNext(MoveContext context) { return true; } @Override public Card[] urchin_attack_cardsToKeep(MoveContext context) { // Using the Militia discard logic for now... ArrayList<Card> keepers = new ArrayList<Card>(); ArrayList<Card> discards = new ArrayList<Card>(); // Just add in the non-victory cards... for (Card card : context.attackedPlayer.getHand()) { if (!shouldDiscard(card)) { keepers.add(card); } else { discards.add(card); } } while (keepers.size() < 4) { keepers.add(discards.remove(0)); } // Still more than 4? Remove all but one action... while (keepers.size() > 4) { int bestAction = -1; boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i) instanceof ActionCard) { if (bestAction == -1) { bestAction = i; } else { if(keepers.get(i).getCost(context) > keepers.get(bestAction).getCost(context)) { keepers.remove(bestAction); bestAction = i; } else { keepers.remove(i); } removed = true; break; } } } if (!removed) { break; } } // Still more than 4? Start removing copper... while (keepers.size() > 4) { boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i).equals(Cards.copper)) { keepers.remove(i); removed = true; break; } } if (!removed) { break; } } // Still more than 4? Start removing silver... while (keepers.size() > 4) { boolean removed = false; for (int i = 0; i < keepers.size(); i++) { if (keepers.get(i).equals(Cards.silver)) { keepers.remove(i); removed = true; break; } } if (!removed) { break; } } while (keepers.size() > 4) { keepers.remove(0); } return keepers.toArray(new Card[0]); } @Override public boolean urchin_shouldTrashForMercenary(MoveContext context) { return true; } @Override public Card[] mercenary_cardsToTrash(MoveContext context) { return pickOutCards(context.getPlayer().getHand(), 2, getTrashCards()); } @Override public Card[] mercenary_attack_cardsToKeep(MoveContext context) { return controlPlayer.militia_attack_cardsToKeep(context); } @Override public Card hermit_cardToTrash(MoveContext context, ArrayList<Card> cardList, int nonTreasureCountInDiscard) { return lowestCards(context, cardList, 1, false)[0]; } @Override public Card hermit_cardToGain(MoveContext context) { return bestCardInPlay(context, 3, false, false); } @Override public boolean madman_shouldReturnToPile(MoveContext context) { return true; } @Override public Card[] dameAnna_cardsToTrash(MoveContext context) { return pickOutCards(context.getPlayer().getHand(), 2, getTrashCards()); } @Override public Card knight_cardToTrash(MoveContext context, ArrayList<Card> canTrash) { return this.lowestCards(context, canTrash, 1, false)[0]; } @Override public Card[] sirMichael_attack_cardsToKeep(MoveContext context) { return controlPlayer.militia_attack_cardsToKeep(context); } @Override public Card dameNatalie_cardToObtain(MoveContext context) { return bestCardInPlay(context, 3); } @Override public ActionCard bandOfMisfits_actionCardToImpersonate(MoveContext context) { //TODO: better logic return (ActionCardImpl) bestCardInPlay(context, 4, false, true, true, false); } @Override public TreasureCard taxman_treasureToTrash(MoveContext context) { ArrayList<TreasureCard> handCards = context.getPlayer().getTreasuresInHand(); Collections.sort(handCards, new CardValueComparator()); HashSet<Integer> treasureCardValues = new HashSet<Integer>(); for (Card card : context.getTreasureCardsInGame()) { if (context.getCardsLeftInPile(card) > 0) treasureCardValues.add(card.getCost(context)); } for (int i = 0; i < handCards.size(); i++) { TreasureCard card = handCards.get(i); if (treasureCardValues.contains(card.getCost(context) + 3)) return card; } if (handCards.size() > 0) return handCards.get(0); return null; } @Override public TreasureCard taxman_treasureToObtain(MoveContext context, int cost) { TreasureCard newCard = null; int newCost = -1; for (Card card : context.getTreasureCardsInGame()) { if (context.getCardsLeftInPile(card) > 0 && card.getCost(context) <= cost && card.getCost(context) >= newCost) { newCard = (TreasureCard) card; newCost = card.getCost(context); } } return newCard; } @Override public TreasureCard plaza_treasureToDiscard(MoveContext context) { for (Card card : context.getPlayer().getHand()) { for(Card trash : getTrashCards()) { if(trash.equals(card) && (card instanceof TreasureCard)) { return (TreasureCard) card; } } } if (Game.rand.nextBoolean() && context.getPlayer().getHand().contains(Cards.silver)) { return (TreasureCard) context.getPlayer().fromHand(Cards.silver); } return null; } @Override public int numGuildsCoinTokensToSpend(MoveContext context) { if (getGuildsCoinTokenCount() > 2) { return getGuildsCoinTokenCount(); } else { return 0; } } @Override public Card butcher_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } if (context.getPlayer().getHand().size() == 1) { return context.getPlayer().getHand().get(0); } for (Card c : context.getPlayer().getHand()) { if(c.equals(Cards.curse)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(c instanceof VictoryCard) { continue; } } return null; } @Override public Card butcher_cardToObtain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card advisor_cardToDiscard(MoveContext context, Card[] cards) { return envoy_cardToDiscard(context, cards); } @Override public Card journeyman_cardToPick(MoveContext context) { // TODO: Implement return Cards.estate; } @Override public int amountToOverpay(MoveContext context, int cardCost) { // TODO: Implement return 0; } @Override public int overpayByPotions(MoveContext context, int availablePotions) { // TODO: Implement return 0; } @Override public Card stonemason_cardToTrash(MoveContext context) { if (context.getPlayer().getHand().size() == 0) { return null; } if (context.getPlayer().getHand().size() == 1) { return context.getPlayer().getHand().get(0); } for (Card c : context.getPlayer().getHand()) { if(c.equals(Cards.curse)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(isTrashCard(c)) { return c; } } for (Card c : context.getPlayer().getHand()) { if(c instanceof VictoryCard) { continue; } } return null; } @Override public Card stonemason_cardToGain(MoveContext context, int maxCost, boolean potion) { return bestCardInPlay(context, maxCost, false, potion); } @Override public Card stonemason_cardToGainOverpay(MoveContext context, int overpayAmount, boolean potion) { return bestCardInPlay(context, overpayAmount, true, potion); } @Override public Card doctor_cardToPick(MoveContext context) { // TODO: Implement return Cards.estate; } @Override public ArrayList<Card> doctor_cardsForDeck(MoveContext context, ArrayList<Card> cards) { return cards; } @Override public DoctorOverpayOption doctor_chooseOption(MoveContext context, Card card) { DoctorOverpayOption doo = DoctorOverpayOption.DiscardIt; for (Card trashCard : getTrashCards()) { if (trashCard.equals(card)) { doo = DoctorOverpayOption.TrashIt; } } if (card.equals(Cards.gold) || card.equals(Cards.platinum) || card.equals(Cards.silver) || (card instanceof ActionCard && context.actions > 0)) { doo = DoctorOverpayOption.PutItBack; } return doo; } public Card herald_cardTopDeck(MoveContext context, Card[] cardList) { Card cardToReturn = null; if (cardList.length > 0) { cardToReturn = cardList[0]; } for (Card c : cardList) { if (!shouldDiscard(c) && !c.equals(Cards.copper)) { cardToReturn = c; break; } } return cardToReturn; } }