moved deck generator functions to framework - want to reuse in other test project

This commit is contained in:
betasteward 2015-09-22 09:49:35 -04:00
parent 39feb03e1f
commit 3a9bfcb1c6
11 changed files with 499 additions and 423 deletions

View file

@ -1,404 +0,0 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.client.deck.generator;
import java.util.*;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.cards.repository.ExpansionInfo;
import mage.cards.repository.ExpansionRepository;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
import mage.constants.Rarity;
/**
* Generates random card pool and builds a deck.
*
* @author nantuko
* @author Simown
*/
public class DeckGenerator {
private static final int MAX_TRIES = 8196;
private static DeckGeneratorDialog genDialog;
private static DeckGeneratorPool genPool;
/**
* Builds a deck out of the selected block/set/format.
* @return a path to the generated deck.
*/
public static String generateDeck() {
genDialog = new DeckGeneratorDialog();
if (genDialog.getSelectedColors() != null) {
Deck deck = buildDeck();
return genDialog.saveDeck(deck);
}
// If the deck couldn't be generated or the user cancelled, repopulate the deck selection with its cached value
return PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE, null);
}
protected static Deck buildDeck() {
String selectedColors = genDialog.getSelectedColors();
List<ColoredManaSymbol> allowedColors = new ArrayList<>();
selectedColors = selectedColors != null ? selectedColors.toUpperCase() : getRandomColors("X");
String format = genDialog.getSelectedFormat();
List<String> setsToUse = ConstructedFormats.getSetsByFormat(format);
if (setsToUse.isEmpty()) {
// Default to using all sets
setsToUse = ExpansionRepository.instance.getSetCodes();
}
int deckSize = genDialog.getDeckSize();
if (selectedColors.contains("X")) {
selectedColors = getRandomColors(selectedColors);
}
for (int i = 0; i < selectedColors.length(); i++) {
char c = selectedColors.charAt(i);
allowedColors.add(ColoredManaSymbol.lookup(c));
}
return generateDeck(deckSize, allowedColors, setsToUse);
}
/**
* If the user has selected random colors, pick them randomly for the user.
* @param selectedColors a string of the colors selected.
* @return a String representation of the new colors chosen.
*/
private static String getRandomColors(String selectedColors) {
Random random = new Random();
List<Character> availableColors = new ArrayList<>();
for (ColoredManaSymbol cms : ColoredManaSymbol.values()) {
availableColors.add(cms.toString().charAt(0));
}
StringBuilder generatedColors = new StringBuilder();
int randomColors = 0;
for (int i = 0; i < selectedColors.length(); i++) {
char currentColor = selectedColors.charAt(i);
if (currentColor != 'X') {
generatedColors.append(currentColor);
availableColors.remove(new Character(currentColor));
} else {
randomColors++;
}
}
for (int i = 0; i < randomColors && !availableColors.isEmpty(); i++) {
int index = random.nextInt(availableColors.size());
generatedColors.append(availableColors.remove(index));
}
return generatedColors.toString();
}
/**
* Generates all the cards to use in the deck.
* Adds creatures, non-creatures, lands (including non-basic).
* Fixes the deck, adjusting for size and color of the cards retrieved.
* @param deckSize how big the deck is to generate.
* @param allowedColors which colors are allowed in the deck.
* @param setsToUse which sets to use to retrieve cards for this deck.
* @return the final deck to use.
*/
private static Deck generateDeck(int deckSize, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton());
final String[] sets = setsToUse.toArray(new String[setsToUse.size()]);
// Creatures
final CardCriteria creatureCriteria = new CardCriteria();
creatureCriteria.setCodes(sets);
creatureCriteria.notTypes(CardType.LAND);
creatureCriteria.types(CardType.CREATURE);
if (!(genDialog.useArtifacts()))
creatureCriteria.notTypes(CardType.ARTIFACT);
// Non-creatures (sorcery, instant, enchantment, artifact etc.)
final CardCriteria nonCreatureCriteria = new CardCriteria();
nonCreatureCriteria.setCodes(sets);
nonCreatureCriteria.notTypes(CardType.LAND);
nonCreatureCriteria.notTypes(CardType.CREATURE);
if (!(genDialog.useArtifacts()))
nonCreatureCriteria.notTypes(CardType.ARTIFACT);
// Non-basic land
final CardCriteria nonBasicLandCriteria = new CardCriteria();
nonBasicLandCriteria.setCodes(sets);
nonBasicLandCriteria.types(CardType.LAND);
nonBasicLandCriteria.notSupertypes("Basic");
// Generate basic land cards
Map<String, List<CardInfo>> basicLands = generateBasicLands(setsToUse);
generateSpells(creatureCriteria, genPool.getCreatureCount());
generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount());
generateLands(nonBasicLandCriteria, genPool.getLandCount(), basicLands);
// Reconstructs the final deck and adjusts for Math rounding and/or missing cards
return genPool.getDeck();
}
/**
* Generates all spells for the deck.
* Each card is retrieved from the database and checked against the converted mana cost (CMC) needed for the current card pool.
* If a card's CMC matches the CMC range required by the pool, it is added to the deck.
* This ensures that the majority of cards fit a fixed mana curve for the deck, and it is playable.
* Creatures and non-creatures are retrieved separately to ensure the deck contains a reasonable mix of both.
* @param criteria the criteria to search for in the database.
* @param spellCount the number of spells that match the criteria needed in the deck.
*/
private static void generateSpells(CardCriteria criteria, int spellCount) {
List<CardInfo> cardPool = CardRepository.instance.findCards(criteria);
int retrievedCount = cardPool.size();
List<DeckGeneratorCMC> deckCMCs = genPool.getCMCsForSpellCount(spellCount);
Random random = new Random();
int count = 0;
int reservesAdded = 0;
boolean added;
if (retrievedCount > 0 && retrievedCount >= spellCount) {
int tries = 0;
while (count < spellCount) {
Card card = cardPool.get(random.nextInt(retrievedCount)).getMockCard();
if (genPool.isValidSpellCard(card)) {
int cardCMC = card.getManaCost().convertedManaCost();
for (DeckGeneratorCMC deckCMC : deckCMCs) {
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
int currentAmount = deckCMC.getAmount();
if (currentAmount > 0) {
deckCMC.setAmount(currentAmount - 1);
genPool.addCard(card.copy());
count++;
}
} else {
if (reservesAdded < (genPool.getDeckSize() / 2)) {
added = genPool.tryAddReserve(card, cardCMC);
if(added)
reservesAdded++;
}
}
}
}
tries++;
if (tries > MAX_TRIES) {
// Break here, we'll fill in random missing ones later
break;
}
}
} else {
throw new IllegalStateException("Not enough cards to generate deck.");
}
}
/**
* Generates all the lands for the deck.
* Generates non-basic if selected by the user and if the deck isn't monocolored.
* Will fetch non-basic lands if required and then fill up the remaining space with basic lands.
* Basic lands are adjusted according to the mana symbols seen in the cards used in this deck.
* Usually the lands will be well balanced relative to the color of cards.
* @param criteria the criteria of the lands to search for in the database.
* @param landsCount the amount of lands required for this deck.
* @param basicLands information about the basic lands from the sets used.
*/
private static void generateLands(CardCriteria criteria, int landsCount, Map<String, List<CardInfo>> basicLands) {
int tries = 0;
int countNonBasic = 0;
// Store the nonbasic lands (if any) we'll add
List<Card> deckLands = new ArrayList<>();
// Calculates the percentage of colored mana symbols over all spells in the deck
Map<String, Double> percentage = genPool.calculateSpellColorPercentages();
// Only dual/tri color lands are generated for now, and not non-basic lands that only produce colorless mana.
if (!genPool.isMonoColoredDeck() && genDialog.useNonBasicLand()) {
List<Card> landCards = genPool.filterLands(CardRepository.instance.findCards(criteria));
int allCount = landCards.size();
Random random = new Random();
if (allCount > 0) {
while (countNonBasic < landsCount / 2) {
Card card = landCards.get(random.nextInt(allCount));
if (genPool.isValidLandCard(card)) {
Card addedCard = card.copy();
deckLands.add(addedCard);
genPool.addCard(addedCard);
countNonBasic++;
}
tries++;
// to avoid infinite loop
if (tries > MAX_TRIES) {
// Not a problem, just use what we have
break;
}
}
}
}
// Calculate the amount of colored mana already can be produced by the non-basic lands
Map<String, Integer> count = genPool.countManaProduced(deckLands);
// Fill up the rest of the land quota with basic lands adjusted to fit the deck's mana costs
addBasicLands(landsCount - countNonBasic, percentage, count, basicLands);
}
/**
* Returns a map of colored mana symbol to basic land cards of that color.
* @param setsToUse which sets to retrieve basic lands from.
* @return a map of color to basic lands.
*/
private static Map<String, List<CardInfo>> generateBasicLands(List<String> setsToUse) {
List<String> landSets = new LinkedList<>();
// decide from which sets basic lands are taken from
for (String setCode :setsToUse) {
ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
if (expansionInfo.hasBasicLands()) {
landSets.add(expansionInfo.getCode());
}
}
// if sets have no basic land, take land from block
if (landSets.isEmpty()) {
for (String setCode :setsToUse) {
ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
List<ExpansionInfo> blockSets = ExpansionRepository.instance.getSetsFromBlock(expansionInfo.getBlockName());
for (ExpansionInfo blockSet: blockSets) {
if (blockSet.hasBasicLands()) {
landSets.add(blockSet.getCode());
}
}
}
}
// if still no set with lands found, take one by random
if (landSets.isEmpty()) {
// if sets have no basic lands and also it has no parent or parent has no lands get last set with lands
// select a set with basic lands by random
Random generator = new Random();
List<ExpansionInfo> basicLandSets = ExpansionRepository.instance.getSetsWithBasicLandsByReleaseDate();
if (basicLandSets.size() > 0) {
landSets.add(basicLandSets.get(generator.nextInt(basicLandSets.size())).getCode());
}
}
if (landSets.isEmpty()) {
throw new IllegalArgumentException("No set with basic land was found");
}
CardCriteria criteria = new CardCriteria();
if (!landSets.isEmpty()) {
criteria.setCodes(landSets.toArray(new String[landSets.size()]));
}
Map<String, List<CardInfo>> basicLandMap = new HashMap<>();
for(ColoredManaSymbol c: ColoredManaSymbol.values()) {
String landName = DeckGeneratorPool.getBasicLandName(c.toString());
criteria.rarities(Rarity.LAND).name(landName);
List<CardInfo> cards = CardRepository.instance.findCards(criteria);
basicLandMap.put(landName, cards);
}
return basicLandMap;
}
/**
* Once any non-basic lands are added, add basic lands until the deck is filled.
* @param landsNeeded how many remaining lands are needed.
* @param percentage the percentage needed for each color in the final deck.
* @param count how many of each color can be produced by non-basic lands.
* @param basicLands list of information about basic lands from the database.
*/
private static void addBasicLands(int landsNeeded, Map<String, Double> percentage, Map<String, Integer> count, Map<String, List<CardInfo>> basicLands) {
int colorTotal = 0;
ColoredManaSymbol colorToAdd = null;
// Add up the totals for all colors, to keep track of the percentage a color is.
for (Map.Entry<String, Integer> c : count.entrySet()) {
colorTotal += c.getValue();
}
// Keep adding basic lands until we fill the deck
while (landsNeeded > 0) {
double minPercentage = Integer.MIN_VALUE;
for (ColoredManaSymbol color : ColoredManaSymbol.values()) {
// What percentage of this color is requested
double neededPercentage = percentage.get(color.toString());
// If there is a 0% need for basic lands of this color, skip it
if (neededPercentage <= 0) {
continue;
}
int currentCount = count.get(color.toString());
double thisPercentage = 0.0;
// Calculate the percentage of lands so far that produce this color
if (currentCount > 0)
thisPercentage = (currentCount / (double) colorTotal) * 100.0;
// Check if the color is the most "needed" (highest percentage) we have seen so far
if (neededPercentage - thisPercentage > minPercentage) {
// Put this color land forward to be added
colorToAdd = color;
minPercentage = (neededPercentage - thisPercentage);
}
}
if(colorToAdd != null) {
genPool.addCard(getBasicLand(colorToAdd, basicLands));
count.put(colorToAdd.toString(), count.get(colorToAdd.toString()) + 1);
colorTotal++;
landsNeeded--;
}
}
}
/**
* Return a random basic land of the chosen color.
* @param color the color the basic land should produce.
* @param basicLands list of information about basic lands from the database.
* @return a single basic land that produces the color needed.
*/
private static Card getBasicLand(ColoredManaSymbol color, Map<String, List<CardInfo>> basicLands) {
Random random = new Random();
String landName = DeckGeneratorPool.getBasicLandName(color.toString());
List<CardInfo> basicLandsInfo = basicLands.get(landName);
return basicLandsInfo.get(random.nextInt(basicLandsInfo.size() - 1)).getMockCard().copy();
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.client.deck.generator;
/**
* Stores a range of converted mana costs (CMC) for use in deck generation.
*/
public class DeckGeneratorCMC
{
public final int min;
public final int max;
public final float percentage;
private int amount = 0;
/**
* Constructs a CMC range given a minimum and maximum, and the percentage of cards that are in this range.
* @param min the minimum CMC a card in this range can be.
* @param max the maximum CMC a card in this range can be.
* @param percentage the percentage of cards in the range (min, max)
*/
DeckGeneratorCMC(int min, int max, float percentage)
{
this.min = min;
this.max = max;
this.percentage = percentage;
}
/**
* Sets the amount of cards needed in this CMC range.
* @param amount the number of cards needed.
*/
public void setAmount(int amount)
{
this.amount = amount;
}
/**
* Gets the number of cards needed in this CMC range.
* @return the number of cards needed in this CMC range.
*/
public int getAmount()
{
return this.amount;
}
}

View file

@ -27,20 +27,29 @@
*/
package mage.client.deck.generator;
import mage.cards.Sets;
import mage.cards.decks.Deck;
import mage.client.MageFrame;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.gui.ColorsChooser;
import mage.client.util.sets.ConstructedFormats;
import javax.swing.*;
import java.awt.*;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import mage.cards.ConstructedFormats;
import mage.cards.Sets;
import mage.cards.decks.Deck;
import mage.cards.decks.generator.DeckOptions;
import mage.client.MageFrame;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.gui.ColorsChooser;
/**
*
@ -195,6 +204,10 @@ public class DeckGeneratorDialog {
return null;
}
public DeckOptions getOptions() {
return new DeckOptions(getSelectedColors(), getSelectedFormat(), getDeckSize(), isSingleton(), useArtifacts(), useNonBasicLand());
}
public String getSelectedFormat() {
return (String) cbSets.getSelectedItem();
}

View file

@ -1,494 +0,0 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.client.deck.generator;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.cards.repository.CardInfo;
import mage.constants.ColoredManaSymbol;
import java.util.*;
/**
*
* @author Simown
*/
public class DeckGeneratorPool
{
// Constants for a 40 card deck
private static final int CREATURE_COUNT_40 = 15;
private static final int LAND_COUNT_40 = 17;
private static final int NONCREATURE_COUNT_40 = 8;
// Constants for a 60 card deck
private static final int CREATURE_COUNT_60 = 23;
private static final int LAND_COUNT_60 = 24;
private static final int NONCREATURE_COUNT_60 = 13;
private final List<ColoredManaSymbol> allowedColors;
private final List<DeckGeneratorCMC> poolCMCs;
private final int creatureCount;
private final int nonCreatureCount;
private final int landCount;
private final boolean isSingleton;
private final int deckSize;
// Count how many copies of the card exists in the deck to check we don't go over 4 copies (or 1 for singleton)
private Map<String, Integer> cardCounts = new HashMap<>();
// If there is only a single color selected to generate a deck
private boolean monoColored = false;
// List of cards so far in the deck
private List<Card> deckCards = new ArrayList<>();
// List of reserve cards found to fix up undersized decks
private List<Card> reserveSpells = new ArrayList<>();
private Deck deck;
public DeckGeneratorPool(final int deckSize, final List<ColoredManaSymbol> allowedColors, boolean isSingleton)
{
this.deckSize = deckSize;
this.allowedColors = allowedColors;
this.isSingleton = isSingleton;
this.deck = new Deck();
if(this.deckSize > 40) {
this.creatureCount = CREATURE_COUNT_60;
this.nonCreatureCount = NONCREATURE_COUNT_60;
this.landCount = LAND_COUNT_60;
poolCMCs = new ArrayList<DeckGeneratorCMC>() {{
add(new DeckGeneratorCMC(0, 2, 0.20f));
add(new DeckGeneratorCMC(3, 5, 0.50f));
add(new DeckGeneratorCMC(6, 7, 0.25f));
add(new DeckGeneratorCMC(8, 100, 0.05f));
}};
}
else {
this.creatureCount = CREATURE_COUNT_40;
this.nonCreatureCount = NONCREATURE_COUNT_40;
this.landCount = LAND_COUNT_40;
poolCMCs = new ArrayList<DeckGeneratorCMC>() {{
add(new DeckGeneratorCMC(0, 2, 0.30f));
add(new DeckGeneratorCMC(3, 4, 0.45f));
add(new DeckGeneratorCMC(5, 6, 0.20f));
add(new DeckGeneratorCMC(7, 100, 0.05f));
}};
}
if(allowedColors.size() == 1) {
monoColored = true;
}
}
/**
* Adjusts the number of spell cards that should be in a converted mana cost (CMC) range, given the amount of cards total.
* @param cardsCount the number of total cards.
* @return a list of CMC ranges, with the amount of cards for each CMC range
*/
public List<DeckGeneratorCMC> getCMCsForSpellCount(int cardsCount) {
List<DeckGeneratorCMC> adjustedCMCs = new ArrayList<>(this.poolCMCs);
// For each CMC calculate how many spell cards are needed, given the total amount of cards
for(DeckGeneratorCMC deckCMC : adjustedCMCs) {
deckCMC.setAmount((int)Math.ceil(deckCMC.percentage * cardsCount));
}
return adjustedCMCs;
}
/**
* Verifies if the spell card supplied is valid for this pool of cards.
* Checks that there isn't too many copies of this card in the deck.
* Checks that the card fits the chosen colors for this pool.
* @param card the spell card
* @return if the spell card is valid for this pool.
*/
public boolean isValidSpellCard(Card card)
{
int cardCount = getCardCount((card.getName()));
// Check it hasn't already got the maximum number of copies in a deck
if(cardCount < (isSingleton ? 1 : 4)) {
if(cardFitsChosenColors(card)) {
return true;
}
}
return false;
}
/**
* Verifies if the non-basic land card supplied is valid for this pool of cards.
* @param card the non-basic land card
* @return if the land card generates the allowed colors for this pool.
*/
public boolean isValidLandCard(Card card)
{
int cardCount = getCardCount((card.getName()));
// No need to check if the land is valid for the colors chosen
// They are all filtered before searching for lands to include in the deck.
return (cardCount < 4);
}
/**
* Adds a card to the pool and updates the count of this card.
* @param card the card to add.
*/
public void addCard(Card card)
{
Object cnt = cardCounts.get((card.getName()));
if(cnt == null)
cardCounts.put(card.getName(), 0);
int existingCount = cardCounts.get((card.getName()));
cardCounts.put(card.getName(), existingCount+1);
deckCards.add(card);
}
/**
* Adds a card to the reserve pool.
* Reserve pool is used when the deck generation fails to build a complete deck, or
* a partially complete deck (e.g. if there are no cards found that match a CMC)
* @param card the card to add
* @param cardCMC the converted mana cost of the card
*/
public boolean tryAddReserve(Card card, int cardCMC) {
// Only cards with CMC < 7 and don't already exist in the deck
// can be added to our reserve pool as not to overwhelm the curve
// with high CMC cards and duplicates.
if(cardCMC < 7 && getCardCount(card.getName()) == 0) {
this.reserveSpells.add(card);
return true;
}
return false;
}
/**
* Checks if the mana symbols in the card all match the allowed colors for this pool.
* @param card the spell card to check.
* @return if all the mana symbols fit the chosen colors.
*/
private boolean cardFitsChosenColors(Card card) {
for (String symbol : card.getManaCost().getSymbols()) {
boolean found = false;
symbol = symbol.replace("{", "").replace("}", "");
if (isColoredManaSymbol(symbol)) {
for (ColoredManaSymbol allowed : allowedColors) {
if (symbol.contains(allowed.toString())) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
}
return true;
}
/**
* Calculates the percentage of colored mana symbols over all spell cards in the deck.
* Used to balance the generation of basic lands so the amount of lands matches the
* cards mana costs.
* @return a list of colored mana symbols and the percentage of symbols seen in cards mana costs.
*/
public Map<String, Double> calculateSpellColorPercentages() {
final Map<String, Integer> colorCount = new HashMap<>();
for (final ColoredManaSymbol color : ColoredManaSymbol.values()) {
colorCount.put(color.toString(), 0);
}
// Counts how many colored mana symbols we've seen in total so we can get the percentage of each color
int totalCount = 0;
List<Card> fixedSpells = getFixedSpells();
for(Card spell: fixedSpells) {
for (String symbol : spell.getManaCost().getSymbols()) {
symbol = symbol.replace("{", "").replace("}", "");
if (isColoredManaSymbol(symbol)) {
for (ColoredManaSymbol allowed : allowedColors) {
if (symbol.contains(allowed.toString())) {
int cnt = colorCount.get(allowed.toString());
colorCount.put(allowed.toString(), cnt+1);
totalCount++;
}
}
}
}
}
final Map<String, Double> percentages = new HashMap<>();
for(Map.Entry<String, Integer> singleCount: colorCount.entrySet()) {
String color = singleCount.getKey();
int count = singleCount.getValue();
// Calculate the percentage this color has out of the total color counts
double percentage = (count / (double) totalCount) * 100;
percentages.put(color, percentage);
}
return percentages;
}
/**
* Calculates how many of each mana the non-basic lands produce.
* @param deckLands the non-basic lands which will be used in the deck.
* @return a mapping of colored mana symbol to the amount that can be produced.
*/
public Map<String,Integer> countManaProduced(List<Card> deckLands)
{
Map<String, Integer> manaCounts = new HashMap<>();
for (final ColoredManaSymbol color : ColoredManaSymbol.values()) {
manaCounts.put(color.toString(), 0);
}
for(Card land: deckLands) {
for(Ability landAbility: land.getAbilities()) {
for (ColoredManaSymbol symbol : allowedColors) {
String abilityString = landAbility.getRule();
if(landTapsForAllowedColor(abilityString, symbol.toString())) {
Integer count = manaCounts.get(symbol.toString());
manaCounts.put(symbol.toString(), count + 1);
}
}
}
}
return manaCounts;
}
/** Filter all the non-basic lands retrieved from the database.
* @param landCardsInfo information about all the cards.
* @return a list of cards that produce the allowed colors for this pool.
*/
public List<Card> filterLands(List<CardInfo> landCardsInfo) {
List<Card> matchingLandList = new ArrayList<>();
for(CardInfo landCardInfo: landCardsInfo) {
Card landCard = landCardInfo.getMockCard();
if(landProducesChosenColors(landCard)) {
matchingLandList.add(landCard);
}
}
return matchingLandList;
}
/**
* Returns the card name that represents the basic land for this color.
* @param symbolString the colored mana symbol.
* @return the name of a basic land card.
*/
public static String getBasicLandName(String symbolString) {
switch(symbolString) {
case "B":
return "Swamp";
case "G":
return "Forest";
case "R":
return "Mountain";
case "U":
return "Island";
case "W":
return "Plains";
default:
return "";
}
}
/**
* Returns a complete deck.
* @return the deck.
*/
public Deck getDeck() {
Set<Card> actualDeck = deck.getCards();
for(Card card : deckCards)
actualDeck.add(card);
return deck;
}
/**
* Returns the number of creatures needed in this pool.
* @return the number of creatures.
*/
public int getCreatureCount() {
return creatureCount;
}
/**
* Returns the number of non-creatures needed in this pool.
* @return the number of non-creatures.
*/
public int getNonCreatureCount() {
return nonCreatureCount;
}
/**
* Returns the number of lands (basic + non-basic) needed in this pool.
* @return the number of lands.
*/
public int getLandCount() {
return landCount;
}
/**
* Returns if this pool only uses one color.
* @return if this pool is monocolored.
*/
public boolean isMonoColoredDeck() {
return monoColored;
}
/**
* Returns the size of the deck to generate from this pool.
* @return the deck size.
*/
public int getDeckSize() {
return deckSize;
}
/**
* Fixes undersized or oversized decks that have been generated.
* Removes random cards from an oversized deck until it is the correct size.
* Uses the reserve pool to fill up and undersized deck with cards.
* @return a fixed list of cards for this deck.
*/
private List<Card> getFixedSpells()
{
Random random = new Random();
int spellSize = deckCards.size();
int nonLandSize = (deckSize - landCount);
// Less spells than needed
if(spellSize < nonLandSize) {
int spellsNeeded = nonLandSize-spellSize;
// If we haven't got enough spells in reserve to fulfil the amount we need, we can't continue.
if(reserveSpells.size() < spellsNeeded) {
throw new IllegalStateException("Not enough cards found to generate deck. Please try again");
}
List<Card> spellsToAdd = new ArrayList<>(spellsNeeded);
// Initial reservoir
for(int i = 0; i < spellsNeeded; i++)
spellsToAdd.add(reserveSpells.get(i));
for(int i = spellsNeeded+1; i < reserveSpells.size()-1; i++) {
int j = random.nextInt(i);
Card randomCard = reserveSpells.get(j);
if (isValidSpellCard(randomCard) && j < spellsToAdd.size()) {
spellsToAdd.set(j, randomCard);
}
}
// Add randomly selected spells needed
deckCards.addAll(spellsToAdd);
}
// More spells than needed
else if(spellSize > (deckSize - landCount)) {
int spellsRemoved = (spellSize)-(deckSize-landCount);
for(int i = 0; i < spellsRemoved; ++i) {
deckCards.remove(random.nextInt(deckCards.size()));
}
}
// Not strictly necessary as we check when adding cards, but worth double checking anyway.
if(deckCards.size() != nonLandSize) {
throw new IllegalStateException("Not enough cards found to generate deck. Please try again");
}
// Return the fixed amount
return deckCards;
}
/**
* Returns if this land will produce the chosen colors for this pool.
* @param card a non-basic land card.
* @return if this land card taps to produces the colors chosen.
*/
private boolean landProducesChosenColors(Card card) {
// All mock card abilities will be MockAbilities so we can't differentiate between ManaAbilities
// and other Abilities so we have to do some basic string matching on land cards for now.
List<Ability> landAbilities = card.getAbilities();
int count = 0;
for(Ability ability : landAbilities) {
String abilityString = ability.getRule();
// Lands that tap to produce mana of the chosen colors
for(ColoredManaSymbol symbol : allowedColors) {
if(landTapsForAllowedColor(abilityString, symbol.toString())) {
count++;
}
}
if(count > 1) {
return true;
}
}
return false;
}
/**
* Returns if this land taps for the given color.
* Basic string matching to check the ability adds one of the chosen mana when tapped.
* @param ability MockAbility of the land card
* @param symbol colored mana symbol.
* @return if the ability is tapping to produce the mana the symbol represents.
*/
private boolean landTapsForAllowedColor(String ability, String symbol) {
return ability.matches(".*Add \\{" + symbol + "\\} to your mana pool.");
}
/**
* Returns if the symbol is a colored mana symbol.
* @param symbol the symbol to check.
* @return If it is a basic mana symbol or a hybrid mana symbol.
*/
private static boolean isColoredManaSymbol(String symbol) {
// Hybrid mana
if(symbol.contains("/")) {
return true;
}
for(ColoredManaSymbol c: ColoredManaSymbol.values()) {
if (symbol.charAt(0) == (c.toString().charAt(0))) {
return true;
}
}
return false;
}
/**
* Returns how many of this card is in the pool.
* If there are none in the pool it will initalise the card count.
* @param cardName the name of the card to check.
* @return the number of cards in the pool of this name.
*/
private int getCardCount(String cardName) {
Object cC = cardCounts.get((cardName));
if(cC == null)
cardCounts.put(cardName, 0);
return cardCounts.get((cardName));
}
}

View file

@ -64,7 +64,7 @@ import mage.client.cards.CardGrid;
import mage.client.cards.ICardGrid;
import mage.client.constants.Constants.SortBy;
import mage.client.deckeditor.table.TableModel;
import mage.client.util.sets.ConstructedFormats;
import mage.cards.ConstructedFormats;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;

View file

@ -45,7 +45,7 @@ import mage.client.MageFrame;
import mage.client.cards.BigCard;
import mage.client.dialog.PreferencesDialog;
import mage.client.plugins.impl.Plugins;
import mage.client.util.sets.ConstructedFormats;
import mage.cards.ConstructedFormats;
import org.apache.log4j.Logger;

View file

@ -43,7 +43,7 @@ import mage.client.util.audio.AudioManager;
import mage.client.util.Command;
import mage.client.util.Config;
import mage.client.util.ImageHelper;
import mage.client.util.sets.ConstructedFormats;
import mage.cards.ConstructedFormats;
import mage.components.ImagePanel;
import mage.constants.Rarity;
import mage.view.CardView;

View file

@ -38,8 +38,11 @@ import java.io.File;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import mage.cards.decks.Deck;
import mage.cards.decks.generator.DeckGenerator;
import mage.client.MageFrame;
import mage.client.deck.generator.DeckGenerator;
import mage.client.deck.generator.DeckGeneratorDialog;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.Config;
/**
@ -49,7 +52,7 @@ import mage.client.util.Config;
public class NewPlayerPanel extends javax.swing.JPanel {
private final JFileChooser fcSelectDeck;
/** Creates new form NewPlayerPanel */
public NewPlayerPanel() {
initComponents();
@ -94,7 +97,14 @@ public class NewPlayerPanel extends javax.swing.JPanel {
}
protected void generateDeck() {
String path = DeckGenerator.generateDeck();
DeckGeneratorDialog genDialog = new DeckGeneratorDialog();
Deck deck = DeckGenerator.generateDeck(genDialog.getOptions());
String path = genDialog.saveDeck(deck);
// If the deck couldn't be generated or the user cancelled, repopulate the deck selection with its cached value
if (path == null) {
path = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE, null);
}
if (path != null) {
this.txtPlayerDeck.setText(path);
MageFrame.getPreferences().put("defaultDeckPath", path);

View file

@ -1,235 +0,0 @@
package mage.client.util.sets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import mage.cards.repository.ExpansionInfo;
import mage.cards.repository.ExpansionRepository;
import mage.constants.SetType;
/**
* Utility class for constructed formats (expansions and other editions).
*
* @author nantuko
*/
public class ConstructedFormats {
private static final GregorianCalendar calendar = new GregorianCalendar();
public static final String ALL = "- All Sets";
public static final String STANDARD = "- Standard";
public static final String EXTENDED = "- Extended";
public static final String MODERN = "- Modern";
private static final Map<String, List<String>> underlyingSetCodesPerFormat = new HashMap<>();
private static final List<String> formats = new ArrayList<>();
private ConstructedFormats() {
}
public static String[] getTypes() {
return formats.toArray(new String[0]);
}
public static String getDefault() {
return STANDARD;
}
public static List<String> getSetsByFormat(final String format) {
if(!format.equals(ALL)) {
return underlyingSetCodesPerFormat.get(format);
}
return all;
}
private static void buildLists() {
GregorianCalendar cutoff;
// month is zero based so January = 0
if (calendar.get(Calendar.MONTH) > 8) {
cutoff = new GregorianCalendar(calendar.get(Calendar.YEAR) - 1, Calendar.SEPTEMBER, 1);
}
else {
cutoff = new GregorianCalendar(calendar.get(Calendar.YEAR) - 2, Calendar.SEPTEMBER, 1);
}
final Map<String, ExpansionInfo> expansionInfo = new HashMap<>();
for (ExpansionInfo set : ExpansionRepository.instance.getAll()) {
expansionInfo.put(set.getName(), set);
formats.add(set.getName());
if (set.getType().equals(SetType.CORE) || set.getType().equals(SetType.EXPANSION)) {
if (set.getReleaseDate().after(cutoff.getTime())) {
if(underlyingSetCodesPerFormat.get(STANDARD) == null) {
underlyingSetCodesPerFormat.put(STANDARD, new ArrayList<String>());
}
underlyingSetCodesPerFormat.get(STANDARD).add(set.getCode());
}
if (set.getReleaseDate().after(extendedDate)) {
if(underlyingSetCodesPerFormat.get(EXTENDED) == null) {
underlyingSetCodesPerFormat.put(EXTENDED, new ArrayList<String>());
}
underlyingSetCodesPerFormat.get(EXTENDED).add(set.getCode());
}
if (set.getReleaseDate().after(modernDate)) {
if(underlyingSetCodesPerFormat.get(MODERN) == null) {
underlyingSetCodesPerFormat.put(MODERN, new ArrayList<String>());
}
underlyingSetCodesPerFormat.get(MODERN).add(set.getCode());
}
}
if(underlyingSetCodesPerFormat.get(set.getName()) == null) {
underlyingSetCodesPerFormat.put(set.getName(), new ArrayList<String>());
}
underlyingSetCodesPerFormat.get(set.getName()).add(set.getCode());
if(set.getType().equals(SetType.EXPANSION) && set.getBlockName() != null) {
String blockDisplayName = getBlockDisplayName(set.getBlockName());
if(underlyingSetCodesPerFormat.get(blockDisplayName) == null) {
underlyingSetCodesPerFormat.put(blockDisplayName, new ArrayList<String>());
}
underlyingSetCodesPerFormat.get(blockDisplayName).add(set.getCode());
if(expansionInfo.get(blockDisplayName) == null) {
expansionInfo.put(blockDisplayName, set);
formats.add(blockDisplayName);
}
if(expansionInfo.get(blockDisplayName).getReleaseDate().after(set.getReleaseDate())) {
expansionInfo.put(blockDisplayName, set);
}
}
if(set.getType().equals(SetType.SUPPLEMENTAL) && set.getBlockName() != null) {
if(expansionInfo.get(set.getBlockName()) == null) {
expansionInfo.put(set.getBlockName(), set);
}
if(expansionInfo.get(set.getBlockName()).getReleaseDate().before(set.getReleaseDate())) {
expansionInfo.put(set.getBlockName(), set);
}
}
}
Collections.sort(formats, new Comparator<String>() {
@Override
public int compare(String name1, String name2) {
ExpansionInfo expansionInfo1 = expansionInfo.get(name1);
ExpansionInfo expansionInfo2 = expansionInfo.get(name2);
if(expansionInfo1.getType().compareTo(expansionInfo2.getType()) == 0) {
SetType setType = expansionInfo1.getType();
if(setType.equals(SetType.EXPANSION)) {
if(expansionInfo1.getBlockName() == null) {
if(expansionInfo2.getBlockName() == null) {
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
}
return 1;
}
if(expansionInfo2.getBlockName() == null) {
return -1;
}
//Block comparison
if(name1.endsWith("Block") && name2.endsWith("Block")) {
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
}
if(name1.endsWith("Block")) {
if(expansionInfo1.getBlockName().equals(expansionInfo2.getBlockName())) {
return -1;
}
}
if(name2.endsWith("Block")) {
if(expansionInfo1.getBlockName().equals(expansionInfo2.getBlockName())) {
return 1;
}
}
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
} else if(setType.equals(SetType.SUPPLEMENTAL)) {
if(expansionInfo1.getBlockName() == null) {
if(expansionInfo2.getBlockName() == null) {
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
}
return -1;
}
if(expansionInfo2.getBlockName() == null) {
return 1;
}
if(expansionInfo1.getBlockName().equals(expansionInfo2.getBlockName())) {
//If release date is the same, sort alphabetically.
if(expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate()) == 0) {
return name1.compareTo(name2);
}
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
}
if(expansionInfo1.getBlockName().startsWith("Duel Decks")) {
if(expansionInfo1.getBlockName().startsWith("Duel Decks: Anthology")) {
return 1;
}
return 1;
}
if(expansionInfo2.getBlockName().startsWith("Duel Decks")) {
return -1;
}
ExpansionInfo blockInfo1 = expansionInfo.get(expansionInfo1.getBlockName());
ExpansionInfo blockInfo2 = expansionInfo.get(expansionInfo2.getBlockName());
return blockInfo2.getReleaseDate().compareTo(blockInfo1.getReleaseDate());
} else {
return expansionInfo2.getReleaseDate().compareTo(expansionInfo1.getReleaseDate());
}
}
return expansionInfo1.getType().compareTo(expansionInfo2.getType());
}
});
formats.add(0, MODERN);
formats.add(0, EXTENDED);
formats.add(0, STANDARD);
formats.add(0, ALL);
}
private static String getBlockDisplayName(String blockName) {
StringBuilder builder = new StringBuilder();
builder.append("* ").append(blockName).append(" Block");
return builder.toString();
}
private static final Date extendedDate = new GregorianCalendar(2009, 8, 20).getTime();
private static final Date modernDate = new GregorianCalendar(2003, 7, 20).getTime();
// for all sets just return empty list
private static final List<String> all = new ArrayList<>();
static {
buildLists();
}
}

View file

@ -47,7 +47,7 @@ import javax.swing.JProgressBar;
import mage.cards.repository.CardInfo;
import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats;
import mage.cards.ConstructedFormats;
import mage.remote.Connection;
import static mage.remote.Connection.ProxyType.HTTP;
import static mage.remote.Connection.ProxyType.SOCKS;