package com.hearthsim.util; import com.hearthsim.card.Card; import com.hearthsim.card.Deck; import com.hearthsim.card.ImplementedCardList; import com.hearthsim.card.ImplementedCardList.ImplementedCard; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; import java.util.function.Predicate; /** * This class provides a mechanism for generating random decks. * * @author dyllonmgagnier * */ public class DeckFactory { private ArrayList<ImplementedCard> cards; private boolean limitCopies; private Random gen; private ImplementedCard[] cardsToInclude; /** * This method initializes a new DeckFactory. * * @param filter * Any card for which this returns true will be removed from the * potential card pool. * @param limitCopies * If true, then any deck will contain no more than two copies of * any card no more than one copy of any legendary. */ protected DeckFactory(Predicate<ImplementedCard> filter, boolean limitCopies, ImplementedCard[] cardsToInclude) { cards = ImplementedCardList.getInstance().getCardList(); cards.removeIf(filter); gen = new Random(); this.limitCopies = limitCopies; this.cardsToInclude = cardsToInclude; } /** * * @return All possible cards that could be in a deck generated by this * class as specified by the DeckFactoryBuilder. */ public ArrayList<ImplementedCard> getAllPossibleCards() { return new ArrayList<ImplementedCard>(cards); } /** * This method generates a new random deck as specified by the builder. The * decks are completely random so shuffling is unnecessary. * * @return */ public Deck generateRandomDeck() { Card[] result = new Card[30]; int resultPos = 0; // Add in cardsToInclude for (ImplementedCard cardToInclude : cardsToInclude) result[resultPos++] = cardToInclude.createCardInstance(); if (limitCopies) { HashMap<ImplementedCard, Integer> cardsInDeck = new HashMap<ImplementedCard, Integer>(); // Insert included cards into HashMap. for (ImplementedCard cardToInclude : cardsToInclude) { if (cardsInDeck.containsKey(cardToInclude)) cardsInDeck.put(cardToInclude, cardsInDeck.get(cardToInclude) + 1); else cardsInDeck.put(cardToInclude, 1); } while (resultPos < 30) { ImplementedCard toAdd; // Keep going until a card is found that can be added to the // deck. while (true) { toAdd = cards.get(gen.nextInt(cards.size())); if (!cardsInDeck.containsKey(toAdd)) { cardsInDeck.put(toAdd, 1); break; } else if (cardsInDeck.get(toAdd).equals(1) && !toAdd.rarity_.equals("legendary")) { cardsInDeck.put(toAdd, 2); break; } } result[resultPos++] = toAdd.createCardInstance(); } } else { while (resultPos < 30) { result[resultPos++] = cards.get(gen.nextInt(cards.size())) .createCardInstance(); } } Deck deckResult = new Deck(result); deckResult.shuffle(); return deckResult; } /** * This class builds a DeckFactory and allows for various options to be * selected for the factory. * * @author dyllonmgagnier * */ public static class DeckFactoryBuilder { private Predicate<ImplementedCard> filter; private boolean limitCopies; private boolean allowUncollectible; private ImplementedCard[] cardsToInclude; /** * Constructs the default builder which does not allow for uncollectible * cards and will limit the number of copies of any card to no more than * two and limits the number of copies any particular legendary to no * more than one. Each method of the builder should only be called once * and successive calls will produce unspecified behavior. * * All of the methods return this DeckFactoryBuilder so that they can be * chained together if so desired or called one at a time. */ public DeckFactoryBuilder() { filter = (card) -> false; limitCopies = true; allowUncollectible = false; cardsToInclude = new ImplementedCard[0]; } /** * Limits the the card pool to only those specified by the given * rarities. This method will throw a NullPointerException if any of the * input rarities is null. * * @param rarities */ public DeckFactoryBuilder filterByRarity(String... rarities) { // Validate input. for (String rarity : rarities) if (rarity == null) throw new NullPointerException( "One of the input rarities was null."); filter = filter.or((card) -> { boolean result = true; if (card.rarity_ == null) return true; for (String rarity : rarities) result = result && !card.rarity_.equals(rarity); return result; }); return this; } /** * Only select cards usable by the input character class (i.e. warlock, * priest, mage, rogue, etc.). As a note, if neutral cards are also * desired, "neutral" must be included as an argument. * * @param characterClass * The classes to filter by. */ public DeckFactoryBuilder filterByHero(String... characterClasses) { filter = filter.or((card) -> { boolean result = true; for (String characterClass : characterClasses) result = result && !card.charClass_.equals(characterClass); return result; }); return this; } /** * This method allows for uncollectible cards to be in the card pool. */ public DeckFactoryBuilder allowUncollectible() { allowUncollectible = true; return this; } /** * This method generates a DeckFactory based on the previously selected * options. * * @return A DeckFactory limited by the various options. */ public DeckFactory buildDeckFactory() { if (!allowUncollectible) filter = filter.or((card) -> !card.collectible).or((card) -> card.isHero); return new DeckFactory(filter, limitCopies, cardsToInclude); } /** * This method only allows for cards between the minimum and maximum * mana cost. * * @param minimumCost * The minimum mana cost allowed. * @param maximumCost * The maximum mana cost allowed. */ public DeckFactoryBuilder filterByManaCost(int minimumCost, int maximumCost) { filter = filter.or((card) -> card.mana_ < minimumCost || card.mana_ > maximumCost); return this; } /** * This method allows for unlimited copies of cards to be used (i.e. * like in Arena). */ public DeckFactoryBuilder allowUnlimitedCopiesOfCards() { limitCopies = false; return this; } /** * This method guarantees that the input cards will be included in any * generated deck. However, the number of cards that are to be included * should be less than 30. * * @param cardsToInclude */ public DeckFactoryBuilder includeSpecificCards( ImplementedCard... cardsToInclude) { this.cardsToInclude = cardsToInclude; return this; } } }