package com.vdom.players; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Random; import com.vdom.api.ActionCard; import com.vdom.api.Card; import com.vdom.api.CardCostComparator; import com.vdom.api.GameEvent; import com.vdom.api.TreasureCard; import com.vdom.api.VictoryCard; import com.vdom.api.CurseCard; import com.vdom.core.AbstractCardPile; import com.vdom.core.BasePlayer; import com.vdom.core.Cards; import com.vdom.core.Game; import com.vdom.core.MoveContext; import com.vdom.core.Player; import com.vdom.core.Util; /** * @author buralien * */ public class VDomPlayerPatrick extends BasePlayer { Random rand = new Random(System.currentTimeMillis()); private enum DiscardOption { Destructive, SemiDestructive, NonDestructive } private enum StrategyOption { Nothing, NoAction, SingleAction, DoubleAction, MultiAction, Mirror, Minion } private class Opponent { public Opponent(int id) { actionCards = new ArrayList<Card>(); VP = -1000; isAttacking = false; playerID = id; } private ArrayList<Card> actionCards; private int VP; private boolean isAttacking; private int playerID; public int getVP() { return this.VP + Game.players[this.playerID].getVictoryTokens(); } public boolean getIsAttacking() { return this.isAttacking; } public ArrayList<Card> getActionCards() { return this.actionCards; } public void setVP(int vP) { this.VP = vP; } public void addVP(int vP) { this.VP += vP; } public void setAttacking(boolean isAttacking) { this.isAttacking = isAttacking; } public void putActionCard(Card card) { this.actionCards.add(card); } @Override public String toString() { return "Opponent [actionCards=" + actionCards + ", VP=" + VP + ", isAttacking=" + isAttacking + "]"; } } private class OpponentList extends HashMap<Integer,Opponent> { private static final long serialVersionUID = -9007482931936952794L; private HashMap<Integer,Opponent> opponents; public OpponentList() { this.opponents = new HashMap<Integer,Opponent>(); } @Override public Opponent get(Object key) { return this.opponents.get(key); } public int maxVP() { int ret = -1000; for (Opponent o : this.opponents.values()) { if (o.getVP() > ret) { ret = o.getVP(); } } if (ret > -1000) { return ret; } else { return -1; } } public ArrayList<Card> getActionCards() { ArrayList<Card> ret = new ArrayList<Card>(); for (Opponent o : this.opponents.values()) { for (Card c : o.getActionCards()) { ret.add(c); } } return ret; } public boolean getIsAttacking() { boolean ret = false; for (Opponent o : this.opponents.values()) { if (o.getIsAttacking()) { ret = true; } } return ret; } @Override public String toString() { return "OpponentList [opponents=" + opponents + "]"; } @Override public void clear() { this.opponents.clear(); } @Override public boolean isEmpty() { return this.opponents.isEmpty(); } @Override public boolean containsKey(Object key) { return this.opponents.containsKey(key); } @Override public Opponent put(Integer key, Opponent value) { return this.opponents.put(key, value); } @Override public int size() { return this.opponents.size(); } } private OpponentList opponents = new OpponentList(); private boolean redefineStrategy = false; private static ArrayList<Card> specialCards = new ArrayList<Card>(); private static ArrayList<Card> specialTreasureCards = new ArrayList<Card>(); private static ArrayList<Card> specialVictoryCards = new ArrayList<Card>(); private static ArrayList<Card> specialActionCards = new ArrayList<Card>(); static { // specialCards specialTreasureCards.add(Cards.foolsGold); specialTreasureCards.add(Cards.loan); specialTreasureCards.add(Cards.hoard); specialTreasureCards.add(Cards.royalSeal); specialTreasureCards.add(Cards.venture); specialTreasureCards.add(Cards.bank); specialTreasureCards.add(Cards.contraband); specialTreasureCards.add(Cards.potion); specialVictoryCards.add(Cards.harem); specialVictoryCards.add(Cards.farmland); specialVictoryCards.add(Cards.feodum); //populate for (Card c : specialTreasureCards) { specialCards.add(c); } for (Card c : specialVictoryCards) { specialCards.add(c); } for (Card c : specialActionCards) { specialCards.add(c); } } private static ArrayList<Card> knownCards = new ArrayList<Card>(); private static ArrayList<Card> knownActionCards = new ArrayList<Card>(); private static ArrayList<Card> knownSingleActionCards = new ArrayList<Card>(); // just one in deck is enough (trashing, etc.) private static ArrayList<Card> knownDoubleActionCards = new ArrayList<Card>(); // two in deck is ok (mostly attacks and other good terminals) private static ArrayList<Card> knownMultiActionCards = new ArrayList<Card>(); // cantrips of which we can have any number without terminal colision private static ArrayList<Card> knownComboActionCards = new ArrayList<Card>(); // cantrips and other cards which don't work on their own but need other cards private static ArrayList<Card> knownDefenseCards = new ArrayList<Card>(); // can be bought as reaction to aggressive opponent, normally no private static ArrayList<Card> knownCursingCards = new ArrayList<Card>(); // cards that add curses to opponent's deck private static ArrayList<Card> knownTrashingCards = new ArrayList<Card>(); // cards that allow trashing of Curse by playing them from hand private static ArrayList<Card> knownTier3Cards = new ArrayList<Card>(); // cards that can be played without any additional implementation, but are not so good private static ArrayList<Card> knownPrizeCards = new ArrayList<Card>(); // prize cards that we know how to play private static ArrayList<Card> knownGood52Cards = new ArrayList<Card>(); // cards that play well with 5/2 start static { // knownActionCards knownSingleActionCards.add(Cards.smithy); knownSingleActionCards.add(Cards.councilRoom); knownSingleActionCards.add(Cards.woodcutter); knownSingleActionCards.add(Cards.moneyLender); knownSingleActionCards.add(Cards.chapel); knownSingleActionCards.add(Cards.nomadCamp); knownSingleActionCards.add(Cards.steward); knownSingleActionCards.add(Cards.bishop); knownSingleActionCards.add(Cards.library); knownSingleActionCards.add(Cards.haggler); knownSingleActionCards.add(Cards.monument); knownSingleActionCards.add(Cards.vault); knownSingleActionCards.add(Cards.merchantShip); knownSingleActionCards.add(Cards.jackOfAllTrades); knownSingleActionCards.add(Cards.bridge); knownSingleActionCards.add(Cards.harvest); knownSingleActionCards.add(Cards.tactician); knownSingleActionCards.add(Cards.tournament); knownSingleActionCards.add(Cards.nobleBrigand); knownSingleActionCards.add(Cards.tradeRoute); knownDoubleActionCards.add(Cards.wharf); knownDoubleActionCards.add(Cards.jackOfAllTrades); knownDoubleActionCards.add(Cards.ghostShip); knownDoubleActionCards.add(Cards.courtyard); knownDoubleActionCards.add(Cards.witch); knownDoubleActionCards.add(Cards.mountebank); knownDoubleActionCards.add(Cards.seaHag); knownDoubleActionCards.add(Cards.militia); knownDoubleActionCards.add(Cards.rabble); knownDoubleActionCards.add(Cards.margrave); knownDoubleActionCards.add(Cards.familiar); knownDoubleActionCards.add(Cards.torturer); knownDoubleActionCards.add(Cards.ambassador); knownDoubleActionCards.add(Cards.saboteur); knownDoubleActionCards.add(Cards.minion); knownDoubleActionCards.add(Cards.masquerade); knownDoubleActionCards.add(Cards.rogue); knownDoubleActionCards.add(Cards.pillage); knownMultiActionCards.add(Cards.laboratory); knownMultiActionCards.add(Cards.market); knownMultiActionCards.add(Cards.bazaar); knownMultiActionCards.add(Cards.treasury); knownMultiActionCards.add(Cards.miningVillage); knownMultiActionCards.add(Cards.caravan); knownMultiActionCards.add(Cards.alchemist); knownMultiActionCards.add(Cards.scryingPool); knownComboActionCards.add(Cards.throneRoom); knownComboActionCards.add(Cards.kingsCourt); knownComboActionCards.add(Cards.huntingParty); knownComboActionCards.add(Cards.peddler); knownComboActionCards.add(Cards.city); knownComboActionCards.add(Cards.grandMarket); knownComboActionCards.add(Cards.village); knownComboActionCards.add(Cards.workersVillage); knownComboActionCards.add(Cards.fishingVillage); knownComboActionCards.add(Cards.farmingVillage); knownComboActionCards.add(Cards.borderVillage); knownComboActionCards.add(Cards.shantyTown); knownComboActionCards.add(Cards.highway); knownComboActionCards.add(Cards.festival); knownComboActionCards.add(Cards.sage); knownComboActionCards.add(Cards.fortress); knownComboActionCards.add(Cards.banditCamp); knownComboActionCards.add(Cards.marketSquare); knownComboActionCards.add(Cards.wanderingMinstrel); knownTier3Cards.add(Cards.bureaucrat); knownTier3Cards.add(Cards.adventurer); knownTier3Cards.add(Cards.conspirator); knownTier3Cards.add(Cards.coppersmith); knownTier3Cards.add(Cards.scout); knownTier3Cards.add(Cards.tribute); knownTier3Cards.add(Cards.lighthouse); knownTier3Cards.add(Cards.cutpurse); knownTier3Cards.add(Cards.outpost); knownTier3Cards.add(Cards.apothecary); knownTier3Cards.add(Cards.countingHouse); knownTier3Cards.add(Cards.fortuneTeller); knownTier3Cards.add(Cards.menagerie); knownTier3Cards.add(Cards.crossroads); knownTier3Cards.add(Cards.ironworks); knownTier3Cards.add(Cards.duchess); knownTier3Cards.add(Cards.watchTower); knownTier3Cards.add(Cards.lookout); knownTier3Cards.add(Cards.rebuild); // knownPrizeCards should be sorted according to importance knownPrizeCards.add(Cards.followers); knownPrizeCards.add(Cards.diadem); knownPrizeCards.add(Cards.princess); knownPrizeCards.add(Cards.bagOfGold); // implemented separately //knownMultiActionCards.add(Cards.golem); //populate for (Card c : knownSingleActionCards) { knownActionCards.add(c); } for (Card c : knownDoubleActionCards) { knownActionCards.add(c); } for (Card c : knownMultiActionCards) { knownActionCards.add(c); } knownDefenseCards.add(Cards.watchTower); knownDefenseCards.add(Cards.moat); knownCursingCards.add(Cards.witch); knownCursingCards.add(Cards.seaHag); knownCursingCards.add(Cards.youngWitch); knownCursingCards.add(Cards.mountebank); knownCursingCards.add(Cards.torturer); knownCursingCards.add(Cards.jester); knownCursingCards.add(Cards.familiar); //knownCursingCards.add(Cards.followers); knownTrashingCards.add(Cards.chapel); knownTrashingCards.add(Cards.remodel); knownTrashingCards.add(Cards.masquerade); knownTrashingCards.add(Cards.steward); knownTrashingCards.add(Cards.tradingPost); knownTrashingCards.add(Cards.upgrade); knownTrashingCards.add(Cards.salvager); knownTrashingCards.add(Cards.apprentice); knownTrashingCards.add(Cards.transmute); knownTrashingCards.add(Cards.tradeRoute); knownTrashingCards.add(Cards.bishop); knownTrashingCards.add(Cards.expand); knownTrashingCards.add(Cards.forge); knownTrashingCards.add(Cards.remake); knownTrashingCards.add(Cards.develop); knownTrashingCards.add(Cards.jackOfAllTrades); knownTrashingCards.add(Cards.trader); knownTrashingCards.add(Cards.ambassador); // it is not actually trashing cards, but uses similar mechanism to get rid of them knownTrashingCards.add(Cards.altar); knownTrashingCards.add(Cards.count); knownTrashingCards.add(Cards.counterfeit); knownTrashingCards.add(Cards.forager); knownTrashingCards.add(Cards.graverobber); knownTrashingCards.add(Cards.junkDealer); knownTrashingCards.add(Cards.procession); knownTrashingCards.add(Cards.rats); knownTrashingCards.add(Cards.rebuild); knownGood52Cards.add(Cards.wharf); knownGood52Cards.add(Cards.jackOfAllTrades); knownGood52Cards.add(Cards.ghostShip); for (Card c : knownActionCards) { knownCards.add(c); } for (Card c : specialTreasureCards) { knownCards.add(c); } for (Card c : specialVictoryCards) { knownCards.add(c); } } private boolean debug = Game.debug; private StrategyOption strategy = StrategyOption.Nothing; private ActionCard strategyCard = null; private ArrayList<ActionCard> strategyPossibleCards = new ArrayList<ActionCard>(); private ActionCard strategyMultiCardTerminal = null; //private ComboCards combo; /** * @param s text to log via System.out (if debug enabled) */ private void log(String s) { if (debug) { System.out.println("<AI> " + s); } } private boolean isCantrip(ActionCard card) { if (card == null) { return false; } if (card.equals(Cards.scryingPool)) { return true; } if ((card.getAddActions() > 0) && (card.getAddCards() > 0)) { return true; } return false; } @Override public Card doAction(MoveContext context) { return this.advisorPlayAction(this.hand.toArrayListClone()); } private int getCardsToEndGame() { int prov_col = 1000; int min1 = 1000; int min2 = 1000; int min3 = 1000; for (AbstractCardPile pile : game.piles.values()) { if ((pile.card().equals(Cards.province)) || (pile.card().equals(Cards.colony))) { if (pile.getCount() < prov_col) { prov_col = pile.getCount(); } } else { if (pile.getCount() < min1) { min1 = pile.getCount(); } else if (pile.getCount() < min2) { min2 = pile.getCount(); } else if (pile.getCount() < min3) { min3 = pile.getCount(); } } } return Math.min(prov_col, (min1 + min2 + min3)); } @Override public Card doBuy(MoveContext context) { int gold = context.getCoinAvailableForBuy(); return this.advisorGeneral(context, gold, false, false); } @Override public String getPlayerName() { return getPlayerName(game.maskPlayerNames); } @Override public String getPlayerName(boolean maskName) { return maskName ? "Player " + (playerNumber + 1) : "Patrick"; } public boolean isAi() { return true; } public boolean foolsGold_shouldTrash(MoveContext context) { this.log("foolsGold_shouldTrash"); ArrayList<Card> temphand = this.hand.toArrayListClone(); Card keep = advisorGeneral(context, this.getCurrencyTotal(temphand), false, true); temphand.remove(Cards.foolsGold); Card trash = advisorGeneral(context, this.getCurrencyTotal(temphand), false, true); if (keep.equals(trash)) { // card to be obtained with -1 Fools Gold is the same return true; } return (inHandCount(Cards.foolsGold) <= 2); } public Card workshop_cardToObtain(MoveContext context) { this.log("workshop_cardToObtain"); return this.advisorGeneral(context, 4, false, true); } /** * @param context context * @return number of players in the game */ private int numPlayers() { return game.getPlayersInTurnOrder().length; } /** * @param context context * @return how many turns will each player have before the end of the game */ private int guessTurnsToEnd() { return Math.round(getCardsToEndGame() / numPlayers()) + 1; } /** * @param context context * @return how many turns before the deck will be reshuffled */ private int guessTurnsToReshuffle() { return Math.round(getDeckSize(deck.toArrayList()) / 5); } /** * @param context context * @return how many times the deck will be reshuffled before the end of the game */ private int guessReshufflesToEnd(MoveContext context) { int t = guessTurnsToEnd() - guessTurnsToReshuffle(); return (t / (getDeckSize(context) / 5)) + 1; } /** * This function calculates the total amount of coin available to a player in his deck * The resulting amount is somehow approximated for cards like Venture, Bank, Fool's Gold * * @param context context * @return approximate value of coin in whole deck */ public int getCurrencyTotal (MoveContext context) { return guessCurrencyTotal(this.getAllCards()); } /** * This function calculates the amount of coins available in the list. * It will return the amount as if all treasure cards have been played. * For cards with variable value (Venture), it only calculates the minimum guaranteed value. * Usually used to evaluate treasure in hand. * * @param list list of cards * @return value in coin */ private int getCurrencyTotal(ArrayList<Card> list) { int money = 0; for (Card card : list) { if (card instanceof TreasureCard) { money += ((TreasureCard) card).getValue(); } if (card.equals(Cards.venture)) { money += 1; } if (card.equals(Cards.bank)) { // money += 1 + this.getMoneyPerCard(list); money += this.getTotalTreasureCards(list) - 1; } if (card instanceof ActionCard) { money += ((ActionCard) card).getAddGold(); } } if (Util.getCardCount(list, Cards.foolsGold) > 1) { money += (Util.getCardCount(list, Cards.foolsGold) - 1) * 3; } return money; } /** * This function calculates the total amount of coin available to a player in his deck * The resulting amount is somehow approximated for cards like Venture, Bank, Fool's Gold * This function should be used to evaluate treasure in deck (or other similar lists of cards) * * @param list * @return */ private int guessCurrencyTotal(ArrayList<Card> list) { int money = 0; for (Card card : list) { if (card instanceof TreasureCard) { money += ((TreasureCard) card).getValue(); } if (card instanceof ActionCard) { ActionCard ac = (ActionCard)card; money += ac.getAddGold(); //money += ac.getAddCards(); } if (card.equals(Cards.venture)) { //TODO maybe there is some way of incorporating the avg money per card without creating an infinite loop? money += 1; } if (card.equals(Cards.philosophersStone)) { money += (list.size()/20); } if (card.equals(Cards.bank)) { //TODO maybe there is some way of incorporating the avg money per card without creating an infinite loop? money += 2; } if (card.equals(Cards.foolsGold)) { // we add +1 coin value to every Fools Gold beyond the first money += (Util.getCardCount(list, Cards.foolsGold) - 1); } } if (this.strategy == StrategyOption.Minion) { money += Math.round(1.6*Util.getCardCount(list, Cards.minion)); } return money; } /** * Function calculates the total amount of VPs for all the cards in the list. * Note that for cards with variable amount of VPs (Vineyard, Silk Road), the amount * of VP they provide is calculated based on the list only, not based on whole deck. * * Use getVPTotalValue(MoveContext context) for calculation in whole deck * * @param list list of cards * @return amount of VPs provided by the cards */ private int getVPTotalValue (ArrayList<Card> list) { int vps = this.getVictoryTokens(); for (Card card : list) { if (card instanceof VictoryCard) { vps += ((VictoryCard) card).getVictoryPoints(); } if (this.isCurse(card)) { vps += ((CurseCard) card).getVictoryPoints(); } if (card.equals(Cards.duke)) { vps += Util.getCardCount(list, Cards.duchy); } if (card.equals(Cards.gardens)) { vps += list.size() / 10; } if (card.equals(Cards.vineyard)) { vps += Math.round(this.getCardCount(ActionCard.class, list) / 3); } if (card.equals(Cards.fairgrounds)) { vps += Math.round(this.getCardNameCount(list) / 5); } if (card.equals(Cards.silkRoad)) { vps += Math.round(this.getCardCount(VictoryCard.class, list) / 4); } if (card.equals(Cards.feodum)) { vps += Math.round(Util.getCardCount(list, Cards.silver) / 3); } //consider also VP token making cards? } return vps; } /** * @param context context * @return number of treasure cards player owns */ private int getTotalTreasureCards (ArrayList<Card> list) { int moneycards = 0; for (Card card : list) { if (card instanceof TreasureCard) { moneycards++; } } if (this.strategy == StrategyOption.Minion) { moneycards += Math.round(1.6*Util.getCardCount(list, Cards.minion)); } return moneycards; } private double getMoneyPerCard(ArrayList<Card> list) { if (!list.isEmpty()) { return ( (double) (this.guessCurrencyTotal(list)) / (double) getDeckSize(list)); } return (double) 0.0; } private double getMoneyPerCard(ArrayList<Card> list, int plustreasure, int pluscards) { return (double) (guessCurrencyTotal(list) + plustreasure) / (double) (getDeckSize(list) + pluscards); } // private Card getCardToDiscard(DiscardOption destructive) { // return getCardToDiscard(this.hand.toArrayListClone(), destructive); // } /** * @param list list of cards * @param destructive whether this is a forced discard, or optional * @return best candidate for discarding */ private Card getCardToDiscard(ArrayList<Card> list, DiscardOption destructive) { // Tunnel if (list.contains(Cards.tunnel)) { return list.get(list.indexOf(Cards.tunnel)); } // Curse if (list.contains(Cards.curse)) { int trashit = 0; for (Card c : list) { trashit += (knownTrashingCards.contains(c) ? 1 : 0); } if (trashit < Util.getCardCount(list, Cards.curse)) { return list.get(list.indexOf(Cards.curse)); } } // Victory cards with no other function for (Card card : list) { if (isOnlyVictory(card)) { return card; } } if ((list.contains(Cards.potion)) && (!list.contains(Cards.alchemist))) { return list.get(list.indexOf(Cards.potion)); } switch (this.strategy) { case NoAction: for (Card card : list) { if (card instanceof ActionCard) { return card; } } break; case SingleAction: case DoubleAction: case MultiAction: case Mirror: for (Card card : list) { if (!this.strategyPossibleCards.contains(card)) { return card; } } break; default: break; } // This is as far as we go with useless cards if (destructive == DiscardOption.NonDestructive) { return null; } // Overgrown Estate if (list.contains(Cards.overgrownEstate)) { return list.get(list.indexOf(Cards.overgrownEstate)); } // Hovel if (list.contains(Cards.hovel)) { return list.get(list.indexOf(Cards.hovel)); } // Necropolis if playing only few actions if (this.strategy != StrategyOption.MultiAction) { if (list.contains(Cards.necropolis)) { return list.get(list.indexOf(Cards.necropolis)); } } // Copper if (list.contains(Cards.copper)) { return list.get(list.indexOf(Cards.copper)); } // This is as far as we go with semi-useless cards if (destructive == DiscardOption.SemiDestructive) { return null; } // Action cards for (Card card : list) { if (card instanceof ActionCard) { return card; } } if (list.contains(Cards.illGottenGains)) { return list.get(list.indexOf(Cards.illGottenGains)); } if (list.contains(Cards.loan)) { return list.get(list.indexOf(Cards.loan)); } // Fool's Gold if only one in hand if (Util.getCardCount(list, Cards.foolsGold) == 1) { return list.get(list.indexOf(Cards.foolsGold)); } // 2 coin treasures if (list.contains(Cards.silver)) { return list.get(list.indexOf(Cards.silver)); } if (list.contains(Cards.harem)) { return list.get(list.indexOf(Cards.harem)); } if (list.contains(Cards.hoard)) { return list.get(list.indexOf(Cards.hoard)); } if (list.contains(Cards.royalSeal)) { return list.get(list.indexOf(Cards.royalSeal)); } if (list.contains(Cards.venture)) { return list.get(list.indexOf(Cards.venture)); } if (list.contains(Cards.contraband)) { return list.get(list.indexOf(Cards.contraband)); } if (!list.isEmpty()) { return list.get(0); } return null; } /** * @param hand list of cards in players hand * @param number how many cards to discard * @param destructive discard is mandatory? * @return list of cards that can be discarded */ private ArrayList<Card> getCardsToDiscard(ArrayList<Card> list, int number, DiscardOption destructive) { ArrayList<Card> ret = new ArrayList<Card>(); int discarded = 0; Card dcard = null; while ((!list.isEmpty()) && (discarded < number)) { dcard = this.getCardToDiscard(list, destructive); if (dcard != null) { ret.add(discarded, dcard); list.remove(ret.get(discarded)); } else { break; } discarded++; } if (ret.size() == number) { Collections.sort(ret, new CardCostComparator()); return ret; } return new ArrayList<Card>(); } private Card getCardToTrash(ArrayList<Card> list, DiscardOption destructive) { return getCardToTrash(list, new ArrayList<Card>(), destructive); } /** * @param context context * @param list cards in hand * @param destructive based on this, return only "useless" cards, or null if no such choice * @return best candidate for trashing */ private Card getCardToTrash(ArrayList<Card> list, ArrayList<Card> deck, DiscardOption destructive) { // Curse, Overgrown Estate and Hovel should be trashed at any time if (list.contains(Cards.curse)) { return list.get(list.indexOf(Cards.curse)); } if (list.contains(Cards.overgrownEstate)) { return list.get(list.indexOf(Cards.overgrownEstate)); } if (list.contains(Cards.hovel)) { return list.get(list.indexOf(Cards.hovel)); } // Potions should be trashed if they are not useful anymore if (list.contains(Cards.potion)) { switch (strategy) { case NoAction: return list.get(list.indexOf(Cards.potion)); case SingleAction: if (Util.getCardCount(deck, strategyCard) > 0) { return list.get(list.indexOf(Cards.potion)); } break; case DoubleAction: if (Util.getCardCount(deck, strategyCard) > 1) { return list.get(list.indexOf(Cards.potion)); } break; case MultiAction: if ((game.pileSize(Cards.alchemist) < 1) && (Util.getCardCount(deck, Cards.alchemist) > 0)) { return list.get(list.indexOf(Cards.potion)); } break; default: break; } } // Estate can be trashed when we have less then 2 province/colony if (list.contains(Cards.estate)) { if ((game.pileSize(Cards.province) > 3) || (game.pileSize(Cards.colony) > 3)) { return list.get(list.indexOf(Cards.estate)); } } // Copper can be trashed if we have high money/card ratio if ((this.getMoneyPerCard(deck) >= 1) || (this.strategyCard != null && this.strategyCard.equals(Cards.chapel))) { if (list.contains(Cards.copper)) { return list.get(list.indexOf(Cards.copper)); } } if (destructive == DiscardOption.NonDestructive) { return null; } if (list.contains(Cards.estate)) { return list.get(list.indexOf(Cards.estate)); } if (list.contains(Cards.copper)) { return list.get(list.indexOf(Cards.copper)); } if (destructive == DiscardOption.SemiDestructive) { return null; } Card ret = Cards.colony; for (Card c : list) { if (c.getCost(null) < ret.getCost(null)) { ret = list.get(list.indexOf(c)); } } return ((destructive == DiscardOption.Destructive) ? (list.isEmpty() ? null : ret) : null); } private boolean inDeck(MoveContext context, Card card) { return (inDeckCount(context, card) > 0); } private int inDeckCount(MoveContext context, Card card) { return Util.getCardCount(this.getAllCards(), card); } @Override public Card[] militia_attack_cardsToKeep(MoveContext context) { ArrayList<Card> cards2keep = this.hand.toArrayListClone(); Card card2discard = null; while (cards2keep.size() > 3) { card2discard = getCardToDiscard(cards2keep, DiscardOption.Destructive); cards2keep.remove(card2discard); } return cards2keep.toArray(new Card[3]); } @Override public Card saboteur_cardToObtain(MoveContext context, int maxCost, boolean potion) { this.log("saboteur_cardToObtain"); return advisorGeneral(context, maxCost, false, true); } @Override public Card[] torturer_attack_cardsToDiscard(MoveContext context) { //TODO test return this.getCardsToDiscard(this.hand.toArrayListClone(), 2, DiscardOption.Destructive).toArray(new Card[2]); } @Override public Card[] vault_cardsToDiscardForGold(MoveContext context) { //TODO test ArrayList<Card> temphand = this.hand.toArrayListClone(); ArrayList<Card> ret = new ArrayList<Card>(); Card card = null; while (ret.size() < 2) { card = this.getCardToDiscard(temphand, DiscardOption.SemiDestructive); if (card == null) { break; } temphand.remove(card); ret.add(card); } while (ret.size() < 2) { if (getMyAddActions() == 0) { for (Card acard : temphand) { if (acard instanceof ActionCard) { ret.add(acard); } } } } while (ret.size() < 2) { for (Card vCard : temphand) { if (vCard instanceof VictoryCard) { ret.add(vCard); } } } while (ret.size() < 2) { for (Card tCard : temphand) { if (!(tCard instanceof TreasureCard)) { ret.add(tCard); } } } while (ret.size() < 2) { for (Card tCard : temphand) { ret.add(tCard); } } this.log("vault: chosen " + ret); if (ret.size() > 0) { return ret.toArray(new Card[ret.size()]); } return null; } @Override public Card[] goons_attack_cardsToKeep(MoveContext context) { return militia_attack_cardsToKeep(context); } @Override public Card[] followers_attack_cardsToKeep(MoveContext context) { return militia_attack_cardsToKeep(context); } @Override public Card[] margrave_attack_cardsToKeep(MoveContext context) { return militia_attack_cardsToKeep(context); } @Override public Card[] ghostShip_attack_cardsToPutBackOnDeck(MoveContext context) { //TODO rewrite ArrayList<Card> cards = new ArrayList<Card>(); for (int i = 0; i < context.getPlayer().getHand().size() - 3; i++) { cards.add(context.getPlayer().getHand().get(i)); } return cards.toArray(new Card[cards.size()]); } @Override public boolean miningVillage_shouldTrashMiningVillage(MoveContext context) { this.log("miningVillage_shouldTrashMiningVillage: keep"); Card normal = advisorGeneral(context, getCurrencyTotal(hand.toArrayListClone()), false, true); this.log("miningVillage_shouldTrashMiningVillage: trash"); Card extra = advisorGeneral(context, getCurrencyTotal(hand.toArrayListClone()) + 2, false, true); //TODO should compare resulting decks if (normal.equals(extra)) { // card to be obtained with +2 coin is the same return false; } return true; } @Override public Card island_cardToSetAside(MoveContext context) { Card ret = null; ArrayList<Card> temphand = this.hand.toArrayListClone(); while (temphand.size() > 0) { ret = getCardToDiscard(temphand, DiscardOption.Destructive); temphand.remove(ret); if (this.isOnlyVictory(ret)) { return ret; } } return getCardToDiscard(temphand, DiscardOption.Destructive); } @Override public Card farmland_cardToObtain(MoveContext context, int exactCost, boolean potion) { //TODO test this.log("farmland_cardToObtain"); return this.advisorGeneral(context, exactCost, true, true); } @Override public Card farmland_cardToTrash(MoveContext context) { return getCardToTrash(DiscardOption.Destructive); } @Override public Card borderVillage_cardToObtain(MoveContext context) { this.log("borderVillage_cardToObtain"); return this.advisorGeneral(context, 5, false, true); } @Override public TorturerOption torturer_attack_chooseOption(MoveContext context) { if (game.pileSize(Cards.curse) <= 0) { return Player.TorturerOption.TakeCurse; } if(inHand(Cards.watchTower) || inHand(Cards.trader)) { return Player.TorturerOption.TakeCurse; } int discarded = 2; ArrayList<Card> temphand = this.hand.toArrayListClone(); ArrayList<Card> toDiscard = this.getCardsToDiscard(temphand, discarded, DiscardOption.NonDestructive); if (toDiscard.size() >= discarded) { return Player.TorturerOption.DiscardTwoCards; } for (Card c : toDiscard) { temphand.remove(c); discarded--; } this.log("torturer_attack_chooseOption: keep"); Card keep = advisorGeneral(context, this.getCurrencyTotal(temphand), false, false); // this.log("torturer_attack_chooseOption: discard"); // Card discard = advisorGeneral(context, this.getCurrencyTotal(temphand), false, false); // // if (keep != null && discard !=null && keep.equals(discard)) { // return Player.TorturerOption.DiscardTwoCards; // } ArrayList<Card> preffered = new ArrayList<Card>(); if (game.isPlatInGame()) { preffered.add(Cards.colony); preffered.add(Cards.platinum); } else { preffered.add(Cards.province); preffered.add(Cards.gold); } if (preffered.contains(keep)) { return Player.TorturerOption.TakeCurse; } return Player.TorturerOption.DiscardTwoCards; } /** * @param orig_set original list of cards * @param new_set new list of cards * @return quality difference between each list (positive if new is better) */ // public double compareCards(ArrayList<Card> orig_set, ArrayList<Card> new_set) { // int vps = this.getVPTotalValue(new_set) - this.getVPTotalValue(orig_set); // double mpc = this.getMoneyPerCard(new_set) - this.getMoneyPerCard(orig_set); // int coins = this.getCurrencyTotal(new_set) - this.getCurrencyTotal(orig_set); // this.log("vps: " + vps + "; coins: " + coins + "; mpc: " + mpc ); // // return (double) (((vps*0.5) * Math.abs(vps*0.5)) + ((coins*0.8) * Math.abs(vps*0.8))); // } public boolean shouldBuyPotion() { boolean ret = false; for (ActionCard c : this.strategyPossibleCards) { ret = ret | c.costPotion(); } return ret; } public boolean shouldDiscard(Card card) { ArrayList<Card> c = new ArrayList<Card>(); c.add(card); return (this.getCardToDiscard(c, DiscardOption.NonDestructive) != null); } public boolean shouldTrash(Card card) { ArrayList<Card> c = new ArrayList<Card>(); c.add(card); return (this.getCardToTrash(c, DiscardOption.NonDestructive) != null); } @Override public boolean loan_shouldTrashTreasure(MoveContext context, TreasureCard treasure) { if ((treasure.equals(Cards.copper)) || (treasure.equals(Cards.illGottenGains))) { if ((getMoneyPerCard(getAllCards(), -1, -1) > 0.7) && (getCurrencyTotal(context) > 6)) { return true; } } if (treasure.equals(Cards.loan)) { return true; } if ((this.getMoneyPerCard(this.getAllCards(), (0 - treasure.getValue()), -1) > (Math.pow(treasure.getValue(), 2) * 0.7)) && (getCurrencyTotal(context) > 6)) { return true; } return false; } /** * Function calculates the total amount of VPs for all the cards in the deck. * * @param context context * @return amount of VPs in the deck */ private int getVPTotalValue (MoveContext context) { return this.getVPTotalValue(this.getAllCards()); } /** * @param context context * @param gold coins available to buy * @param exact card must cost exactly the value * @param mandatory must return a card (not nothing) * @return best card to gain */ //@SuppressWarnings("unused") private Card advisorGeneral(MoveContext context, int gold, boolean exact, boolean mandatory) { if (this.shouldReEvaluateStrategy()) { advisorAction(); this.log("strategy options: " + this.strategyPossibleCards); this.redefineStrategy = false; } double maxmpc = -1; int maxvp = -1; TreasureCard maxMPC_card = null; VictoryCard maxVP_card = null; ActionCard action_card = null; ArrayList<Card> special_cards = new ArrayList<Card>(); ArrayList<Card> deck = this.getAllCards(); ArrayList<Card> potentialBuys = new ArrayList<Card>(); double mpc = this.getMoneyPerCard(deck); float cph = this.getCardsPerHand(context); int allowedTerminals = Math.max(1, Math.round((float)getDeckSize(context) / ((1 + cph) * 2))); switch (strategy) { case Minion: allowedTerminals = 1; case SingleAction: allowedTerminals = (int) Math.round(allowedTerminals / 1.5); break; case NoAction: allowedTerminals = 0; break; default: break; } this.log("allowedTerminals: " + getDeckSize(context) + " / " + ((1 + cph) * 2) + " = " + allowedTerminals); this.log("getStrategyCardsInDeck: " + getStrategyCardsInDeck(context, true)); this.log("getTerminalsInDeck: " + getTerminalsInDeck(context)); // here we check each card available for buy // the goal is to find the best VP card, best treasure and best action card for (AbstractCardPile pile : game.piles.values()) { Card card = pile.card(); if (!exact || card.getCost(context) == gold) { if (game.isValidBuy(context, card, gold)) { if ((card instanceof VictoryCard) && (!specialCards.contains(card))) { VictoryCard vc = (VictoryCard) card; int vp = vc.getVictoryPoints(); if (vc.equals(Cards.gardens)) { vp += Math.floor(deck.size() / 10); } if (vc.equals(Cards.duke)) { vp += Util.getCardCount(deck, Cards.duchy); } if (vc.equals(Cards.duchy)) { vp += Util.getCardCount(deck, Cards.duke); } if (vc.equals(Cards.silkRoad)) { vp += Math.floor(this.getCardCount(VictoryCard.class, deck) / 4); } if (vc.equals(Cards.feodum)) { vp += Math.floor(Util.getCardCount(this.getAllCards(), Cards.silver) / 3); } if (game.embargos.containsKey(vc.getName())) { vp -= (game.embargos.get(vc.getName()) + this.guessReshufflesToEnd(context)); } // don't end the game if losing and the last card will not secure a win // this is not really good with +buys, as it will prevent buying multiple cards to secure a win // TODO review, because with >1 buys, will buy the last card anyway if ((willEndGameGaining(card)) && (winningBy(context) < (0 - vp)) && (context.buys == 1)) { this.log("can't recommend " + card + ", would lose game by " + (winningBy(context) + (vp * (vp > 0 ? 1 : -1)))); continue; } if (vp >= maxvp) { maxvp = vp; maxVP_card = vc; } } // victory // don't end the game if losing if ((willEndGameGaining(card)) && (this.winningBy(context) < 0) && (context.buys == 1)) { this.log("can't recommend " + card + ", would lose game by " + Math.abs(winningBy(context)) + " VP"); continue; } if ((card instanceof TreasureCard) && (!specialCards.contains(card))) { TreasureCard tc = (TreasureCard) card; ArrayList<Card> tempdeck = new ArrayList<Card>(deck); tempdeck.add(tc); if (card.equals(Cards.cache)) { tempdeck.add(Cards.copper); tempdeck.add(Cards.copper); } if (game.embargos.containsKey(card.getName())) { for (int i = 0; i < game.embargos.get(card.getName()); i++) { tempdeck.add(Cards.curse); } } double tmpc = this.getMoneyPerCard(tempdeck); if (tmpc > maxmpc) { maxmpc = tmpc; maxMPC_card = tc; } } // treasure if ((card.equals(Cards.golem)) && (this.getCardCount(ActionCard.class, deck) > 1) && (this.strategy != StrategyOption.NoAction)) { this.log("action: Golem (have " + this.getCardCount(ActionCard.class, deck) + " actions)"); potentialBuys.add(Cards.golem); } // action cards if ((this.strategyPossibleCards.contains(card)) && ((game.pileSize(Cards.curse) > 3) || (!knownCursingCards.contains(card)))) { ActionCard ac = (ActionCard) card; // we can buy another piece of "single" card only when the deck is big enough switch (this.strategy) { case Minion: this.log("action: " + card + " (have " + (this.inDeckCount(context, card) + ")")); potentialBuys.add(card); break; case DoubleAction: if ((allowedTerminals < 2) && (Util.getCardCount(deck, Cards.gold) > 0)) { allowedTerminals = 2; } case SingleAction: if (this.strategyPossibleCards.contains(card)) { if ((getStrategyCardsInDeck(context, false).size() < allowedTerminals) || (isCantrip(ac))) { this.log("action: " + card + " (have " + (this.inDeckCount(context, card) + ")")); potentialBuys.add(card); potentialBuys.add(card); } } if ((knownMultiActionCards.contains(card)) && (this.rand.nextInt(4) == 1)) { //this.log("action: extra " + card); potentialBuys.add(card); } break; case MultiAction: // choose at every opportunity if (this.strategyPossibleCards.contains(card)) { this.log("action: evaluating another " + card); if ((!VDomPlayerPatrick.knownComboActionCards.contains(card)) || (this.rand.nextBoolean()) || (card.costPotion())) { ArrayList<Card> temp = new ArrayList<Card>(getAllCards()); temp.retainAll(knownComboActionCards); //this.log(card + (isCantrip(ac) ? " is " : " isn't ") + "cantrip"); if (temp.size()*2 <= this.getStrategyCardsInDeck(context, false).size()+(isCantrip(ac) ? 1 : 0)) { potentialBuys.add(card); } } } if ((knownSingleActionCards.contains(card) || knownDoubleActionCards.contains(card)) && (getTerminalsInDeck(context).size() < allowedTerminals)) { //this.log("action: terminal " + card); potentialBuys.add(card); } break; case Mirror: if (this.opponents.getActionCards().contains(card)) { potentialBuys.add(ac); //this.log("action: same " + card); } default: break; } } if (specialCards.contains(card)) { special_cards.add(card); } } } } this.log("VPs: " + this.winningBy(context)); this.log("best basic mpc: " + maxMPC_card); this.log("best vp: " + maxVP_card); this.log("specials: " + special_cards); this.log("potential actions: " + potentialBuys); int embargopiles = 0; while (potentialBuys.size() > 0) { action_card = (ActionCard) Util.randomCard(potentialBuys); potentialBuys.remove(action_card); if (game.embargos.containsKey(action_card.getName())) { this.log("action " + action_card + " is embargoed, skipping"); action_card = null; embargopiles++; } } this.log("picked action: " + action_card); if ((action_card == null) && (embargopiles > 0)) { this.redefineStrategy = true; } if (!special_cards.isEmpty()) { Card scard = Cards.loan; if (special_cards.contains(scard)) { // we have no loan in deck and significantly more copper then other trasures if ((this.inDeck(context, scard)) || (this.inDeckCount(context, Cards.copper)*2 <= this.getCurrencyTotal(context))) { // buying only when there are a lot of coppers and only 1 piece this.log("Loan not good, " + this.inDeckCount(context, Cards.copper) + " coppers in deck and total treasure value " + getCurrencyTotal(context)); special_cards.remove(scard); } } scard = Cards.bank; if (special_cards.contains(scard)) { // TODO tpc counted badly, to check again double tpc = (double) (this.getCardCount(TreasureCard.class) / getDeckSize(deck)); if (tpc * 5.0 < 3.0) { this.log("Bank not good, tpc is " + tpc); special_cards.remove(scard); } } scard = Cards.hoard; if (special_cards.contains(scard)) { if ((Util.getCardCount(deck, Cards.gold) <= Util.getCardCount(deck, Cards.hoard)) || game.isPlatInGame()) { this.log("Hoard not good, either platinum in play or have " + Util.getCardCount(deck, Cards.gold) + " gold and " + Util.getCardCount(deck, Cards.hoard) + " hoards"); special_cards.remove(scard); } } // here are the generic "Better then Silver" cards based on value scard = Cards.harem; if (special_cards.contains(scard)) { if ((mpc < 1.2) || (game.isPlatInGame())) { this.log("Harem not good, mpc = " + mpc); special_cards.remove(scard); } } if (maxMPC_card != null) { scard = Cards.venture; if (special_cards.contains(scard)) { if (maxMPC_card.getValue() > 2) { special_cards.remove(scard); } } scard = Cards.royalSeal; if (special_cards.contains(scard)) { if (maxMPC_card.getValue() > 2) { special_cards.remove(scard); } } scard = Cards.foolsGold; if (special_cards.contains(scard)) { if (maxMPC_card.getValue() > 1) { special_cards.remove(scard); } } } scard = Cards.contraband; if (special_cards.contains(scard)) { special_cards.remove(scard); } scard = Cards.potion; if ((special_cards.contains(scard)) && (!this.needMorePotion(deck))) { special_cards.remove(scard); } scard = Cards.farmland; if (special_cards.contains(scard)) { if (maxVP_card != null && maxVP_card.getVictoryPoints() < 4) { if (!(hand.contains(Cards.curse))) { special_cards.remove(scard); } } } } this.log("specials : " + special_cards); if (!special_cards.isEmpty()) { int scost = -1; int svalue = -1; //int spoints = -1; for (Card c : special_cards) { // potion is special because it can't be compared based on value if (c.equals(Cards.potion)) { this.log("potion: have " + Util.getCardCount(deck, Cards.potion) + " and " + Util.getCardCount(deck, Cards.alchemist) + " Alchemist(s) in " + deck.size() + " cards"); if (this.needMorePotion(deck)) { switch (this.strategy) { case SingleAction: if ((Util.getCardCount(deck, Cards.potion) < 1) && (Util.getCardCount(deck, this.strategyCard) < 1)) { maxMPC_card = (TreasureCard) c; scost = 1000; svalue = 1000; } break; case DoubleAction: if ((Util.getCardCount(deck, Cards.potion) < 1) && (Util.getCardCount(deck, this.strategyCard) < 2)) { maxMPC_card = (TreasureCard) c; scost = 1000; svalue = 1000; } break; case MultiAction: if ((Util.getCardCount(deck, Cards.potion) * Math.max(10,(20 - (Util.getCardCount(deck, Cards.alchemist) * 2)))) < deck.size()) { maxMPC_card = (TreasureCard) c; scost = 1000; svalue = 1000; } break; case Mirror: for (Card op : this.opponents.getActionCards()) { if (op.costPotion()) { if ((Util.getCardCount(deck, Cards.potion) * Math.max(10,(20 - (Util.getCardCount(deck, Cards.alchemist) * 2)))) < deck.size()) { maxMPC_card = (TreasureCard) c; scost = 1000; svalue = 1000; } else if (Util.getCardCount(deck, Cards.potion) < 1) { maxMPC_card = (TreasureCard) c; scost = 1000; svalue = 1000; } } } break; default: break; } } } // potion else if (specialTreasureCards.contains(c)) { TreasureCard tc = (TreasureCard) c; if ((tc.getValue() > svalue) && (tc.getCost(context) > scost)) { scost = tc.getCost(context); svalue = tc.getValue(); maxMPC_card = tc; } } else if (specialVictoryCards.contains(c)) { if (c.equals(Cards.farmland)) { if ((hand.contains(Cards.curse)) && (maxVP_card == null || maxVP_card.getVictoryPoints() <= 4)) { maxVP_card = (VictoryCard) c; } } } } } this.log("best final mpc: " + maxMPC_card); this.log("best final vp: " + maxVP_card); if (maxMPC_card != null) { if (maxMPC_card.equals(Cards.copper)) { if (mpc * 5 > 3) { maxMPC_card = null; } } } this.log("current deck mpc: " + mpc); this.log("guessTurnsToReshuffle(): " + guessTurnsToReshuffle()); this.log("guessTurnsToEnd(): " + guessTurnsToEnd()); this.log("best action: " + action_card); if (action_card != null && maxVP_card != null && (maxVP_card.getVictoryPoints() < 6)) { this.log("choosing action"); switch (strategy) { case Minion: return action_card; case Mirror: this.opponents.getActionCards().remove(action_card); return action_card; case SingleAction: case DoubleAction: if (this.strategyPossibleCards.contains(action_card)) { return action_card; } break; case MultiAction: int acvalue = Math.max(action_card.getCost(context), 3) + action_card.getAddGold() + action_card.getAddActions(); if (action_card.costPotion()) { acvalue += 2; } this.log("action card value: " + acvalue); if (acvalue >= gold) { if (!knownMultiActionCards.contains(action_card)) { this.strategyMultiCardTerminal = action_card; this.log("multi action terminal: " + this.strategyMultiCardTerminal); } if (this.guessCurrencyTotal(getAllCards()) > 7 + this.getActionCardCount()) { return action_card; } } break; default: break; } } if (maxVP_card != null) { if (!exact || maxVP_card.getCost(context) == gold) { if ((mpc > (1.9 - (maxVP_card.getVictoryPoints() * 0.15 * (context.countCardsInPlay(Cards.hoard) + 1)))) || ((guessTurnsToReshuffle() > guessTurnsToEnd()) && (maxVP_card.getVictoryPoints() > 1))) { this.log("choosing victory (hoards: " + context.countCardsInPlay(Cards.hoard) + ")"); return maxVP_card; } } } if (maxMPC_card != null) { if (!exact || maxMPC_card.getCost(context) == gold) { this.log("choosing treasure"); return maxMPC_card; } } if (mandatory) { this.log("must choose a card"); for (AbstractCardPile pile : game.piles.values()) { Card card = pile.card(); if (!exact || card.getCost(context) == gold) { if ((game.isValidBuy(context, card, gold)) && !(card.equals(Cards.curse))) { return card; } } } } this.log("choosing nothing"); return null; } private boolean willEndGameGaining(Card card) { if (game.emptyPiles() > 2) { return true; } if ((card.equals(Cards.province)) && (game.pileSize(card) == 1)) { return true; } if ((card.equals(Cards.colony)) && (game.pileSize(card) == 1)) { return true; } if (game.emptyPiles() == 2) { if (game.pileSize(card) == 1) { return true; } if ((knownCursingCards.contains(card)) && (game.pileSize(Cards.curse) == 1)) { return true; } if ((card.equals(Cards.cache)) && (game.pileSize(Cards.copper) <= 2) && (game.pileSize(Cards.copper) > 0) ) { return true; } } return false; } // public double compareDeckOptions(ArrayList<Card> deck, Card option1, Card option2, int hoardsPlayed) { // ArrayList<Card> o1 = new ArrayList<Card>(); // ArrayList<Card> o2 = new ArrayList<Card>(); // // if (option1 != null) { // o1.add(option1); // } // // if (option2 != null) { // o2.add(option2); // } // // return compareDeckOptions(deck, o1, o2, hoardsPlayed); // } /** * @param deck * @param option1 * @param option2 * @param hoardsPlayed * @return */ // public double compareDeckOptions(ArrayList<Card> deck, ArrayList<Card> option1, ArrayList<Card> option2, int hoardsPlayed) { // ArrayList<Card> deck1 = new ArrayList<Card>(); // ArrayList<Card> deck2 = new ArrayList<Card>(); // // for (Card c : deck) { // deck1.add(c); // deck2.add(c); // } // // for (Card c : option1) { // deck1.add(c); // if (c.equals(Cards.cache)) { // deck1.add(Cards.copper); // deck1.add(Cards.copper); // } // if (c.equals(Cards.farmland)) { // deck1.remove(Cards.curse); // deck1.add(Cards.estate); // } // if (c instanceof VictoryCard) { // for (int i = 0; i < hoardsPlayed; i++) { // deck1.add(Cards.gold); // } // } // for (int e = 0; e < game.getEmbargos(c.toString()); e++) { // deck1.add(Cards.curse); // } // } // for (Card c : option2) { // deck2.add(c); // if (c.equals(Cards.cache)) { // deck2.add(Cards.copper); // deck2.add(Cards.copper); // } // if (c.equals(Cards.farmland)) { // deck2.remove(Cards.curse); // deck2.add(Cards.estate); // } // if (c instanceof VictoryCard) { // for (int i = 0; i < hoardsPlayed; i++) { // deck2.add(Cards.gold); // } // } // for (int e = 0; e < game.getEmbargos(c.toString()); e++) { // deck2.add(Cards.curse); // } // // } // // return compareCards(deck1, deck2); // } @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. super.newGame(context); redefineStrategy = false; strategy = StrategyOption.Nothing; strategyCard = null; strategyMultiCardTerminal = null; strategyPossibleCards = new ArrayList<ActionCard>(); //opponentActionCards = new ArrayList<Card>(); //opponentVP = -1000; //opponentIsAttacking = false; this.opponents = new OpponentList(); } private void advisorAction() { ArrayList<ActionCard> cards = new ArrayList<ActionCard>(); boolean shouldReCurse = false; this.strategyPossibleCards.clear(); for (Card card : this.opponents.getActionCards()) { if ((knownCursingCards.contains(card)) && (game.pileSize(Cards.curse) > (this.guessTurnsToReshuffle() + 2))) { shouldReCurse = true; } } for (AbstractCardPile pile : game.piles.values()) { if ((knownActionCards.contains(pile.card())) && (pile.getCount() > 2)) { if ((knownCursingCards.contains(pile.card())) || (!shouldReCurse)) { if (!game.embargos.containsKey(pile.card().getName())) { cards.add((ActionCard) pile.card()); } else { this.log("advisorAction: skipped " + pile.card() + " due to embargo"); } } } } this.log("advisorAction: considering " + cards.size() + " action cards out of " + knownActionCards.size() + " total known cards"); ArrayList<Card> tier1 = new ArrayList<Card>(cards); tier1.retainAll(VDomPlayerPatrick.knownDoubleActionCards); if (tier1.size() > 0) { this.log("advisorAction: found Tier1 cards " + tier1); cards.clear(); for (Card c : tier1) { if (game.pileSize(c) > 2) { cards.add((ActionCard) c); } } } if (cards.size() > 0) { // pick random card to base strategy on this.strategyCard = null; while ((cards.size() > 0) && (this.strategyCard == null)) { this.strategyCard = cards.get(this.rand.nextInt(cards.size())); cards.remove(this.strategyCard); if (game.pileSize(this.strategyCard) < 3) { this.strategyCard = null; } } if (this.strategyCard != null) { this.strategyPossibleCards.add(this.strategyCard); } else return; if (this.strategyCard.equals(Cards.minion)) { this.strategy = StrategyOption.Minion; this.log("advisorAction: " + this.strategyCard); } else if (VDomPlayerPatrick.knownMultiActionCards.contains(this.strategyCard)) { this.strategy = StrategyOption.MultiAction; this.log("advisorAction: multiple cantrips and combo cards (via " + this.strategyCard + ")"); for (AbstractCardPile pile : game.piles.values()) { if (VDomPlayerPatrick.knownMultiActionCards.contains(pile.card())) { this.strategyPossibleCards.add((ActionCard) pile.card()); } } for (AbstractCardPile pile : game.piles.values()) { if (VDomPlayerPatrick.knownComboActionCards.contains(pile.card())) { this.strategyPossibleCards.add((ActionCard) pile.card()); } } } else if (knownDoubleActionCards.contains(this.strategyCard)) { this.strategy = StrategyOption.DoubleAction; this.log("advisorAction: double " + this.strategyCard); } else if (knownSingleActionCards.contains(this.strategyCard)) { this.strategy = StrategyOption.SingleAction; this.log("advisorAction: single " + this.strategyCard); } } else { this.strategy = StrategyOption.NoAction; this.log("advisorAction: pure big money"); } } private int getDeckSize(MoveContext context) { return getDeckSize(getAllCards()); } private int getDeckSize(ArrayList<Card> deck) { int size = 0; for (Card card : deck) { size++; if (card instanceof ActionCard) { if(isCantrip((ActionCard) card)) { size--; } } } return size; } @Override public Card jackOfAllTrades_nonTreasureToTrash(MoveContext context) { ArrayList<Card> temphand = hand.toArrayListClone(); while (temphand.size() > 0) { Card tcard = this.getCardToTrash(temphand, DiscardOption.SemiDestructive); temphand.remove(tcard); temphand.trimToSize(); if (tcard == null) { return null; } if (tcard instanceof TreasureCard) { tcard = null; } if (tcard != null) { return tcard; } } return null; } @Override public boolean jackOfAllTrades_shouldDiscardCardFromTopOfDeck(MoveContext context, Card card) { ArrayList<Card> a = new ArrayList<Card>(); a.add(card); if(card.equals(getCardToDiscard(a, DiscardOption.NonDestructive))) { return true; } return false; } @Override public void gameEvent(GameEvent event) { super.gameEvent(event); if (event.player.equals(this)) { return; // we keep track of our own events, thank you very much } if (this.opponents.isEmpty()) { this.opponents.put(event.player.playerNumber, new Opponent(event.player.playerNumber)); } else if (!this.opponents.containsKey(event.player.playerNumber)) { this.opponents.put(event.player.playerNumber, new Opponent(event.player.playerNumber)); } if (this.opponents.get(event.player.playerNumber).getVP() == -1000) { if (game.sheltersInPlay) { this.opponents.get(event.player.playerNumber).setVP(0); } else { this.opponents.get(event.player.playerNumber).setVP(3); } } if ((event.getType() == GameEvent.Type.BuyingCard) || (event.getType() == GameEvent.Type.CardObtained)) { Card card = event.getCard(); if (card instanceof ActionCard) { ActionCard ac = (ActionCard) card; this.opponents.get(event.player.playerNumber).putActionCard(card); if (ac.isAttack()) { this.opponents.get(event.player.playerNumber).setAttacking(true); } } if (card instanceof VictoryCard) { this.opponents.get(event.player.playerNumber).addVP(((VictoryCard)card).getVictoryPoints()); } if (card.equals(Cards.curse)) { this.opponents.get(event.player.playerNumber).addVP(-1); } } if (event.getType() == GameEvent.Type.CardTrashed) { Card card = event.getCard(); if (card instanceof VictoryCard) { this.opponents.get(event.player.playerNumber).addVP(0-((VictoryCard)card).getVictoryPoints()); } if (card.equals(Cards.curse)) { this.opponents.get(event.player.playerNumber).addVP(1); } } } @Override public Card[] chapel_cardsToTrash(MoveContext context) { ArrayList<Card> ret = new ArrayList<Card>(); ArrayList<Card> temphand = this.hand.toArrayListClone(); for (int i = 0; i < 4; i++) { Card card = this.getCardToTrash(temphand, DiscardOption.NonDestructive); if (card != null) { ret.add(card); temphand.remove(card); } else { break; } } if (ret.size() > 0) { return ret.toArray(new Card[ret.size()]); } return null; } @Override public Card[] vault_cardsToDiscardForCard(MoveContext context) { return this.getCardsToDiscard(hand.toArrayListClone(), 2, DiscardOption.NonDestructive).toArray(new Card[2]); } @Override public Card trader_cardToTrash(MoveContext context) { return this.getCardToTrash(DiscardOption.Destructive); } @Override public boolean trader_shouldGainSilverInstead(MoveContext context, Card card) { ArrayList<Card> temp = new ArrayList<Card>(); temp.add(card); if (card == this.getCardToTrash(temp, DiscardOption.SemiDestructive)) { return true; } return false; } @Override public WatchTowerOption watchTower_chooseOption(MoveContext context, Card card) { if(this.shouldTrash(card)) { return WatchTowerOption.Trash; } if(isOnlyVictory(card)) { return WatchTowerOption.Normal; } return WatchTowerOption.TopOfDeck; } private int getCardNameCount(ArrayList<Card> deck) { ArrayList<String> names = new ArrayList<String>(); for (Card card : deck) { if (!names.contains(card.toString())) { names.add(card.toString()); } } return names.size(); } private boolean needMorePotion(ArrayList<Card> deck) { if (this.strategyPossibleCards.size() > 0) { if (this.strategyCard.costPotion()) { switch (strategy) { case SingleAction: case DoubleAction: return (Util.getCardCount(deck, Cards.potion) == 0); case NoAction: return false; case MultiAction: if (Util.getCardCount(deck, Cards.potion) < 1) { return true; } if ((Util.getCardCount(deck, Cards.alchemist) > 1) && (deck.size() / 5 > Util.getCardCount(deck, Cards.potion))) { return true; } break; default: break; } } } return false; } @SuppressWarnings("unused") private int needPotion() { // TODO must be tested float needpotion = 0; if (this.strategyPossibleCards.isEmpty()) { return 0; } for (ActionCard ac : this.strategyPossibleCards) { if (ac.costPotion() && needpotion < 1) { needpotion = 1; } if (ac.equals(Cards.alchemist)) { needpotion = (getAllCards().size() / (6 + (Util.getCardCount(getAllCards(), Cards.alchemist) * 3))); } } return Math.round(needpotion); } @Override public Card[] steward_cardsToTrash(MoveContext context) { ArrayList<Card> temphand = this.hand.toArrayListClone(); Card[] ret = new Card[2]; ret[0] = this.getCardToTrash(temphand, this.getAllCards(), DiscardOption.Destructive); temphand.remove(ret[0]); ret[1] = this.getCardToTrash(temphand, this.getAllCards(), DiscardOption.Destructive); return ret; } @Override public StewardOption steward_chooseOption(MoveContext context) { ArrayList<Card> temphand = hand.toArrayListClone(); Card[] ret = { null, null }; ret[0] = this.getCardToTrash(temphand, DiscardOption.SemiDestructive); if (ret[0] != null ) { temphand.remove(ret[0]); ret[1] = this.getCardToTrash(temphand, DiscardOption.SemiDestructive); if (ret[1] != null) { return StewardOption.TrashCards; } } if ((getMyAddActions() > 1) || (this.getMoneyPerCard(deck.toArrayList()) > 1)) { return StewardOption.AddCards; } return StewardOption.AddGold; } private int winningBy(MoveContext context) { int maxVP = this.opponents.maxVP(); return ( maxVP > -1000 ? this.getVPTotalValue(context) - maxVP : 0); } @Override public boolean duchess_shouldGainBecauseOfDuchy(MoveContext context) { return this.strategyPossibleCards.contains(Cards.duchess); } @Override public Card bishop_cardToTrash(MoveContext context) { return this.getCardToTrash(DiscardOption.NonDestructive); } @Override public Card bishop_cardToTrashForVictoryTokens(MoveContext context) { if (inHand(Cards.curse)) { return Cards.curse; } if (inHand(Cards.estate)) { return Cards.estate; } return this.getCardToTrash(DiscardOption.Destructive); } private Card getCardToTrash(DiscardOption destructive) { return getCardToTrash(this.hand.toArrayListClone(), this.getAllCards(), destructive); } @Override public int ambassador_returnToSupplyFromHand(MoveContext context, Card card) { ArrayList<Card> temphand = this.hand.toArrayListClone(); this.log("ambassador_returnToSupplyFromHand: current hand"); Card zero = this.advisorGeneral(context, this.getCurrencyTotal(temphand), false, false); temphand.remove(this.getCardToTrash(temphand, DiscardOption.Destructive)); this.log("ambassador_returnToSupplyFromHand: -1 card"); Card one = this.advisorGeneral(context, this.getCurrencyTotal(temphand), false, false); temphand.remove(this.getCardToTrash(temphand, DiscardOption.Destructive)); this.log("ambassador_returnToSupplyFromHand: -2 cards"); Card two = this.advisorGeneral(context, this.getCurrencyTotal(temphand), false, false); if (zero != null && two != null && zero.equals(two)) { return 2; } else if (zero != null && one != null && zero.equals(one)) { return 1; } else if (one != null && two != null && one.equals(two)) { return 2; } else { return 0; } } @Override public Card ambassador_revealedCard(MoveContext context) { return this.getCardToTrash(DiscardOption.Destructive); } public Card lookout_cardToDiscard(MoveContext context, Card[] cards) { ArrayList<Card> temp = new ArrayList<Card>(); for (Card c : cards) { temp.add(c); } this.log("lookout_cardToDiscard: " + temp); return this.getCardToDiscard(temp, DiscardOption.Destructive); } public Card lookout_cardToTrash(MoveContext context, Card[] cards) { ArrayList<Card> temp = new ArrayList<Card>(); for (Card c : cards) { temp.add(c); } this.log("lookout_cardToTrash: " + temp); return this.getCardToTrash(temp, DiscardOption.Destructive); } private boolean shouldReEvaluateStrategy() { if (this.strategy == StrategyOption.Nothing) { return true; } if (this.redefineStrategy) { return true; } if (this.opponents != null && this.opponents.getIsAttacking() && this.strategyCard != null && !this.strategyCard.isAttack()) { return true; } if (this.strategy == StrategyOption.Minion) { return false; } int available = 0; for (Card c : this.strategyPossibleCards) { available += game.pileSize(c); } return (available == 0); } private ArrayList<Card> getStrategyCardsInDeck(MoveContext context, boolean onlyTerminals) { ArrayList<Card> ret = new ArrayList<Card>(); for (Card card : getAllCards()) { if (this.strategyPossibleCards.contains(card)) { if (!isCantrip((ActionCard) card) || !onlyTerminals) { ret.add(card); } } } return ret; } private ArrayList<Card> getTerminalsInDeck(MoveContext context) { ArrayList<Card> ret = new ArrayList<Card>(); for (Card card : getAllCards()) { if (card instanceof ActionCard) { if (((ActionCard) card).getAddActions() <= 0) { ret.add(card); } } } return ret; } private float getCardsPerHand(MoveContext context) { int addcards = 0; for (Card card : getAllCards()) { if (card instanceof ActionCard) { addcards += ((ActionCard) card).getAddCards(); } } return (addcards / 5) + 5; } @Override public Card haggler_cardToObtain(MoveContext context, int maxCost, boolean potion) { this.log("haggler_cardToObtain"); if (maxCost < 0) return null; else return this.advisorGeneral(context, maxCost, false, true); } private Card advisorPlayAction(ArrayList<Card> hand) { this.log("advisorPlayAction: " + hand); ArrayList<ActionCard> ac = new ArrayList<ActionCard>(); for (Card card : hand) { if (card instanceof ActionCard) { ac.add((ActionCard) card); } } ArrayList<Card> temphand = new ArrayList<Card>(hand); if (ac.size() > 0) { if (ac.contains(Cards.kingsCourt)) { temphand.remove(Cards.kingsCourt); Card temp = advisorPlayAction(temphand); if (temp != null) { return hand.get(hand.indexOf(Cards.kingsCourt)); } } if (ac.contains(Cards.throneRoom)) { temphand.remove(Cards.throneRoom); Card temp = advisorPlayAction(temphand); if (temp != null) { return hand.get(hand.indexOf(Cards.throneRoom)); } } for (ActionCard a : ac) { if (knownTrashingCards.contains(ac)) { if ((this.getDeckSize() < 6) || (getCardToTrash(DiscardOption.SemiDestructive) == null) || (a.equals(Cards.masquerade))) { ac.remove(a); } } } for (ActionCard a : ac) { if (isCantrip(a)) { return a; } } for (ActionCard a : ac) { if (a.getAddActions() > 0) { return a; } } ActionCard bestcoin = (ActionCard) Cards.village; ActionCard bestcards = (ActionCard) Cards.militia; for (ActionCard a : ac) { if (a.getAddGold() > bestcoin.getAddGold()) { bestcoin = a; } if (a.getAddCards() > bestcards.getAddCards()) { bestcards = a; } } if ((bestcoin.getAddGold() > (bestcards.getAddCards() * getMoneyPerCard(this.deck.toArrayList()))) && (!bestcoin.equals(Cards.village))) { return bestcoin; } if (!bestcards.equals(Cards.militia)) { return bestcards; } return ac.get(rand.nextInt(ac.size())); } return null; } @Override public boolean illGottenGains_gainCopper(MoveContext context) { int gold = this.getCurrencyTotal(this.hand.toArrayListClone()); this.log("illGottenGains_gainCopper: evaluating with " + gold + " gold"); return (this.advisorGeneral(context, gold, false, false) != this.advisorGeneral(context, gold+1, false, false)); } @Override public TournamentOption tournament_chooseOption(MoveContext context) { for(Card c : VDomPlayerPatrick.knownPrizeCards) { if(c.isPrize() && context.getPileSize(c) > 0) { this.log("tournament_chooseOption: prize"); return TournamentOption.GainPrize; } } this.log("tournament_chooseOption: duchy"); return TournamentOption.GainDuchy; } @Override public Card tournament_choosePrize(MoveContext context) { for(Card c : VDomPlayerPatrick.knownPrizeCards) { if(c.isPrize() && context.getPileSize(c) > 0) { this.log("tournament_choosePrize: " + c); return c; } } this.log("tournament_choosePrize: nothing"); return null; } public Card courtyard_cardToPutBackOnDeck(MoveContext context) { HashMap<Card, Card> options = new HashMap<Card, Card>(); // this list will contain card to discard as key and best card to obtain with the rest as value ArrayList<Card> list = this.hand.toArrayListClone(); Card ret = this.hand.get(0); // let's see what we can buy with each of the cards in hand removed for (Card c : list) { ArrayList<Card> temp = new ArrayList<Card>(list); temp.remove(c); Card a = this.advisorGeneral(context, this.getCurrencyTotal(temp) + context.getCoinAvailableForBuy(), false, false); if (a != null) { options.put(a, c); } } this.log("courtyard_cardToPutBackOnDeck: " + options); // if we can buy a good card with one of the cards removed, we will do that if (options.containsKey(Cards.colony)) { this.log("courtyard_cardToPutBackOnDeck: can buy colony without " + options.get(Cards.colony)); return this.hand.get(options.get(Cards.colony)); //return options.get(Cards.colony); } if (options.containsKey(Cards.platinum)) { this.log("courtyard_cardToPutBackOnDeck: can buy platinum without " + options.get(Cards.platinum)); return this.hand.get(options.get(Cards.platinum)); } if (options.containsKey(Cards.province)) { this.log("courtyard_cardToPutBackOnDeck: can buy province without " + options.get(Cards.province)); return this.hand.get(options.get(Cards.province)); } if (options.containsKey(Cards.gold)) { this.log("courtyard_cardToPutBackOnDeck: can buy gold without " + options.get(Cards.gold)); return this.hand.get(options.get(Cards.gold)); } if (this.hand.contains(Cards.gold)) { this.log("courtyard_cardToPutBackOnDeck: gold"); return this.hand.get(Cards.gold); } if (this.hand.contains(Cards.silver)) { this.log("courtyard_cardToPutBackOnDeck: silver"); return this.hand.get(Cards.silver); } if (this.hand.contains(Cards.copper)) { this.log("courtyard_cardToPutBackOnDeck: copper"); return this.hand.get(Cards.copper); } return ret; } @Override public boolean scryingPool_shouldDiscard(MoveContext context, Player targetPlayer, Card card) { boolean discard = this.shouldDiscard(card); if (targetPlayer == this) { this.log("scryingPool_shouldDiscard: " + (discard ? "discard " : "keep ") + "my " + card ); return discard; } else { this.log("scryingPool_shouldDiscard: " + (!discard ? "discard " : "keep ") + "opponents " + card ); return !discard; } } @Override public MinionOption minion_chooseOption(MoveContext context) { int inhand = Util.getCardCount(hand, Cards.minion); if (this.strategy == StrategyOption.Minion) { int coins = this.getCurrencyTotal(this.hand.toArrayListClone()) + context.getCoinAvailableForBuy(); int limit = 8; if (game.pileSize(Cards.minion) > 0) { limit = 5; } this.log("minion_chooseOption: inhand=" + inhand + "; coins: " + coins); if (inhand > 0) { return Player.MinionOption.AddGold; } if (inhand*2 + coins + 2 >= limit) { return Player.MinionOption.AddGold; } else { return Player.MinionOption.RolloverCards; } } if (inhand > 0) { return Player.MinionOption.AddGold; } if (context.getCoinAvailableForBuy() >= 5) { return Player.MinionOption.AddGold; } if (context.getPlayer().getHand().size() < 3) { return Player.MinionOption.RolloverCards; } return Player.MinionOption.AddGold; } @Override public Card masquerade_cardToPass(MoveContext context) { return this.getCardToTrash(DiscardOption.Destructive); } @Override public Card masquerade_cardToTrash(MoveContext context) { return this.getCardToTrash(DiscardOption.NonDestructive); } @Override public Card ironworks_cardToObtain(MoveContext context) { return this.advisorGeneral(context, 4, false, true); } @Override public boolean duchess_shouldDiscardCardFromTopOfDeck(MoveContext context, Card card) { return this.shouldDiscard(card); } @Override public Card tradeRoute_cardToTrash(MoveContext context) { return this.getCardToTrash(DiscardOption.Destructive); } @Override public Card rogue_cardToGain(MoveContext context) { // TODO Auto-generated method stub return super.rogue_cardToGain(context); } @Override public Card rogue_cardToTrash(MoveContext context, ArrayList<Card> canTrash) { return this.getCardToTrash(canTrash, DiscardOption.Destructive); } @Override public Card pillage_opponentCardToDiscard(MoveContext context, ArrayList<Card> handCards) { // TODO Auto-generated method stub return super.pillage_opponentCardToDiscard(context, handCards); } }