mirror of
https://github.com/correl/mage.git
synced 2025-01-12 19:25:44 +00:00
Overhaul and fix of random deck generation.
This commit is contained in:
parent
77b3706c9f
commit
a1fd4e91f0
5 changed files with 1013 additions and 322 deletions
|
@ -1,200 +1,91 @@
|
|||
/*
|
||||
* 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.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import mage.Mana;
|
||||
import java.util.*;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Sets;
|
||||
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.MageFrame;
|
||||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.client.util.gui.ColorsChooser;
|
||||
import mage.client.util.sets.ConstructedFormats;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.constants.Rarity;
|
||||
import mage.interfaces.rate.RateCallback;
|
||||
import mage.utils.DeckBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* Generates random card pool and builds a deck.
|
||||
*
|
||||
* @author nantuko
|
||||
* @author Simown
|
||||
*/
|
||||
public class DeckGenerator {
|
||||
|
||||
private static JDialog dlg;
|
||||
private static String selectedColors;
|
||||
private static JComboBox cbSets;
|
||||
private static JComboBox cbDeckSize;
|
||||
|
||||
private static final int SPELL_CARD_POOL_SIZE = 180;
|
||||
|
||||
private static final int DECK_LANDS = 17;
|
||||
private static final int MAX_NON_BASIC_SOURCE = DECK_LANDS / 2;
|
||||
|
||||
private static final int MAX_TRIES = 4096;
|
||||
|
||||
private static Deck deck = new Deck();
|
||||
private static final int ADDITIONAL_CARDS_FOR_3_COLOR_DECKS = 20;
|
||||
private static final int MAX_TRIES = 8196;
|
||||
private static DeckGeneratorDialog genDialog;
|
||||
private static DeckGeneratorPool genPool;
|
||||
|
||||
/**
|
||||
* Opens color chooser dialog. Generates deck.
|
||||
* Saves generated deck and use it as selected deck to play.
|
||||
*
|
||||
* @return
|
||||
* Builds a deck out of the selected block/set/format.
|
||||
* @return a path to the generated deck.
|
||||
*/
|
||||
public static String generateDeck() {
|
||||
JPanel p0 = new JPanel();
|
||||
p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS));
|
||||
|
||||
JLabel text = new JLabel("Choose color for your deck: ");
|
||||
text.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
p0.add(text);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
String chosen = MageFrame.getPreferences().get("genDeckColor", "u");
|
||||
final ColorsChooser colorsChooser = new ColorsChooser(chosen);
|
||||
p0.add(colorsChooser);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JLabel text2 = new JLabel("(X - random color)");
|
||||
text2.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
p0.add(text2);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JPanel jPanel = new JPanel();
|
||||
JLabel text3 = new JLabel("Choose sets:");
|
||||
cbSets = new JComboBox(ConstructedFormats.getTypes());
|
||||
cbSets.setSelectedIndex(0);
|
||||
cbSets.setPreferredSize(new Dimension(300, 25));
|
||||
cbSets.setMaximumSize(new Dimension(300, 25));
|
||||
cbSets.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
jPanel.add(text3);
|
||||
jPanel.add(cbSets);
|
||||
p0.add(jPanel);
|
||||
|
||||
String prefSet = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, null);
|
||||
if (prefSet != null) {
|
||||
cbSets.setSelectedItem(prefSet);
|
||||
genDialog = new DeckGeneratorDialog();
|
||||
if (genDialog.getSelectedColors() != null) {
|
||||
Deck deck = buildDeck();
|
||||
return genDialog.saveDeck(deck);
|
||||
}
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JPanel jPanel2 = new JPanel();
|
||||
JLabel textDeckSize = new JLabel("Deck size:");
|
||||
cbDeckSize = new JComboBox(new String[]{"40","60"});
|
||||
cbDeckSize.setSelectedIndex(0);
|
||||
cbDeckSize.setPreferredSize(new Dimension(300, 25));
|
||||
cbDeckSize.setMaximumSize(new Dimension(300, 25));
|
||||
cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
jPanel2.add(textDeckSize);
|
||||
jPanel2.add(cbDeckSize);
|
||||
p0.add(jPanel2);
|
||||
|
||||
String prefSize = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, "60");
|
||||
if (prefSet != null) {
|
||||
cbDeckSize.setSelectedItem(prefSize);
|
||||
}
|
||||
|
||||
final JButton btnGenerate = new JButton("Ok");
|
||||
btnGenerate.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
btnGenerate.setEnabled(false);
|
||||
colorsChooser.setEnabled(false);
|
||||
selectedColors = (String) colorsChooser.getSelectedItem();
|
||||
dlg.setVisible(false);
|
||||
MageFrame.getPreferences().put("genDeckColor", selectedColors);
|
||||
}
|
||||
});
|
||||
final JButton btnCancel = new JButton("Cancel");
|
||||
btnCancel.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
dlg.setVisible(false);
|
||||
selectedColors = null;
|
||||
}
|
||||
});
|
||||
Object[] options = {btnGenerate, btnCancel};
|
||||
JOptionPane optionPane = new JOptionPane(p0, JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
|
||||
dlg = optionPane.createDialog("Generating deck");
|
||||
dlg.setVisible(true);
|
||||
dlg.dispose();
|
||||
|
||||
if (selectedColors != null) {
|
||||
// save values to prefs
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString());
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, cbSets.getSelectedItem().toString());
|
||||
|
||||
// build deck
|
||||
buildDeck();
|
||||
try {
|
||||
File tmp = File.createTempFile("tempDeck" + UUID.randomUUID().toString(), ".dck");
|
||||
tmp.createNewFile();
|
||||
deck.setName("Generated-Deck-" + UUID.randomUUID());
|
||||
Sets.saveDeck(tmp.getAbsolutePath(), deck.getDeckCardLists());
|
||||
deck = null;
|
||||
//JOptionPane.showMessageDialog(null, "Deck has been generated.");
|
||||
DeckGenerator.cleanUp(btnGenerate, btnCancel);
|
||||
return tmp.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(null, "Couldn't generate deck. Try again.");
|
||||
}
|
||||
}
|
||||
DeckGenerator.cleanUp(btnGenerate, btnCancel);
|
||||
return selectedColors;
|
||||
// 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);
|
||||
}
|
||||
|
||||
private static void cleanUp(JButton btnGenerate, JButton btnCancel) {
|
||||
for (ActionListener al: btnGenerate.getActionListeners()) {
|
||||
btnGenerate.removeActionListener(al);
|
||||
}
|
||||
for (ActionListener al: btnCancel.getActionListeners()) {
|
||||
btnCancel.removeActionListener(al);
|
||||
}
|
||||
deck = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates card pool
|
||||
*/
|
||||
protected static void buildDeck() {
|
||||
List<ColoredManaSymbol> allowedColors = new ArrayList<ColoredManaSymbol>();
|
||||
|
||||
protected static Deck buildDeck() {
|
||||
|
||||
String selectedColors = genDialog.getSelectedColors();
|
||||
List<ColoredManaSymbol> allowedColors = new ArrayList<>();
|
||||
selectedColors = selectedColors != null ? selectedColors.toUpperCase() : getRandomColors("X");
|
||||
String format = genDialog.getSelectedFormat();
|
||||
|
||||
String format = (String) cbSets.getSelectedItem();
|
||||
List<String> setsToUse = ConstructedFormats.getSetsByFormat(format);
|
||||
if (setsToUse.isEmpty()) {
|
||||
// use all
|
||||
// Default to using all sets
|
||||
setsToUse = ExpansionRepository.instance.getSetCodes();
|
||||
}
|
||||
|
||||
int deckSize = Integer.parseInt(cbDeckSize.getSelectedItem().toString());
|
||||
if (deckSize < 40) {
|
||||
deckSize = 40;
|
||||
}
|
||||
int deckSize = genDialog.getDeckSize();
|
||||
|
||||
if (selectedColors.contains("X")) {
|
||||
selectedColors = getRandomColors(selectedColors);
|
||||
}
|
||||
|
@ -204,43 +95,26 @@ public class DeckGenerator {
|
|||
allowedColors.add(ColoredManaSymbol.lookup(c));
|
||||
}
|
||||
|
||||
int cardPoolSize = SPELL_CARD_POOL_SIZE;
|
||||
if (selectedColors.length() > 2) {
|
||||
cardPoolSize += ADDITIONAL_CARDS_FOR_3_COLOR_DECKS;
|
||||
}
|
||||
List<Card> spellCardPool = generateSpellCardPool(cardPoolSize, allowedColors, setsToUse);
|
||||
List<Card> landCardPool = generateNonBasicLandCardPool(MAX_NON_BASIC_SOURCE, allowedColors, setsToUse);
|
||||
|
||||
// System.out.println("deck generator card pool: spells=" + spellCardPool.size() + ", lands=" + landCardPool.size());
|
||||
|
||||
final List<String> setsToUseFinal = setsToUse;
|
||||
|
||||
deck = DeckBuilder.buildDeck(spellCardPool, allowedColors, setsToUseFinal, landCardPool, deckSize, new RateCallback() {
|
||||
@Override
|
||||
public int rateCard(Card card) {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card getBestBasicLand(ColoredManaSymbol color, List<String> setsToUse) {
|
||||
return DeckGenerator.getBestBasicLand(color, setsToUseFinal);
|
||||
}
|
||||
});
|
||||
return generateDeck(deckSize, allowedColors, setsToUse);
|
||||
}
|
||||
|
||||
private static String getRandomColors(String _selectedColors) {
|
||||
/**
|
||||
* 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();
|
||||
availableColors.add('R');
|
||||
availableColors.add('G');
|
||||
availableColors.add('B');
|
||||
availableColors.add('U');
|
||||
availableColors.add('W');
|
||||
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);
|
||||
for (int i = 0; i < selectedColors.length(); i++) {
|
||||
char currentColor = selectedColors.charAt(i);
|
||||
if (currentColor != 'X') {
|
||||
generatedColors.append(currentColor);
|
||||
availableColors.remove(new Character(currentColor));
|
||||
|
@ -248,165 +122,168 @@ public class DeckGenerator {
|
|||
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 card pool of cardsCount cards that have manacost of allowed colors.
|
||||
*
|
||||
* @param cardsCount
|
||||
* @param allowedColors
|
||||
* @return
|
||||
* 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 List<Card> generateSpellCardPool(int cardsCount, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
|
||||
List<Card> spellCardPool = new ArrayList<Card>();
|
||||
private static Deck generateDeck(int deckSize, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
|
||||
genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton());
|
||||
|
||||
CardCriteria spellCriteria = new CardCriteria();
|
||||
spellCriteria.setCodes(setsToUse.toArray(new String[0]));
|
||||
spellCriteria.notTypes(CardType.LAND);
|
||||
final String[] sets = setsToUse.toArray(new String[setsToUse.size()]);
|
||||
|
||||
List<CardInfo> cardPool = CardRepository.instance.findCards(spellCriteria);
|
||||
int cardPoolCount = cardPool.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();
|
||||
if (cardPoolCount > 0) {
|
||||
int count = 0;
|
||||
int reservesAdded = 0;
|
||||
if (retrievedCount > 0 && retrievedCount >= spellCount) {
|
||||
int tries = 0;
|
||||
int count = 0;
|
||||
while (count < cardsCount) {
|
||||
Card card = cardPool.get(random.nextInt(cardPoolCount)).getMockCard();
|
||||
if (cardFitsChosenColors(card, allowedColors)) {
|
||||
spellCardPool.add(card);
|
||||
count++;
|
||||
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) {
|
||||
genPool.tryAddReserve(card, cardCMC);
|
||||
reservesAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tries++;
|
||||
if (tries > MAX_TRIES) { // to avoid infinite loop
|
||||
throw new IllegalStateException("Not enough cards for chosen colors to generate deck: " + selectedColors);
|
||||
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.");
|
||||
}
|
||||
|
||||
return spellCardPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that card can be played using chosen (allowed) colors.
|
||||
*
|
||||
* @param card
|
||||
* @param allowedColors
|
||||
* @return
|
||||
* 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 boolean cardFitsChosenColors(Card card, List<ColoredManaSymbol> allowedColors) {
|
||||
for (String symbol : card.getManaCost().getSymbols()) {
|
||||
boolean found = false;
|
||||
symbol = symbol.replace("{", "").replace("}", "");
|
||||
if (isColoredMana(symbol)) {
|
||||
for (ColoredManaSymbol allowed : allowedColors) {
|
||||
if (symbol.contains(allowed.toString())) {
|
||||
found = true;
|
||||
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 coloured mana symbols over all spells in the deck
|
||||
Map<String, Double> percentage = genPool.calculateSpellColourPercentages();
|
||||
|
||||
// Only dual/tri colour lands are generated for now, and not non-basic lands that only produce colourless 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;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
// Calculate the amount of coloured 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates card pool of land cards that can produce allowed colors.
|
||||
*
|
||||
* @param landsCount
|
||||
* @param allowedColors
|
||||
* @return
|
||||
* 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 List<Card> generateNonBasicLandCardPool(int landsCount, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
|
||||
List<Card> nonBasicLandCardPool = new ArrayList<Card>();
|
||||
private static Map<String, List<CardInfo>> generateBasicLands(List<String> setsToUse) {
|
||||
|
||||
CardCriteria landCriteria = new CardCriteria();
|
||||
landCriteria.setCodes(setsToUse.toArray(new String[0]));
|
||||
landCriteria.types(CardType.LAND);
|
||||
landCriteria.notSupertypes("Basic");
|
||||
List<CardInfo> landCards = CardRepository.instance.findCards(landCriteria);
|
||||
|
||||
int allCount = landCards.size();
|
||||
Random random = new Random();
|
||||
if (allCount > 0) {
|
||||
int tries = 0;
|
||||
int count = 0;
|
||||
while (count < landsCount) {
|
||||
Card card = landCards.get(random.nextInt(allCount)).getMockCard();
|
||||
if (cardCardProduceChosenColors(card, allowedColors)) {
|
||||
nonBasicLandCardPool.add(card);
|
||||
count++;
|
||||
}
|
||||
tries++;
|
||||
if (tries > MAX_TRIES) { // to avoid infinite loop
|
||||
// return what have been found
|
||||
return nonBasicLandCardPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nonBasicLandCardPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that chosen card can produce mana of specific color.
|
||||
*
|
||||
* @param card
|
||||
* @param allowedColors
|
||||
* @return
|
||||
*/
|
||||
private static boolean cardCardProduceChosenColors(Card card, List<ColoredManaSymbol> allowedColors) {
|
||||
int score = 0;
|
||||
for (Mana mana : card.getMana()) {
|
||||
for (ColoredManaSymbol color : allowedColors) {
|
||||
score += mana.getColor(color);
|
||||
}
|
||||
}
|
||||
if (score > 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random basic land that can produce specified color mana.
|
||||
* Random here means random set and collector id for the same mana producing land.
|
||||
*
|
||||
* @param color
|
||||
* @return
|
||||
*/
|
||||
private static Card getBestBasicLand(ColoredManaSymbol color, List<String> setsToUse) {
|
||||
String cardName = "";
|
||||
switch(color) {
|
||||
case G:
|
||||
cardName = "Forest";
|
||||
break;
|
||||
case W:
|
||||
cardName = "Plains";
|
||||
break;
|
||||
case R:
|
||||
cardName = "Mountain";
|
||||
break;
|
||||
case B:
|
||||
cardName = "Swamp";
|
||||
break;
|
||||
case U:
|
||||
cardName = "Island";
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> landSets = new LinkedList<String>();
|
||||
List<String> landSets = new LinkedList<>();
|
||||
|
||||
// decide from which sets basic lands are taken from
|
||||
for (String setCode :setsToUse) {
|
||||
|
@ -447,19 +324,78 @@ public class DeckGenerator {
|
|||
if (!landSets.isEmpty()) {
|
||||
criteria.setCodes(landSets.toArray(new String[landSets.size()]));
|
||||
}
|
||||
criteria.rarities(Rarity.LAND).name(cardName);
|
||||
List<CardInfo> cards = CardRepository.instance.findCards(criteria);
|
||||
|
||||
if (cards.isEmpty() && !setsToUse.isEmpty()) {
|
||||
cards = CardRepository.instance.findCards(cardName);
|
||||
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 colourToAdd = 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();
|
||||
}
|
||||
|
||||
int randomInt = new Random().nextInt(cards.size());
|
||||
return cards.get(randomInt).getMockCard();
|
||||
// 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 colour, 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 colour
|
||||
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
|
||||
colourToAdd = color;
|
||||
minPercentage = (neededPercentage - thisPercentage);
|
||||
}
|
||||
}
|
||||
if(colourToAdd != null) {
|
||||
genPool.addCard(getBasicLand(colourToAdd, basicLands));
|
||||
count.put(colourToAdd.toString(), count.get(colourToAdd.toString()) + 1);
|
||||
colorTotal++;
|
||||
landsNeeded--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean isColoredMana(String symbol) {
|
||||
return symbol.equals("W") || symbol.equals("G") || symbol.equals("U") || symbol.equals("B") || symbol.equals("R") || symbol.contains("/");
|
||||
/**
|
||||
* 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());
|
||||
return basicLands.get(landName).get(random.nextInt(basicLands.size() - 1)).getMockCard().copy();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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.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.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Simown
|
||||
*/
|
||||
public class DeckGeneratorDialog {
|
||||
|
||||
private static JDialog dlg;
|
||||
private static String selectedColors;
|
||||
private static JComboBox cbSets;
|
||||
private static JComboBox cbDeckSize;
|
||||
private static JButton btnGenerate, btnCancel;
|
||||
private static JCheckBox cArtifacts, cSingleton, cNonBasicLands;
|
||||
|
||||
public DeckGeneratorDialog()
|
||||
{
|
||||
initDialog();
|
||||
}
|
||||
|
||||
private void initDialog() {
|
||||
JPanel p0 = new JPanel();
|
||||
p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS));
|
||||
|
||||
JLabel text = new JLabel("Choose color for your deck: ");
|
||||
text.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
p0.add(text);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
String chosen = MageFrame.getPreferences().get("genDeckColor", "u");
|
||||
final ColorsChooser colorsChooser = new ColorsChooser(chosen);
|
||||
p0.add(colorsChooser);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JLabel text2 = new JLabel("(X - random color)");
|
||||
text2.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
p0.add(text2);
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JPanel jPanel = new JPanel();
|
||||
JLabel text3 = new JLabel("Choose sets:");
|
||||
cbSets = new JComboBox(ConstructedFormats.getTypes());
|
||||
cbSets.setSelectedIndex(0);
|
||||
cbSets.setPreferredSize(new Dimension(300, 25));
|
||||
cbSets.setMaximumSize(new Dimension(300, 25));
|
||||
cbSets.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
jPanel.add(text3);
|
||||
jPanel.add(cbSets);
|
||||
p0.add(jPanel);
|
||||
|
||||
String prefSet = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, null);
|
||||
if (prefSet != null) {
|
||||
cbSets.setSelectedItem(prefSet);
|
||||
}
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JPanel jPanel2 = new JPanel();
|
||||
JLabel textDeckSize = new JLabel("Deck size:");
|
||||
cbDeckSize = new JComboBox(new String[]{"40","60"});
|
||||
cbDeckSize.setSelectedIndex(0);
|
||||
cbDeckSize.setPreferredSize(new Dimension(300, 25));
|
||||
cbDeckSize.setMaximumSize(new Dimension(300, 25));
|
||||
cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
jPanel2.add(textDeckSize);
|
||||
jPanel2.add(cbDeckSize);
|
||||
p0.add(jPanel2);
|
||||
|
||||
String prefSize = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, "60");
|
||||
if (prefSet != null) {
|
||||
cbDeckSize.setSelectedItem(prefSize);
|
||||
}
|
||||
|
||||
p0.add(Box.createVerticalStrut(5));
|
||||
JPanel jCheckBoxes = new JPanel();
|
||||
|
||||
// Singletons
|
||||
cSingleton = new JCheckBox("Singleton", false);
|
||||
cSingleton.setToolTipText("Allow only a single copy of each non-land card in your deck.");
|
||||
String singletonEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SINGLETON, "false");
|
||||
cSingleton.setSelected(Boolean.valueOf(singletonEnabled));
|
||||
jCheckBoxes.add(cSingleton);
|
||||
|
||||
// Artifacts
|
||||
cArtifacts = new JCheckBox("Artifacts", false);
|
||||
cArtifacts.setToolTipText("Use artifacts and artifact creatures in your deck.");
|
||||
String artifactEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, "false");
|
||||
cArtifacts.setSelected(Boolean.valueOf(artifactEnabled));
|
||||
jCheckBoxes.add(cArtifacts);
|
||||
|
||||
// Non-basic lands
|
||||
cNonBasicLands = new JCheckBox("Non-basic Lands", false);
|
||||
cNonBasicLands.setToolTipText("Use non-basic lands in your deck (if applicable).");
|
||||
String nonBasicEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS, "false");
|
||||
cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled));
|
||||
jCheckBoxes.add(cNonBasicLands);
|
||||
|
||||
jCheckBoxes.setPreferredSize(new Dimension(300, 25));
|
||||
jCheckBoxes.setMaximumSize(new Dimension(300, 25));
|
||||
p0.add(jCheckBoxes);
|
||||
|
||||
btnGenerate = new JButton("Ok");
|
||||
btnGenerate.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
btnGenerate.setEnabled(false);
|
||||
colorsChooser.setEnabled(false);
|
||||
selectedColors = (String) colorsChooser.getSelectedItem();
|
||||
dlg.setVisible(false);
|
||||
MageFrame.getPreferences().put("genDeckColor", selectedColors);
|
||||
}
|
||||
});
|
||||
btnCancel = new JButton("Cancel");
|
||||
btnCancel.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
dlg.setVisible(false);
|
||||
selectedColors = null;
|
||||
}
|
||||
});
|
||||
JButton[] options = {btnGenerate, btnCancel};
|
||||
JOptionPane optionPane = new JOptionPane(p0, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]);
|
||||
dlg = optionPane.createDialog("Generating deck");
|
||||
dlg.setVisible(true);
|
||||
dlg.dispose();
|
||||
}
|
||||
|
||||
public static void cleanUp() {
|
||||
for (ActionListener al: btnGenerate.getActionListeners()) {
|
||||
btnGenerate.removeActionListener(al);
|
||||
}
|
||||
for (ActionListener al: btnCancel.getActionListeners()) {
|
||||
btnCancel.removeActionListener(al);
|
||||
}
|
||||
//deck = null;
|
||||
}
|
||||
|
||||
public String saveDeck(Deck deck) {
|
||||
try {
|
||||
File tmp = File.createTempFile("tempDeck" + UUID.randomUUID().toString(), ".dck");
|
||||
tmp.createNewFile();
|
||||
deck.setName("Generated-Deck-" + UUID.randomUUID());
|
||||
Sets.saveDeck(tmp.getAbsolutePath(), deck.getDeckCardLists());
|
||||
DeckGeneratorDialog.cleanUp();
|
||||
return tmp.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(null, "Couldn't generate deck. Try again.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSelectedFormat() {
|
||||
return (String) cbSets.getSelectedItem();
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
boolean selected = cSingleton.isSelected();
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SINGLETON, Boolean.toString(selected));
|
||||
return selected;
|
||||
}
|
||||
|
||||
public boolean useArtifacts() {
|
||||
boolean selected = cArtifacts.isSelected();
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, Boolean.toString(selected));
|
||||
return selected;
|
||||
}
|
||||
|
||||
public boolean useNonBasicLand() {
|
||||
boolean selected = cNonBasicLands.isSelected();
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS, Boolean.toString(selected));
|
||||
return selected;
|
||||
}
|
||||
|
||||
public int getDeckSize() {
|
||||
return Integer.parseInt(cbDeckSize.getSelectedItem().toString());
|
||||
}
|
||||
|
||||
public String getSelectedColors() {
|
||||
if (selectedColors != null) {
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString());
|
||||
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, cbSets.getSelectedItem().toString());
|
||||
}
|
||||
return selectedColors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
* 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 colour 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.5f));
|
||||
}};
|
||||
|
||||
}
|
||||
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.5f));
|
||||
}};
|
||||
}
|
||||
|
||||
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 void 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> calculateSpellColourPercentages() {
|
||||
|
||||
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 colour has out of the total colour 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 colour.
|
||||
* @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;
|
||||
List<Card> spellsToAdd = new ArrayList<>(spellsNeeded);
|
||||
|
||||
// Initial reservoir
|
||||
for(int i = 0; i < spellsNeeded-1; i++)
|
||||
spellsToAdd.add(reserveSpells.get(i));
|
||||
|
||||
for(int j = spellsNeeded+1; j < reserveSpells.size()-1; j++) {
|
||||
int index = random.nextInt(j);
|
||||
Card randomCard = reserveSpells.get(index);
|
||||
if (index < j && isValidSpellCard(randomCard)) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -210,6 +210,9 @@ public class PreferencesDialog extends javax.swing.JDialog {
|
|||
// pref setting for deck generator
|
||||
public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize";
|
||||
public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet";
|
||||
public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton";
|
||||
public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts";
|
||||
public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands";
|
||||
|
||||
// used to save and restore the settings for the cardArea (draft, sideboarding, deck builder)
|
||||
public static final String KEY_DRAFT_VIEW = "draftView";
|
||||
|
|
Loading…
Reference in a new issue