diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java index 5f654e18b7..b7738c5a6f 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java @@ -155,7 +155,10 @@ public class DeckGenerator { * @return the final deck to use. */ private static Deck generateDeck(int deckSize, List allowedColors, List setsToUse) { - genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton(), genDialog.isColorless()); + + genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(), + genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(), + genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC()); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]); @@ -210,7 +213,7 @@ public class DeckGenerator { private static void generateSpells(CardCriteria criteria, int spellCount) { List cardPool = CardRepository.instance.findCards(criteria); int retrievedCount = cardPool.size(); - List deckCMCs = genPool.getCMCsForSpellCount(spellCount); + List deckCMCs = genPool.getCMCsForSpellCount(spellCount); Random random = new Random(); int count = 0; int reservesAdded = 0; @@ -221,7 +224,7 @@ public class DeckGenerator { Card card = cardPool.get(random.nextInt(retrievedCount)).getMockCard(); if (genPool.isValidSpellCard(card)) { int cardCMC = card.getManaCost().convertedManaCost(); - for (DeckGeneratorCMC deckCMC : deckCMCs) { + for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) { if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) { int currentAmount = deckCMC.getAmount(); if (currentAmount > 0) { @@ -339,8 +342,9 @@ public class DeckGenerator { * database. */ private static void addBasicLands(int landsNeeded, Map percentage, Map count, Map> basicLands) { + int colorTotal = 0; - ColoredManaSymbol colorToAdd = null; + ColoredManaSymbol colorToAdd = ColoredManaSymbol.U; // Add up the totals for all colors, to keep track of the percentage a color is. for (Map.Entry c : count.entrySet()) { @@ -372,12 +376,10 @@ public class DeckGenerator { minPercentage = (neededPercentage - thisPercentage); } } - if (colorToAdd != null) { - genPool.addCard(getBasicLand(colorToAdd, basicLands)); - count.put(colorToAdd.toString(), count.get(colorToAdd.toString()) + 1); - colorTotal++; - landsNeeded--; - } + genPool.addCard(getBasicLand(colorToAdd, basicLands)); + count.put(colorToAdd.toString(), count.get(colorToAdd.toString()) + 1); + colorTotal++; + landsNeeded--; } } diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java index 06ceac14c4..e56f2d8a61 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java @@ -27,44 +27,101 @@ */ 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; +import java.util.ArrayList; - /** - * 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; +public enum DeckGeneratorCMC { + + Low( + new ArrayList() {{ + add(new CMC(0, 2, 0.60f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 6, 0.10f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.65f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 5, 0.05f)); + }}), + Default( + new ArrayList() {{ + add(new CMC(0, 2, 0.20f)); + add(new CMC(3, 5, 0.50f)); + add(new CMC(6, 7, 0.25f)); + add(new CMC(8, 100, 0.05f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.30f)); + add(new CMC(3, 4, 0.45f)); + add(new CMC(5, 6, 0.20f)); + add(new CMC(7, 100, 0.05f)); + }}), + High( + new ArrayList() {{ + add(new CMC(0, 2, 0.05f)); + add(new CMC(3, 5, 0.35f)); + add(new CMC(6, 7, 0.40f)); + add(new CMC(8, 100, 0.15f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.10f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 6, 0.45f)); + add(new CMC(7, 100, 0.15f)); + }}); + + private ArrayList poolCMCs60, poolCMCs40; + + DeckGeneratorCMC(ArrayList CMCs60, ArrayList CMCs40) { + this.poolCMCs60 = CMCs60; + this.poolCMCs40 = CMCs40; } - /** - * 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; + public ArrayList get40CardPoolCMC() { + return this.poolCMCs40; } - /** - * 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; + public ArrayList get60CardPoolCMC() { + return this.poolCMCs60; } + + static class CMC + { + 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) + */ + CMC(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; + } + } + } + diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index 0c0ca15850..3d541a75eb 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -35,9 +35,14 @@ import mage.client.util.gui.ColorsChooser; import mage.client.util.sets.ConstructedFormats; import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; @@ -48,13 +53,14 @@ import java.util.Date; */ public class DeckGeneratorDialog { - private JDialog dlg; - private String selectedColors; - private JComboBox cbSets; - private JComboBox cbDeckSize; - private JButton btnGenerate, btnCancel; - private JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless; - private SimpleDateFormat dateFormat; + private static JDialog dlg; + private static String selectedColors; + private static JComboBox cbSets, cbDeckSize, cbCMC; + private static JButton btnGenerate, btnCancel, btnReset; + private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced; + private static JLabel averageCMCLabel; + private static SimpleDateFormat dateFormat; + private static RatioAdjustingSliderPanel adjustingSliderPanel; public DeckGeneratorDialog() { @@ -63,59 +69,92 @@ public class DeckGeneratorDialog { } 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); + JPanel mainPanel = new JPanel(); - p0.add(Box.createVerticalStrut(5)); + mainPanel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(0, 15, 0, 0); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.10; + JLabel text = new JLabel("Choose color for your deck:"); + mainPanel.add(text, c); + + // Color selector dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.80; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.gridx = 1; + c.gridy = 0; String chosen = MageFrame.getPreferences().get("genDeckColor", "u"); final ColorsChooser colorsChooser = new ColorsChooser(chosen); - p0.add(colorsChooser); + mainPanel.add(colorsChooser, c); - p0.add(Box.createVerticalStrut(5)); - JLabel text2 = new JLabel("(X - random color)"); - text2.setAlignmentX(Component.CENTER_ALIGNMENT); - p0.add(text2); + c.insets = new Insets(0, 15, 0, 0); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.10; + c.gridx = 2; + c.gridy = 0; + c.ipadx = 0; + JLabel text2 = new JLabel("(X = random color)"); + mainPanel.add(text2); - p0.add(Box.createVerticalStrut(5)); - JPanel jPanel = new JPanel(); - JLabel text3 = new JLabel("Choose sets:"); - cbSets = new JComboBox(ConstructedFormats.getTypes()); + // Format/set label + JLabel formatSetText = new JLabel("Choose format/set for your deck:"); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 1; + c.weightx = 0.10; + mainPanel.add(formatSetText, c); + + // Format/set dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 1; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.weightx = 0.90; + 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); + mainPanel.add(cbSets, c); 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(); + // Deck size label + c.fill = GridBagConstraints.HORIZONTAL; + c.insets = new Insets(0, 15, 0, 0); + c.ipadx = 0; + c.gridx = 0; + c.gridy = 2; + c.weightx = 0.10; JLabel textDeckSize = new JLabel("Deck size:"); - cbDeckSize = new JComboBox(new String[] { "40", "60" }); + mainPanel.add(textDeckSize, c); + + // Deck size dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 2; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.weightx = 0.90; + 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); + mainPanel.add(cbDeckSize, c); + 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(); + JPanel jCheckBoxes = new JPanel(new FlowLayout(FlowLayout.LEFT)); // Singletons cSingleton = new JCheckBox("Singleton", false); @@ -138,16 +177,47 @@ public class DeckGeneratorDialog { cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled)); jCheckBoxes.add(cNonBasicLands); - // Non-basic lands + // Colorless mana cColorless = new JCheckBox("Colorless mana", false); cColorless.setToolTipText("Allow cards with colorless mana cost."); String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false"); cColorless.setSelected(Boolean.valueOf(colorlessEnabled)); jCheckBoxes.add(cColorless); + c.ipadx = 0; + c.gridx = 0; + c.gridy = 3; + c.weightx = 1; + c.gridwidth = 3; + mainPanel.add(jCheckBoxes, c); + + // Create the advanced configuration panel + JPanel advancedPanel = createAdvancedPanel(); + + // Advanced checkbox (enable/disable advanced configuration) + cAdvanced = new JCheckBox("Advanced"); + cAdvanced.setToolTipText("Enable advanced configuration options"); + cAdvanced.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent itemEvent) { + boolean enable = cAdvanced.isSelected(); + enableAdvancedPanel(enable); + } + }); + + // Advanced Checkbox + String advancedSavedValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, "false"); + boolean advancedEnabled = Boolean.valueOf(advancedSavedValue); + enableAdvancedPanel(advancedEnabled); + cAdvanced.setSelected(advancedEnabled); + c.gridy = 4; + c.weightx = 0; + c.insets = new Insets(10, 15, 10, 0); + mainPanel.add(cAdvanced, c); + c.gridy = 5; + c.weightx = 1; + c.insets = new Insets(5, 10, 0, 5); + mainPanel.add(advancedPanel, c); - jCheckBoxes.setPreferredSize(new Dimension(450, 25)); - jCheckBoxes.setMaximumSize(new Dimension(450, 25)); - p0.add(jCheckBoxes); btnGenerate = new JButton("Ok"); btnGenerate.addActionListener(new ActionListener() { @@ -169,12 +239,95 @@ public class DeckGeneratorDialog { } }); JButton[] options = {btnGenerate, btnCancel}; - JOptionPane optionPane = new JOptionPane(p0, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]); - dlg = optionPane.createDialog("Generating deck"); + JOptionPane optionPane = new JOptionPane(mainPanel, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]); + dlg = optionPane.createDialog("Generating Deck"); + dlg.setResizable(false); dlg.setVisible(true); dlg.dispose(); } + private void enableAdvancedPanel(boolean enable) { + adjustingSliderPanel.setEnabled(enable); + btnReset.setEnabled(enable); + cbCMC.setEnabled(enable); + averageCMCLabel.setEnabled(enable); + } + + private JPanel createAdvancedPanel() { + + JPanel advancedPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + + // Average CMC Label + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.10; + averageCMCLabel = new JLabel("Average CMC:"); + advancedPanel.add(averageCMCLabel, c); + + // CMC selection dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.90; + c.gridx = 2; + c.gridy = 0; + cbCMC = new JComboBox<>(DeckGeneratorCMC.values()); + cbCMC.setSelectedItem(DeckGeneratorCMC.Default); + String cmcSelected = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED_CMC, DeckGeneratorCMC.Default.name()); + cbCMC.setSelectedItem(DeckGeneratorCMC.valueOf(cmcSelected)); + advancedPanel.add(cbCMC, c); + + // Advanced percentage sliders + c.fill = GridBagConstraints.HORIZONTAL; + c.ipady = 20; + c.ipadx = 40; + c.weightx = 1; + c.gridwidth = 3; + c.gridx = 0; + c.gridy = 1; + c.insets = new Insets(10, 0, 0, 0); + adjustingSliderPanel = new RatioAdjustingSliderPanel(); + + // Restore saved slider values + String creaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setCreaturePercentage(Integer.parseInt(creaturePercentage)); + String nonCreaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setNonCreaturePercentage(Integer.parseInt(nonCreaturePercentage)); + String landPercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE)); + adjustingSliderPanel.setLandPercentage(Integer.parseInt(landPercentage)); + advancedPanel.add(adjustingSliderPanel, c); + + // Reset + c.fill = GridBagConstraints.NONE; + c.ipadx = 0; + c.ipady = 0; + c.weightx = 1.0; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.insets = new Insets(10,10, 0, 0); + c.gridx = 2; + c.gridwidth = 1; + c.gridy = 2; + btnReset = new JButton("Reset"); + btnReset.setToolTipText("Reset advanced dialog to default values"); + btnReset.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + cbCMC.setSelectedItem(DeckGeneratorCMC.Default); + adjustingSliderPanel.resetValues(); + } + }); + advancedPanel.add(btnReset, c); + + // Add a border around the advanced bits + CompoundBorder border = BorderFactory.createCompoundBorder(new EtchedBorder(), new EmptyBorder(10, 10, 10, 10)); + advancedPanel.setBorder(border); + + return advancedPanel; + } + public void cleanUp() { for (ActionListener al: btnGenerate.getActionListeners()) { btnGenerate.removeActionListener(al); @@ -182,6 +335,12 @@ public class DeckGeneratorDialog { for (ActionListener al: btnCancel.getActionListeners()) { btnCancel.removeActionListener(al); } + for (ActionListener al: btnReset.getActionListeners()) { + btnReset.removeActionListener(al); + } + for(ItemListener il: cAdvanced.getItemListeners()) { + cAdvanced.removeItemListener(il); + } } public String saveDeck(Deck deck) { @@ -189,7 +348,7 @@ public class DeckGeneratorDialog { // Random directory through the system property to avoid random numeric string attached to temp files. String tempDir = System.getProperty("java.io.tmpdir"); // Generated deck has a nice unique name which corresponds to the timestamp at which it was created. - String deckName = "Generated-Deck-" + dateFormat.format( new Date()); + String deckName = "Generated-Deck-" + dateFormat.format(new Date()); File tmp = new File(tempDir + File.separator + deckName + ".dck"); tmp.createNewFile(); deck.setName(deckName); @@ -230,10 +389,42 @@ public class DeckGeneratorDialog { return selected; } + public boolean isAdvanced() { + boolean selected = cAdvanced.isSelected(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, Boolean.toString(selected)); + return selected; + } + + public int getCreaturePercentage() { + int percentage = adjustingSliderPanel.getCreaturePercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + + public int getNonCreaturePercentage() { + int percentage = adjustingSliderPanel.getNonCreaturePercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + + public int getLandPercentage() { + int percentage = adjustingSliderPanel.getLandPercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + public int getDeckSize() { return Integer.parseInt(cbDeckSize.getSelectedItem().toString()); } + public DeckGeneratorCMC getDeckGeneratorCMC() { + DeckGeneratorCMC selectedCMC = (DeckGeneratorCMC)cbCMC.getSelectedItem(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED_CMC, selectedCMC.name()); + return selectedCMC; + } + + + public String getSelectedColors() { if (selectedColors != null) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString()); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java index 849a31997d..7c7ff556f1 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java @@ -41,18 +41,15 @@ import java.util.*; */ 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; + + + public static int DEFAULT_CREATURE_PERCENTAGE = 38; + public static int DEFAULT_NON_CREATURE_PERCENTAGE = 21; + public static int DEFAULT_LAND_PERCENTAGE = 41; private final List allowedColors; private boolean colorlessAllowed; - private final List poolCMCs; + private final List poolCMCs; private final int creatureCount; private final int nonCreatureCount; private final int landCount; @@ -69,7 +66,21 @@ public class DeckGeneratorPool private List reserveSpells = new ArrayList<>(); private Deck deck; - public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton, boolean colorlessAllowed) + /** + * Creates a card pool with specified criterea used when generating a deck. + * + * @param deckSize the size of the complete deck + * @param creaturePercentage what percentage of creatures to use when generating the deck. + * @param nonCreaturePercentage percentage of non-creatures to use when generating the deck. + * @param landPercentage percentage of lands to use when generating the deck. + * @param allowedColors which card colors are allowed in the generated deck. + * @param isSingleton if the deck only has 1 copy of each non-land card. + * @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck. + * @param isAdvanced if the user has provided advanced options to generate the deck. + * @param deckGeneratorCMC the CMC curve to use for this deck + */ + public DeckGeneratorPool(final int deckSize, final int creaturePercentage, final int nonCreaturePercentage, final int landPercentage, + final List allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC) { this.deckSize = deckSize; this.allowedColors = allowedColors; @@ -78,28 +89,26 @@ public class DeckGeneratorPool 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() {{ - 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() {{ - 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)); - }}; + // Advanced (CMC Slider panel and curve drop-down in the dialog) + if(isAdvanced) { + this.creatureCount = (int)Math.ceil((deckSize / 100.0) * creaturePercentage); + this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0)* nonCreaturePercentage); + this.landCount = (int)Math.ceil((deckSize / 100.0)* landPercentage); + if(this.deckSize == 60) { + this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC(); + } else { + this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC(); + } + } else { + // Ignore the advanced group, just use defaults + this.creatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE); + this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE); + this.landCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE); + if(this.deckSize == 60) { + this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC(); + } else { + this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC(); + } } if(allowedColors.size() == 1) { @@ -114,16 +123,15 @@ public class DeckGeneratorPool * @param cardsCount the number of total cards. * @return a list of CMC ranges, with the amount of cards for each CMC range */ - public List getCMCsForSpellCount(int cardsCount) { - List adjustedCMCs = new ArrayList<>(this.poolCMCs); + public List getCMCsForSpellCount(int cardsCount) { + List 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) { + for(DeckGeneratorCMC.CMC 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. @@ -391,45 +399,54 @@ public class DeckGeneratorPool 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"); - } + // If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any. + if(reserveSpells.size() >= spellsNeeded) { - List spellsToAdd = new ArrayList<>(spellsNeeded); + List spellsToAdd = new ArrayList<>(spellsNeeded); - // Initial reservoir - for(int i = 0; i < spellsNeeded; i++) - spellsToAdd.add(reserveSpells.get(i)); + // 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); + 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); } - // 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. + // Check we have exactly the right amount of cards for a deck. if(deckCards.size() != nonLandSize) { - throw new IllegalStateException("Not enough cards found to generate deck. Please try again"); + throw new IllegalStateException("Not enough cards found to generate deck."); } - // Return the fixed amount return deckCards; } + /** + * 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 this land will produce the chosen colors for this pool. * @param card a non-basic land card. @@ -455,17 +472,6 @@ public class DeckGeneratorPool 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. diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java new file mode 100644 index 0000000000..665fdbbfef --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -0,0 +1,248 @@ +/* + * 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.client.deck.generator.DeckGeneratorPool; + +import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * @author Simown + */ +public class RatioAdjustingSliderPanel extends JPanel { + + private JStorageSlider creatureSlider, nonCreatureSlider, landSlider; + private List textLabels = new ArrayList<>(); + private AdjustingSliderGroup sg; + + private class JStorageSlider extends JSlider { + + // Slider stores its initial value to revert to when reset + private int defaultValue; + private int previousValue; + + public JStorageSlider(int min, int max, int value) { + super(min, max, value); + previousValue = value; + defaultValue = value; + setMinorTickSpacing(5); + setMajorTickSpacing(10); + setPaintTicks(true); + setPaintLabels(true); + setLabelTable(createStandardLabels(10)); + } + + public int getPreviousValue() { + return previousValue; + } + + public void setPreviousValue(int value) { + previousValue = value; + } + + public void resetDefault() { + this.setValue(defaultValue); + previousValue = defaultValue; + } + + } + + private class AdjustingSliderGroup + { + private final ArrayList storageSliders; + private int sliderIndex = 0; + + AdjustingSliderGroup(JStorageSlider... sliders) + { + storageSliders = new ArrayList<>(); + for(JStorageSlider slider: sliders) { + storageSliders.add(slider); + slider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + fireSliderChangedEvent((JStorageSlider) e.getSource()); + } + }); + } + } + public void fireSliderChangedEvent(JStorageSlider source) { + // We don't want to do anything if the value isn't changing + if(!source.getValueIsAdjusting()) + return; + // Update the slider depending on how much it's changed relative to its previous position + int change = (source.getValue() - source.getPreviousValue()); + updateSliderPosition(change, source); + } + + private void updateSliderPosition(int change, JStorageSlider source) { + int remaining = change; + while (remaining != 0) { + // Get the currently indexed slider + JStorageSlider slider = storageSliders.get(sliderIndex); + // If it's not the slider that fired the event + if (slider != source) { + // Check we don't go over the upper and lower bounds + if (remaining < 0 || (remaining > 0 && slider.getValue() > 0)) { + // Adjust the currently selected slider by +/- 1 + int adjustment = Integer.signum(remaining); + slider.setValue(slider.getValue() - adjustment); + remaining -= adjustment; + } + } + // Select the next slider in the list of sliders + sliderIndex = (sliderIndex + 1) % storageSliders.size(); + } + for (JStorageSlider slider : storageSliders) { + slider.setPreviousValue(slider.getValue()); + } + } + + List getSliders() { + return storageSliders; + } + + } + + public RatioAdjustingSliderPanel() { + initPanel(); + } + + private void initPanel() { + + // Create three sliders with default values + creatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE); + nonCreatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE); + landSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE); + + sg = new AdjustingSliderGroup(creatureSlider, nonCreatureSlider, landSlider); + + this.setLayout(new GridLayout(3, 1)); + + this.add(createSliderPanel("Creatures ", creatureSlider)); + this.add(createSliderPanel("Non-creatures ", nonCreatureSlider)); + this.add(createSliderPanel("Lands ", landSlider)); + + setEnabled(true); + } + + private JPanel createSliderPanel(String label, JStorageSlider slider) { + + JPanel sliderPanel = new JPanel(new BorderLayout()); + + // Title + JLabel titleLabel = new JLabel(label); + textLabels.add(titleLabel); + sliderPanel.add(titleLabel, BorderLayout.WEST); + // Slider + slider.setToolTipText("Percentage of " + label.trim().toLowerCase() + " in the generated deck."); + sliderPanel.add(slider, BorderLayout.CENTER); + // Percentage + JLabel percentageLabel = createChangingPercentageLabel(slider); + textLabels.add(percentageLabel); + sliderPanel.add(percentageLabel, BorderLayout.EAST); + + return sliderPanel; + } + + private static JLabel createChangingPercentageLabel(final JSlider slider) { + + final JLabel label = new JLabel(" " + String.valueOf(slider.getValue()) + "%"); + + slider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + String value = String.valueOf(slider.getValue()); + StringBuilder labelBuilder = new StringBuilder(); + // Pad with spaces so all percentage labels are of equal size + for(int i = 0; i < (5-value.length()); i++) { + labelBuilder.append(" "); + } + labelBuilder.append(value); + labelBuilder.append("%"); + label.setText(labelBuilder.toString()); + } + }); + return label; + } + + @Override + public void setEnabled(boolean enabled) { + for(JStorageSlider slider: sg.getSliders()) { + slider.setEnabled(enabled); + } + for(JLabel label: textLabels) { + label.setEnabled(enabled); + } + } + + public void resetValues() { + for(JStorageSlider slider: sg.getSliders()) { + slider.resetDefault(); + } + } + + public int getCreaturePercentage() { + return creatureSlider.getValue(); + } + + public int getNonCreaturePercentage() { + return nonCreatureSlider.getValue(); + } + + public int getLandPercentage() { + return landSlider.getValue(); + } + + public void setCreaturePercentage(int percentage) { + creatureSlider.setValue(percentage); + creatureSlider.previousValue = percentage; + } + + public void setNonCreaturePercentage(int percentage) { + nonCreatureSlider.setValue(percentage); + nonCreatureSlider.previousValue = percentage; + } + + public void setLandPercentage(int percentage) { + landSlider.setValue(percentage); + landSlider.previousValue = percentage; + } + + + + +} diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index abe9cea1d3..ca80f4c160 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -236,6 +236,12 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts"; public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands"; public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless"; + public static final String KEY_NEW_DECK_GENERATOR_ADVANCED = "newDeckGeneratorAdvanced"; + public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage"; + public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage"; + public static final String KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE = "newDeckGeneratorLandPercentage"; + public static final String KEY_NEW_DECK_GENERATOR_ADVANCED_CMC = "newDeckGeneratorAdvancedCMC"; + // used to save and restore the settings for the cardArea (draft, sideboarding, deck builder) public static final String KEY_DRAFT_VIEW = "draftView";