From f237a8d29aa8b1af70b623c5dc936da97d3143b7 Mon Sep 17 00:00:00 2001 From: austinio7116 Date: Sun, 18 Feb 2018 18:05:08 +0000 Subject: [PATCH] Addition of new new random commander deck generation - both fully random decks based on a selected commander and matrix-based synergistic deck generation using a model learned from thousands of commander decks. This also includes support for commander net-decks on desktop. --- .../deck/generation/DeckGenerator4Color.java | 134 +++ .../java/forge/deckchooser/DecksComboBox.java | 8 +- .../java/forge/deckchooser/FDeckChooser.java | 69 +- .../main/java/forge/screens/home/CLobby.java | 26 +- .../main/java/forge/screens/home/VLobby.java | 119 ++- .../home/gauntlet/VSubmenuGauntletBuild.java | 3 +- .../gauntlet/VSubmenuGauntletContests.java | 3 +- .../home/gauntlet/VSubmenuGauntletQuick.java | 3 +- .../src/forge/deck/FDeckChooser.java | 10 + .../deck/CardRelationMatrixGenerator.java | 111 ++- .../forge/deck/CardThemedDeckGenerator.java | 13 +- .../forge/deck/CommanderDeckGenerator.java | 103 +++ .../src/main/java/forge/deck/DeckType.java | 12 + .../src/main/java/forge/deck/DeckgenUtil.java | 82 +- .../forge/deck/io/CardThemedMatrixIO.java | 4 +- .../CardThemedCommanderDeckBuilder.java | 828 ++++++++++++++++++ 16 files changed, 1459 insertions(+), 69 deletions(-) create mode 100644 forge-core/src/main/java/forge/deck/generation/DeckGenerator4Color.java create mode 100644 forge-gui/src/main/java/forge/deck/CommanderDeckGenerator.java create mode 100644 forge-gui/src/main/java/forge/limited/CardThemedCommanderDeckBuilder.java diff --git a/forge-core/src/main/java/forge/deck/generation/DeckGenerator4Color.java b/forge-core/src/main/java/forge/deck/generation/DeckGenerator4Color.java new file mode 100644 index 00000000000..1a0e0b36c94 --- /dev/null +++ b/forge-core/src/main/java/forge/deck/generation/DeckGenerator4Color.java @@ -0,0 +1,134 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.deck.generation; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.deck.CardPool; +import forge.deck.DeckFormat; +import forge.item.PaperCard; +import forge.util.MyRandom; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.List; + +/** + *

+ * Generate3ColorDeck class. + *

+ * + * @author Forge + * @version $Id$ + */ +public class DeckGenerator4Color extends DeckGeneratorBase { + @Override + protected final float getLandPercentage() { + return 0.44f; + } + @Override + protected final float getCreaturePercentage() { + return 0.33f; + } + @Override + protected final float getSpellPercentage() { + return 0.23f; + } + + @SuppressWarnings("unchecked") + final List> cmcLevels = Lists.newArrayList( + ImmutablePair.of(new FilterCMC(0, 2), 12), + ImmutablePair.of(new FilterCMC(3, 5), 9), + ImmutablePair.of(new FilterCMC(6, 20), 3) + ); + + public DeckGenerator4Color(IDeckGenPool pool0, DeckFormat format0, Predicate formatFilter0, final String clr1, final String clr2, final String clr3, final String clr4) { + super(pool0, format0, formatFilter0); + initialize(format0,clr1,clr2,clr3,clr4); + } + + public DeckGenerator4Color(IDeckGenPool pool0, DeckFormat format0, final String clr1, final String clr2, final String clr3, final String clr4) { + super(pool0, format0); + initialize(format0,clr1,clr2,clr3,clr4); + } + + private void initialize(DeckFormat format0, final String clr1, final String clr2, final String clr3, final String clr4){ + format0.adjustCMCLevels(cmcLevels); + + int c1 = MagicColor.fromName(clr1); + int c2 = MagicColor.fromName(clr2); + int c3 = MagicColor.fromName(clr3); + int c4 = MagicColor.fromName(clr4); + + int rc = 0; + int combo = c1 | c2 | c3 | c4; + + ColorSet param = ColorSet.fromMask(combo); + switch(param.countColors()) { + case 3: + colors = param; + return; + + case 0: + int color1 = r.nextInt(5); + int color2 = (color1 + 1 + r.nextInt(4)) % 5; + colors = ColorSet.fromMask(MagicColor.WHITE << color1 | MagicColor.WHITE << color2).inverse(); + return; + + case 1: + do { + rc = MagicColor.WHITE << MyRandom.getRandom().nextInt(5); + } while ( rc == combo ); + combo |= rc; + + //$FALL-THROUGH$ + case 2: + do { + rc = MagicColor.WHITE << MyRandom.getRandom().nextInt(5); + } while ( (rc & combo) != 0 ); + combo |= rc; + break; + } + colors = ColorSet.fromMask(combo); + } + + @Override + public final CardPool getDeck(final int size, final boolean forAi) { + addCreaturesAndSpells(size, cmcLevels, forAi); + + // Add lands + int numLands = Math.round(size * getLandPercentage()); + adjustDeckSize(size - numLands); + trace.append("numLands:").append(numLands).append("\n"); + + // Add dual lands + List duals = getDualLandList(); + for (String s : duals) { + this.cardCounts.put(s, 0); + } + + int dblsAdded = addSomeStr((numLands / 4), duals); + numLands -= dblsAdded; + + addBasicLand(numLands); + adjustDeckSize(size); + trace.append("DeckSize:").append(tDeck.countAll()).append("\n"); + return tDeck; + } +} diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java b/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java index 82385e0bdc4..3d40e0efac1 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/DecksComboBox.java @@ -25,8 +25,12 @@ public class DecksComboBox extends FComboBoxWrapper { addActionListener(getDeckTypeComboListener()); } - public void refresh(final DeckType deckType) { - setModel(new DefaultComboBoxModel(DeckType.ConstructedOptions)); + public void refresh(final DeckType deckType, final boolean isForCommander) { + if(isForCommander){ + setModel(new DefaultComboBoxModel(DeckType.CommanderOptions)); + }else { + setModel(new DefaultComboBoxModel(DeckType.ConstructedOptions)); + } setSelectedItem(deckType); } diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java index 42fa4a971c7..cb905b96d8d 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java @@ -43,6 +43,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { private ItemManagerContainer lstDecksContainer; private NetDeckCategory netDeckCategory; private boolean refreshingDeckType; + private boolean isForCommander; private final DeckManager lstDecks; private final FLabel btnViewDeck = new FLabel.ButtonBuilder().text("View Deck").fontSize(14).build(); @@ -56,7 +57,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { //Show dialog to select a deck public static Deck promptForDeck(final CDetailPicture cDetailPicture, final String title, final DeckType defaultDeckType, final boolean forAi) { FThreads.assertExecutedByEdt(true); - final FDeckChooser chooser = new FDeckChooser(cDetailPicture, forAi); + final FDeckChooser chooser = new FDeckChooser(cDetailPicture, forAi, GameType.Constructed, false); chooser.initialize(defaultDeckType); chooser.populate(); final Dimension parentSize = JOptionPane.getRootFrame().getSize(); @@ -78,10 +79,11 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return null; } - public FDeckChooser(final CDetailPicture cDetailPicture, final boolean forAi) { - lstDecks = new DeckManager(GameType.Constructed, cDetailPicture); + public FDeckChooser(final CDetailPicture cDetailPicture, final boolean forAi, GameType gameType, boolean forCommander) { + lstDecks = new DeckManager(gameType, cDetailPicture); setOpaque(false); isAi = forAi; + isForCommander = forCommander; final UiCommand cmdViewDeck = new UiCommand() { @Override public void run() { if (selectedDeckType != DeckType.COLOR_DECK && selectedDeckType != DeckType.THEME_DECK) { @@ -168,6 +170,49 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { lstDecks.setSelectedIndices(new Integer[]{0}); } + private void updateRandomCommander() { + if((!lstDecks.getGameType().getDeckFormat().equals(DeckFormat.Commander)&& + !(lstDecks.getGameType().getDeckFormat().equals(DeckFormat.TinyLeaders)))){ + return; + } + lstDecks.setAllowMultipleSelections(false); + + lstDecks.setPool(CommanderDeckGenerator.getCommanderDecks(lstDecks.getGameType().getDeckFormat(), isAi, false)); + lstDecks.setup(ItemManagerConfig.STRING_ONLY); + + btnRandom.setText("Random"); + btnRandom.setCommand(new UiCommand() { + @Override + public void run() { + DeckgenUtil.randomSelect(lstDecks); + } + }); + + // default selection = basic two color deck + lstDecks.setSelectedIndices(new Integer[]{0}); + } + + private void updateRandomCardGenCommander() { + if((!lstDecks.getGameType().getDeckFormat().equals(DeckFormat.Commander)&& + !(lstDecks.getGameType().getDeckFormat().equals(DeckFormat.TinyLeaders)))){ + return; + } + lstDecks.setAllowMultipleSelections(false); + lstDecks.setPool(CommanderDeckGenerator.getCommanderDecks(lstDecks.getGameType().getDeckFormat(), isAi, true)); + lstDecks.setup(ItemManagerConfig.STRING_ONLY); + + btnRandom.setText("Random"); + btnRandom.setCommand(new UiCommand() { + @Override + public void run() { + DeckgenUtil.randomSelect(lstDecks); + } + }); + + // default selection = basic two color deck + lstDecks.setSelectedIndices(new Integer[]{0}); + } + private void updateThemes() { updateDecks(DeckProxy.getAllThemeDecks(), ItemManagerConfig.STRING_ONLY); } @@ -250,7 +295,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { @Override public void deckTypeSelected(final DecksComboBoxEvent ev) { - if (ev.getDeckType() == DeckType.NET_DECK && !refreshingDeckType) { + if ((ev.getDeckType() == DeckType.NET_DECK || ev.getDeckType() == DeckType.NET_COMMANDER_DECK) && !refreshingDeckType) { FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override public void run() { @@ -268,7 +313,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { } netDeckCategory = category; - refreshDecksList(DeckType.NET_DECK, true, ev); + refreshDecksList(ev.getDeckType(), true, ev); } }); } @@ -285,7 +330,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { if (ev == null) { refreshingDeckType = true; - decksComboBox.refresh(deckType); + decksComboBox.refresh(deckType, isForCommander); refreshingDeckType = false; } lstDecks.setCaption(deckType.toString()); @@ -294,6 +339,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { case CUSTOM_DECK: updateCustom(); break; + case COMMANDER_DECK: + updateCustom(); + break; case COLOR_DECK: updateColors(null); break; @@ -309,6 +357,12 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { case MODERN_CARDGEN_DECK: updateMatrix(FModel.getFormats().getModern()); break; + case RANDOM_COMMANDER_DECK: + updateRandomCommander(); + break; + case RANDOM_CARDGEN_COMMANDER_DECK: + updateRandomCardGenCommander(); + break; case THEME_DECK: updateThemes(); break; @@ -324,6 +378,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { case NET_DECK: updateNetDecks(); break; + case NET_COMMANDER_DECK: + updateNetDecks(); + break; default: break; //other deck types not currently supported here } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java index 20b8b04390d..1513b4d27e8 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java @@ -8,6 +8,7 @@ import javax.swing.SwingUtilities; import com.google.common.collect.Iterables; +import forge.deck.CommanderDeckGenerator; import forge.deck.DeckProxy; import forge.deck.DeckType; import forge.deckchooser.DecksComboBoxEvent; @@ -23,6 +24,7 @@ public class CLobby { private final VLobby view; public CLobby(final VLobby view) { this.view = view; + this.view.setForCommander(true); } private void addDecks(final Iterable commanderDecks, FList deckList, String... initialItems) { @@ -56,8 +58,8 @@ public class CLobby { final Iterable planarDecks = DeckProxy.getAllPlanarDecks(); for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { - addDecks(commanderDecks, view.getCommanderDeckLists().get(i)); - addDecks(tinyLeadersDecks, view.getTinyLeadersDeckLists().get(i)); + //addDecks(commanderDecks, view.getCommanderDeckLists().get(i)); + //addDecks(tinyLeadersDecks, view.getTinyLeadersDeckLists().get(i)); addDecks(schemeDecks, view.getSchemeDeckLists().get(i), "Use deck's scheme section (random if unavailable)"); addDecks(planarDecks, view.getPlanarDeckLists().get(i), @@ -76,7 +78,23 @@ public class CLobby { final FDeckChooser fdc = view.getDeckChooser(iSlot); fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot)); fdc.populate(); - fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() { + /*fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() { + @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { + view.focusOnAvatar(); + } + });*/ + final FDeckChooser fdccom = view.getCommanderDeckChooser(iSlot); + fdccom.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot)); + fdccom.populate(); + fdccom.getDecksComboBox().addListener(new IDecksComboBoxListener() { + @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { + view.focusOnAvatar(); + } + }); + final FDeckChooser fdtlcom = view.getTinyLeaderDeckChooser(iSlot); + fdtlcom.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot)); + fdtlcom.populate(); + fdtlcom.getDecksComboBox().addListener(new IDecksComboBoxListener() { @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { view.focusOnAvatar(); } @@ -107,6 +125,6 @@ public class CLobby { } private static DeckType defaultDeckTypeForSlot(final int iSlot) { - return iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; + return iSlot == 0 ? DeckType.RANDOM_COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index a3b3907c63e..1852f49c2d5 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -116,6 +116,8 @@ public class VLobby implements ILobbyView { private final List> tinyLeadersDeckLists = new ArrayList>(); private final List tinyLeadersDeckPanels = new ArrayList(MAX_PLAYERS); + private final List commanderDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); + private final List tinyLeadersDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); private final List> schemeDeckLists = new ArrayList>(); private final List schemeDeckPanels = new ArrayList(MAX_PLAYERS); @@ -133,6 +135,16 @@ public class VLobby implements ILobbyView { private final Vector humanListData = new Vector(); private final Vector aiListData = new Vector(); + public boolean isForCommander() { + return isForCommander; + } + + public void setForCommander(boolean forCommander) { + isForCommander = forCommander; + } + + private boolean isForCommander = false; + // CTR public VLobby(final GameLobby lobby) { this.lobby = lobby; @@ -202,6 +214,8 @@ public class VLobby implements ILobbyView { for (int iPlayer = 0; iPlayer < activePlayersNum; iPlayer++) { final FDeckChooser fdc = getDeckChooser(iPlayer); fdc.restoreSavedState(); + final FDeckChooser fdcom = getCommanderDeckChooser(iPlayer); + fdcom.restoreSavedState(); } } @@ -226,6 +240,7 @@ public class VLobby implements ILobbyView { // visible panels final LobbySlot slot = lobby.getSlot(i); final FDeckChooser deckChooser = getDeckChooser(i); + final FDeckChooser commanderDeckChooser = getCommanderDeckChooser(i); final PlayerPanel panel; final boolean isNewPanel; if (hasPanel) { @@ -240,6 +255,7 @@ public class VLobby implements ILobbyView { } playersScroll.add(panel, constraints); deckChooser.restoreSavedState(); + commanderDeckChooser.restoreSavedState(); if (i == 0) { changePlayerFocus(0); } @@ -331,7 +347,7 @@ public class VLobby implements ILobbyView { @SuppressWarnings("serial") private void buildDeckPanels(final int playerIndex) { // Main deck - final FDeckChooser mainChooser = new FDeckChooser(null, false); + final FDeckChooser mainChooser = new FDeckChooser(null, false, GameType.Constructed, false); mainChooser.getLstDecks().setSelectCommand(new UiCommand() { @Override public final void run() { selectMainDeck(playerIndex); @@ -348,19 +364,36 @@ public class VLobby implements ILobbyView { }); // Commander deck list - buildDeckPanel("Commander Deck", playerIndex, commanderDeckLists, commanderDeckPanels, new ListSelectionListener() { + /*buildDeckPanel("Commander Deck", playerIndex, commanderDeckLists, commanderDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { selectCommanderDeck(playerIndex); } + });*/ + final FDeckChooser commanderChooser = new FDeckChooser(null, false, GameType.Commander, true); + commanderChooser.getLstDecks().setSelectCommand(new UiCommand() { + @Override public final void run() { + selectCommanderDeck(playerIndex); + } }); + commanderChooser.initialize(); + commanderDeckChoosers.add(commanderChooser); + + final FDeckChooser tinyLeaderChooser = new FDeckChooser(null, false, GameType.TinyLeaders, true); + tinyLeaderChooser.getLstDecks().setSelectCommand(new UiCommand() { + @Override public final void run() { + selectTinyLeadersDeck(playerIndex); + } + }); + tinyLeaderChooser.initialize(); + tinyLeadersDeckChoosers.add(tinyLeaderChooser); - // Tiny Leaders deck list +/* // Tiny Leaders deck list buildDeckPanel("Tiny Leaders Deck", playerIndex, tinyLeadersDeckLists, tinyLeadersDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { selectTinyLeadersDeck(playerIndex); } - }); + });*/ // Planar deck list buildDeckPanel("Planar Deck", playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() { @@ -432,6 +465,42 @@ public class VLobby implements ILobbyView { mainChooser.saveState(); } + private void selectCommanderDeck(final int playerIndex) { + if (!hasVariant(GameType.Commander)) { + // Only these game types use this specific deck panel + return; + } + final FDeckChooser mainChooser = getCommanderDeckChooser(playerIndex); + final DeckType type = mainChooser.getSelectedDeckType(); + final Deck deck = mainChooser.getDeck(); + final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); + if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { + final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); + playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + fireDeckChangeListener(playerIndex, deck); + } + mainChooser.saveState(); + } + + private void selectTinyLeadersDeck(final int playerIndex) { + if (!hasVariant(GameType.TinyLeaders)) { + // Only these game types use this specific deck panel + return; + } + final FDeckChooser mainChooser = getTinyLeaderDeckChooser(playerIndex); + final DeckType type = mainChooser.getSelectedDeckType(); + final Deck deck = mainChooser.getDeck(); + final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); + if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { + final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); + playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + fireDeckChangeListener(playerIndex, deck); + } + mainChooser.saveState(); + } + + + private void selectSchemeDeck(final int playerIndex) { if (playerIndex >= activePlayersNum || !(hasVariant(GameType.Archenemy) || hasVariant(GameType.ArchenemyRumble))) { return; @@ -463,36 +532,6 @@ public class VLobby implements ILobbyView { getDeckChooser(playerIndex).saveState(); } - private void selectCommanderDeck(final int playerIndex) { - selectCommanderOrTinyLeadersDeck(playerIndex, GameType.Commander, getCommanderDeckLists()); - } - - private void selectTinyLeadersDeck(final int playerIndex) { - selectCommanderOrTinyLeadersDeck(playerIndex, GameType.TinyLeaders, getTinyLeadersDeckLists()); - } - - private void selectCommanderOrTinyLeadersDeck(final int playerIndex, final GameType gameType, final List> deckLists) { - if (playerIndex >= activePlayersNum || !hasVariant(gameType)) { - return; - } - - final Object selected = deckLists.get(playerIndex).getSelectedValue(); - Deck deck = null; - if (selected instanceof String) { - if (selected.equals("Random")) { - deck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); - } - } else if (selected instanceof Deck) { - deck = (Deck) selected; - } - if (deck == null) { //Can be null if player deselects the list selection or chose Generate - deck = DeckgenUtil.generateCommanderDeck(isPlayerAI(playerIndex), gameType); - } - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(deck.getName()); - fireDeckChangeListener(playerIndex, deck); - getDeckChooser(playerIndex).saveState(); - } - private void selectPlanarDeck(final int playerIndex) { if (playerIndex >= activePlayersNum || !hasVariant(GameType.Planechase)) { return; @@ -594,10 +633,10 @@ public class VLobby implements ILobbyView { } break; case Commander: - decksFrame.add(commanderDeckPanels.get(playerWithFocus), "grow, push"); + decksFrame.add(commanderDeckChoosers.get(playerWithFocus), "grow, push"); break; case TinyLeaders: - decksFrame.add(tinyLeadersDeckPanels.get(playerWithFocus), "grow, push"); + decksFrame.add(tinyLeadersDeckChoosers.get(playerWithFocus), "grow, push"); break; case Planechase: decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push"); @@ -642,6 +681,14 @@ public class VLobby implements ILobbyView { return deckChoosers.get(playernum); } + public final FDeckChooser getCommanderDeckChooser(final int playernum) { + return commanderDeckChoosers.get(playernum); + } + + public final FDeckChooser getTinyLeaderDeckChooser(final int playernum) { + return tinyLeadersDeckChoosers.get(playernum); + } + GameType getCurrentGameMode() { return lobby.getGameType(); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletBuild.java b/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletBuild.java index ecad23741e8..a6f715c2ea4 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletBuild.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletBuild.java @@ -2,6 +2,7 @@ package forge.screens.home.gauntlet; import forge.assets.FSkinProp; import forge.deckchooser.FDeckChooser; +import forge.game.GameType; import forge.gauntlet.GauntletIO; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; @@ -40,7 +41,7 @@ public enum VSubmenuGauntletBuild implements IVSubmenu { private final JPanel pnlStrut = new JPanel(); private final JPanel pnlDirections = new JPanel(); - private final FDeckChooser lstLeft = new FDeckChooser(null, false); + private final FDeckChooser lstLeft = new FDeckChooser(null, false, GameType.Constructed, false); private final JList lstRight = new FList(); private final FScrollPane scrRight = new FScrollPane(lstRight, true, diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletContests.java b/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletContests.java index 1c280b3e0c5..68f48dc66c6 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletContests.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/gauntlet/VSubmenuGauntletContests.java @@ -1,6 +1,7 @@ package forge.screens.home.gauntlet; import forge.deckchooser.FDeckChooser; +import forge.game.GameType; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; @@ -42,7 +43,7 @@ public enum VSubmenuGauntletContests implements IVSubmenu { private final JCheckBox boxModernColorDecks = new FCheckBox(DeckType.MODERN_COLOR_DECK.toString()); private final JCheckBox boxThemeDecks = new FCheckBox(DeckType.THEME_DECK.toString()); - private final FDeckChooser lstDecks = new FDeckChooser(null, false); + private final FDeckChooser lstDecks = new FDeckChooser(null, false, GameType.Constructed, false); private final FLabel lblOptions = new FLabel.Builder().fontSize(16) .fontStyle(Font.BOLD).text("OPTIONS").fontAlign(SwingConstants.CENTER).build(); diff --git a/forge-gui-mobile/src/forge/deck/FDeckChooser.java b/forge-gui-mobile/src/forge/deck/FDeckChooser.java index 8842a3bce77..eb34f6bcd95 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckChooser.java +++ b/forge-gui-mobile/src/forge/deck/FDeckChooser.java @@ -453,6 +453,8 @@ public class FDeckChooser extends FScreen { case TinyLeaders: cmbDeckTypes.addItem(DeckType.CUSTOM_DECK); cmbDeckTypes.addItem(DeckType.RANDOM_DECK); + cmbDeckTypes.addItem(DeckType.RANDOM_CARDGEN_COMMANDER_DECK); + cmbDeckTypes.addItem(DeckType.RANDOM_COMMANDER_DECK); cmbDeckTypes.addItem(DeckType.NET_DECK); break; case DeckManager: @@ -575,6 +577,14 @@ public class FDeckChooser extends FScreen { pool = DeckProxy.getAllTinyLeadersDecks(); config = ItemManagerConfig.COMMANDER_DECKS; break; + case RANDOM_COMMANDER_DECK: + pool = CommanderDeckGenerator.getCommanderDecks(lstDecks.getGameType().getDeckFormat(),isAi, false); + config = ItemManagerConfig.STRING_ONLY; + break; + case RANDOM_CARDGEN_COMMANDER_DECK: + pool = CommanderDeckGenerator.getCommanderDecks(lstDecks.getGameType().getDeckFormat(),isAi, true); + config = ItemManagerConfig.STRING_ONLY; + break; case SCHEME_DECKS: pool = DeckProxy.getAllSchemeDecks(); config = ItemManagerConfig.SCHEME_DECKS; diff --git a/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java b/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java index fbf0d8dbcfa..50e2565241f 100644 --- a/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java +++ b/forge-gui/src/main/java/forge/deck/CardRelationMatrixGenerator.java @@ -1,9 +1,12 @@ package forge.deck; +import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import forge.card.CardRules; import forge.card.CardRulesPredicates; +import forge.deck.generation.DeckGeneratorBase; import forge.deck.io.CardThemedMatrixIO; import forge.deck.io.DeckStorage; import forge.game.GameFormat; @@ -22,24 +25,27 @@ import java.util.*; */ public final class CardRelationMatrixGenerator { - public static HashMap>>> cardPools = new HashMap<>(); + public static HashMap>>> cardPools = new HashMap<>(); public static void initialize(){ - HashMap>> standardMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getStandard()); - HashMap>> modernMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getModern()); - if(standardMap==null || modernMap==null){ + HashMap>> standardMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getStandard().getName()); + HashMap>> modernMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getModern().getName()); + HashMap>> commanderMap = CardThemedMatrixIO.loadMatrix(DeckFormat.Commander.toString()); + if(standardMap==null || modernMap==null || commanderMap==null){ reInitialize(); return; } - cardPools.put(FModel.getFormats().getStandard(),standardMap); - cardPools.put(FModel.getFormats().getModern(),modernMap); + cardPools.put(FModel.getFormats().getStandard().getName(),standardMap); + cardPools.put(FModel.getFormats().getModern().getName(),modernMap); + cardPools.put(DeckFormat.Commander.toString(),commanderMap); } public static void reInitialize(){ - cardPools.put(FModel.getFormats().getStandard(),initializeFormat(FModel.getFormats().getStandard())); - cardPools.put(FModel.getFormats().getModern(),initializeFormat(FModel.getFormats().getModern())); - for(GameFormat format:cardPools.keySet()){ + cardPools.put(FModel.getFormats().getStandard().getName(),initializeFormat(FModel.getFormats().getStandard())); + cardPools.put(FModel.getFormats().getModern().getName(),initializeFormat(FModel.getFormats().getModern())); + cardPools.put(DeckFormat.Commander.toString(),initializeCommanderFormat()); + for(String format:cardPools.keySet()){ HashMap>> map = cardPools.get(format); CardThemedMatrixIO.saveMatrix(format,map); } @@ -71,7 +77,6 @@ public final class CardRelationMatrixGenerator { Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND_NOT_WASTES), PaperCard.FN_GET_RULES))){ if (!pairCard.getName().equals(card.getName())){ try { - int old = matrix[cardIntegerMap.get(card.getName())][cardIntegerMap.get(pairCard.getName())]; matrix[cardIntegerMap.get(card.getName())][cardIntegerMap.get(pairCard.getName())] = old + 1; }catch (NullPointerException ne){ @@ -115,6 +120,92 @@ public final class CardRelationMatrixGenerator { return cardPools; } + public static HashMap>> initializeCommanderFormat(){ + + IStorage decks = new StorageImmediatelySerialized("Generator", new DeckStorage(new File(ForgeConstants.DECK_GEN_DIR+ForgeConstants.PATH_SEPARATOR+DeckFormat.Commander.toString()), + ForgeConstants.DECK_GEN_DIR, false), + true); + + final Iterable cards = Iterables.filter(FModel.getMagicDb().getCommonCards().getUniqueCards() + , Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND_NOT_WASTES), PaperCard.FN_GET_RULES)); + List cardList = Lists.newArrayList(cards); + cardList.add(FModel.getMagicDb().getCommonCards().getCard("Wastes")); + Map cardIntegerMap = new HashMap<>(); + Map integerCardMap = new HashMap<>(); + Map legendIntegerMap = new HashMap<>(); + Map integerLegendMap = new HashMap<>(); + for (int i=0; i legends = Lists.newArrayList(Iterables.filter(cardList,Predicates.compose( + new Predicate() { + @Override + public boolean apply(CardRules rules) { + return DeckFormat.Commander.isLegalCommander(rules); + } + }, PaperCard.FN_GET_RULES))); + + for (int i=0; i>> cardPools = new HashMap<>(); + for (PaperCard card:legends){ + int col=legendIntegerMap.get(card.getName()); + int[] distances = matrix[col]; + int max = (Integer) Collections.max(Arrays.asList(ArrayUtils.toObject(distances))); + if (max>0) { + ArrayIndexComparator comparator = new ArrayIndexComparator(ArrayUtils.toObject(distances)); + Integer[] indices = comparator.createIndexArray(); + Arrays.sort(indices, comparator); + List> deckPool=new ArrayList<>(); + int k=0; + boolean excludeThisCard=false;//if there are too few cards with at least one connection + for (int j=0;j<56;++k){ + if(distances[indices[cardList.size()-1-k]]==0){ + excludeThisCard = true; + break; + } + PaperCard cardToAdd=integerCardMap.get(indices[cardList.size()-1-k]); + if(!cardToAdd.getRules().getMainPart().getType().isLand()){//need x non-land cards + ++j; + } + deckPool.add(new AbstractMap.SimpleEntry(cardToAdd,distances[indices[cardList.size()-1-k]])); + }; + /*if(excludeThisCard){ + continue; + }*/ + cardPools.put(card.getName(),deckPool); + } + } + return cardPools; + } + public static class ArrayIndexComparator implements Comparator { private final Integer[] array; diff --git a/forge-gui/src/main/java/forge/deck/CardThemedDeckGenerator.java b/forge-gui/src/main/java/forge/deck/CardThemedDeckGenerator.java index 4d48f3df7d7..b536ebb6a17 100644 --- a/forge-gui/src/main/java/forge/deck/CardThemedDeckGenerator.java +++ b/forge-gui/src/main/java/forge/deck/CardThemedDeckGenerator.java @@ -1,7 +1,11 @@ package forge.deck; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import forge.card.CardEdition; import forge.game.GameFormat; +import forge.item.PaperCard; +import forge.model.FModel; import java.util.ArrayList; import java.util.List; @@ -12,7 +16,7 @@ import java.util.List; public class CardThemedDeckGenerator extends DeckProxy implements Comparable { public static List getMatrixDecks(GameFormat format, boolean isForAi){ final List decks = new ArrayList(); - for(String card: CardRelationMatrixGenerator.cardPools.get(format).keySet()) { + for(String card: CardRelationMatrixGenerator.cardPools.get(format.getName()).keySet()) { decks.add(new CardThemedDeckGenerator(card, format, isForAi)); } return decks; @@ -61,4 +65,11 @@ public class CardThemedDeckGenerator extends DeckProxy implements Comparable cardFilter = Predicates.and(format.getFilterPrinted(),PaperCard.Predicates.name(name)); + List cards=FModel.getMagicDb().getCommonCards().getAllCards(cardFilter); + return cards.get(cards.size()-1).getImageKey(altState);*/ + return FModel.getMagicDb().getCommonCards().getUniqueByName(name).getImageKey(altState); + } } diff --git a/forge-gui/src/main/java/forge/deck/CommanderDeckGenerator.java b/forge-gui/src/main/java/forge/deck/CommanderDeckGenerator.java new file mode 100644 index 00000000000..5df93da5e01 --- /dev/null +++ b/forge-gui/src/main/java/forge/deck/CommanderDeckGenerator.java @@ -0,0 +1,103 @@ +package forge.deck; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import forge.card.CardEdition; +import forge.card.CardRules; +import forge.card.CardRulesPredicates; +import forge.deck.generation.DeckGeneratorBase; +import forge.deck.generation.IDeckGenPool; +import forge.game.GameFormat; +import forge.game.GameType; +import forge.item.PaperCard; +import forge.model.FModel; +import forge.util.ItemPool; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created by maustin on 09/05/2017. + */ +public class CommanderDeckGenerator extends DeckProxy implements Comparable { + public static List getCommanderDecks(DeckFormat format, boolean isForAi, boolean isCardGen){ + ItemPool uniqueCards; + if(isCardGen){ + uniqueCards = new ItemPool(PaperCard.class); + Iterable legendNames=CardRelationMatrixGenerator.cardPools.get(DeckFormat.Commander.toString()).keySet(); + for(String legendName:legendNames) { + uniqueCards.add(FModel.getMagicDb().getCommonCards().getUniqueByName(legendName)); + } + }else { + uniqueCards = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getUniqueCards(), PaperCard.class); + } + Predicate canPlay = isForAi ? DeckGeneratorBase.AI_CAN_PLAY : DeckGeneratorBase.HUMAN_CAN_PLAY; + @SuppressWarnings("unchecked") + Iterable legends = Iterables.filter(uniqueCards.toFlatList(), Predicates.compose(Predicates.and( + new Predicate() { + @Override + public boolean apply(CardRules rules) { + return format.isLegalCommander(rules); + } + }, + canPlay), PaperCard.FN_GET_RULES)); + final List decks = new ArrayList(); + for(PaperCard legend: legends) { + decks.add(new CommanderDeckGenerator(legend, format, isForAi, isCardGen)); + } + return decks; + } + + private final PaperCard legend; + private final int index; + private final DeckFormat format; + private final boolean isForAi; + private final boolean isCardgen; + + + private CommanderDeckGenerator(PaperCard legend0, DeckFormat format0, boolean isForAi0, boolean isCardgen0) { + super(); + legend = legend0; + index = 0; + isForAi=isForAi0; + format=format0; + isCardgen=isCardgen0; + } + + public CardEdition getEdition() { + return CardEdition.UNKNOWN; + } + + + @Override + public String getName() { + return legend.getName(); + } + + @Override + public String toString() { + return legend.getName(); + } + + @Override + public int compareTo(final CommanderDeckGenerator d) { + return this.getName().compareTo(d.getName()); + } + + @Override + public Deck getDeck() { + + return DeckgenUtil.generateRandomCommanderDeck(legend, format,isForAi, isCardgen); + } + + @Override + public boolean isGeneratedDeck() { + return true; + } + + public String getImageKey(boolean altState) { + return legend.getImageKey(altState); + } +} diff --git a/forge-gui/src/main/java/forge/deck/DeckType.java b/forge-gui/src/main/java/forge/deck/DeckType.java index 9b074229372..0685b2be8ed 100644 --- a/forge-gui/src/main/java/forge/deck/DeckType.java +++ b/forge-gui/src/main/java/forge/deck/DeckType.java @@ -7,6 +7,8 @@ public enum DeckType { CUSTOM_DECK ("Custom User Decks"), CONSTRUCTED_DECK ("Constructed Decks"), COMMANDER_DECK ("Commander Decks"), + RANDOM_COMMANDER_DECK ("Random Commander Decks"), + RANDOM_CARDGEN_COMMANDER_DECK ("Random Commander Card-based Decks"), TINY_LEADERS_DECKS ("Tiny Leaders Decks"), SCHEME_DECKS ("Scheme Decks"), PLANAR_DECKS ("Planar Decks"), @@ -25,6 +27,7 @@ public enum DeckType { NET_COMMANDER_DECK ("Net Commander Decks"); public static DeckType[] ConstructedOptions; + public static DeckType[] CommanderOptions; static { if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.LOAD_CARD_SCRIPTS_LAZILY)) { @@ -55,6 +58,15 @@ public enum DeckType { }; } } + static { + CommanderOptions = new DeckType[]{ + DeckType.COMMANDER_DECK, + DeckType.RANDOM_COMMANDER_DECK, + DeckType.RANDOM_CARDGEN_COMMANDER_DECK, + DeckType.RANDOM_DECK, + DeckType.NET_COMMANDER_DECK + }; + } private String value; private DeckType(final String value) { diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java index 326ee1325b6..ce2b324de38 100644 --- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java +++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java @@ -16,6 +16,7 @@ import forge.game.GameFormat; import forge.game.GameType; import forge.item.PaperCard; import forge.itemmanager.IItemManager; +import forge.limited.CardThemedCommanderDeckBuilder; import forge.limited.CardThemedDeckBuilder; import forge.model.FModel; import forge.properties.ForgePreferences.FPref; @@ -44,7 +45,7 @@ public class DeckgenUtil { public static Deck buildCardGenDeck(GameFormat format, boolean isForAI){ Random random = new Random(); try { - List keys = new ArrayList<>(CardRelationMatrixGenerator.cardPools.get(format).keySet()); + List keys = new ArrayList<>(CardRelationMatrixGenerator.cardPools.get(format.getName()).keySet()); String randomKey = keys.get( random.nextInt(keys.size()) ); Predicate cardFilter = Predicates.and(format.getFilterPrinted(),PaperCard.Predicates.name(randomKey)); PaperCard keyCard = FModel.getMagicDb().getCommonCards().getAllCards(cardFilter).get(0); @@ -119,7 +120,7 @@ public class DeckgenUtil { */ public static Deck buildCardGenDeck(PaperCard card, GameFormat format, boolean isForAI){ List> potentialCards = new ArrayList<>(); - potentialCards.addAll(CardRelationMatrixGenerator.cardPools.get(format).get(card.getName())); + potentialCards.addAll(CardRelationMatrixGenerator.cardPools.get(format.getName()).get(card.getName())); Collections.sort(potentialCards,new CardDistanceComparator()); Collections.reverse(potentialCards); //get second keycard @@ -146,7 +147,7 @@ public class DeckgenUtil { randMax=preSelectedCards.size(); } PaperCard secondKeycard = preSelectedCards.get(r.nextInt(randMax)); - List> potentialSecondCards = CardRelationMatrixGenerator.cardPools.get(format).get(secondKeycard.getName()); + List> potentialSecondCards = CardRelationMatrixGenerator.cardPools.get(format.getName()).get(secondKeycard.getName()); //combine card distances from second key card and re-sort if(potentialSecondCards !=null && potentialSecondCards.size()>0) { @@ -486,7 +487,7 @@ public class DeckgenUtil { return res; } - /** Generate a 2-color Commander deck. */ + /** Generate a 2-5-color Commander deck. */ public static Deck generateCommanderDeck(boolean forAi, GameType gameType) { final Deck deck; IDeckGenPool cardDb = FModel.getMagicDb().getCommonCards(); @@ -504,7 +505,6 @@ public class DeckgenUtil { return format.isLegalCommander(rules); } }, - CardRulesPredicates.Presets.IS_MULTICOLOR, canPlay), PaperCard.FN_GET_RULES)); do { @@ -534,6 +534,78 @@ public class DeckgenUtil { return deck; } + /** Generate a ramdom Commander deck. */ + public static Deck generateRandomCommanderDeck(PaperCard commander, DeckFormat format, boolean forAi, boolean isCardGen) { + final Deck deck; + IDeckGenPool cardDb; + DeckGeneratorBase gen = null; + if(isCardGen){ + List> potentialCards = new ArrayList<>(); + potentialCards.addAll(CardRelationMatrixGenerator.cardPools.get(DeckFormat.Commander.toString()).get(commander.getName())); + Collections.shuffle(potentialCards); + //get second keycard + Random r = new Random(); + List preSelectedCards = new ArrayList<>(); + for(Map.Entry pair:potentialCards){ + preSelectedCards.add(pair.getKey()); + } + //randomly remove cards + int removeCount=0; + int i=0; + List toRemove = new ArrayList<>(); + for(PaperCard c:preSelectedCards){ + if(preSelectedCards.size()<80){ + break; + } + if(r.nextInt(100)>70+(15-(i/preSelectedCards.size())*preSelectedCards.size()) && removeCount<4 //randomly remove some cards - more likely as distance increases + &&!c.getName().contains("Urza")){ //avoid breaking Tron decks + toRemove.add(c); + removeCount++; + } + ++i; + } + preSelectedCards.removeAll(toRemove); + gen = new CardThemedCommanderDeckBuilder(commander,preSelectedCards,forAi); + }else{ + cardDb = FModel.getMagicDb().getCommonCards(); + ColorSet colorID; + colorID = commander.getRules().getColorIdentity(); + List comColors = new ArrayList(2); + if (colorID.hasWhite()) { comColors.add("White"); } + if (colorID.hasBlue()) { comColors.add("Blue"); } + if (colorID.hasBlack()) { comColors.add("Black"); } + if (colorID.hasRed()) { comColors.add("Red"); } + if (colorID.hasGreen()) { comColors.add("Green"); } + + if(comColors.size()==1){ + gen = new DeckGeneratorMonoColor(cardDb, format, comColors.get(0)); + }else if(comColors.size()==2){ + gen = new DeckGenerator2Color(cardDb, format, comColors.get(0), comColors.get(1)); + }else if(comColors.size()==3){ + gen = new DeckGenerator3Color(cardDb, format, comColors.get(0), comColors.get(1), comColors.get(2)); + }else if(comColors.size()==4){ + gen = new DeckGenerator4Color(cardDb, format, comColors.get(0), comColors.get(1), comColors.get(2), comColors.get(3)); + }else if(comColors.size()==5){ + gen = new DeckGenerator5Color(cardDb, format); + } + + } + + + + gen.setSingleton(true); + gen.setUseArtifacts(!FModel.getPreferences().getPrefBoolean(FPref.DECKGEN_ARTIFACTS)); + CardPool cards = gen.getDeck(format.getMainRange().getMaximum(), forAi); + + // After generating card lists, build deck. + deck = new Deck("Generated " + format.toString() + " deck (" + commander.getName() + ")"); + deck.setDirectory("generated/commander"); + deck.getMain().addAll(cards); + deck.getOrCreate(DeckSection.Commander).add(commander); + + return deck; + } + public static Map suggestBasicLandCount(Deck d) { int W=0, U=0, R=0, B=0, G=0, total=0; List cards = d.getOrCreate(DeckSection.Main).toFlatList(); diff --git a/forge-gui/src/main/java/forge/deck/io/CardThemedMatrixIO.java b/forge-gui/src/main/java/forge/deck/io/CardThemedMatrixIO.java index 1bca2c69420..d226ffe4566 100644 --- a/forge-gui/src/main/java/forge/deck/io/CardThemedMatrixIO.java +++ b/forge-gui/src/main/java/forge/deck/io/CardThemedMatrixIO.java @@ -22,7 +22,7 @@ public class CardThemedMatrixIO { /** suffix for all gauntlet data files */ public static final String SUFFIX_DATA = ".dat"; - public static void saveMatrix(GameFormat format, HashMap>> map){ + public static void saveMatrix(String format, HashMap>> map){ File file = getMatrixFile(format); ObjectOutputStream s = null; try { @@ -43,7 +43,7 @@ public class CardThemedMatrixIO { } } - public static HashMap>> loadMatrix(GameFormat format){ + public static HashMap>> loadMatrix(String format){ try { FileInputStream fin = new FileInputStream(getMatrixFile(format)); ObjectInputStream s = new ObjectInputStream(fin); diff --git a/forge-gui/src/main/java/forge/limited/CardThemedCommanderDeckBuilder.java b/forge-gui/src/main/java/forge/limited/CardThemedCommanderDeckBuilder.java new file mode 100644 index 00000000000..5c42fbd5fdf --- /dev/null +++ b/forge-gui/src/main/java/forge/limited/CardThemedCommanderDeckBuilder.java @@ -0,0 +1,828 @@ +package forge.limited; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import forge.card.*; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostShard; +import forge.deck.CardPool; +import forge.deck.Deck; +import forge.deck.DeckFormat; +import forge.deck.DeckSection; +import forge.deck.generation.DeckGenPool; +import forge.deck.generation.DeckGeneratorBase; +import forge.game.GameFormat; +import forge.item.IPaperCard; +import forge.item.PaperCard; +import forge.model.FModel; +import forge.util.MyRandom; + +import java.util.*; + +/** + * Limited format deck. + */ +public class CardThemedCommanderDeckBuilder extends DeckGeneratorBase { + @Override + protected final float getLandPercentage() { + return 0.44f; + } + @Override + protected final float getCreaturePercentage() { + return 0.33f; + } + @Override + protected final float getSpellPercentage() { + return 0.23f; + } + + protected int numSpellsNeeded = 55; + protected int landsNeeded = 44; + + protected final PaperCard keyCard; + + protected Predicate hasColor; + protected final List availableList; + protected final List aiPlayables; + protected final List deckList = new ArrayList<>(); + protected final List setsWithBasicLands = new ArrayList<>(); + protected List rankedColorList; + + // Views for aiPlayable + protected Iterable onColorCreatures; + protected Iterable onColorNonCreatures; + protected Iterable keyCards; + + protected static final boolean logToConsole = true; + protected static final boolean logColorsToConsole = true; + + + /** + * + * Constructor. + * + * @param dList + * Cards to build the deck from. + */ + public CardThemedCommanderDeckBuilder(PaperCard keyCard0, final List dList, boolean isForAI) { + super(FModel.getMagicDb().getCommonCards(), DeckFormat.Commander); + this.availableList = dList; + keyCard=keyCard0; + // remove Unplayables + if(isForAI) { + final Iterable playables = Iterables.filter(availableList, + Predicates.compose(CardRulesPredicates.IS_KEPT_IN_AI_DECKS, PaperCard.FN_GET_RULES)); + this.aiPlayables = Lists.newArrayList(playables); + }else{ + this.aiPlayables = Lists.newArrayList(availableList); + } + this.availableList.removeAll(aiPlayables); + colors = keyCard.getRules().getColorIdentity(); + if (logColorsToConsole) { + System.out.println(keyCard.getName()); + System.out.println("Pre Colors: " + colors.toEnumSet().toString()); + } + if(!colors.hasAllColors(keyCard.getRules().getColorIdentity().getColor())){ + colors = ColorSet.fromMask(colors.getColor() | keyCard.getRules().getColorIdentity().getColor()); + } + if (logColorsToConsole) { + System.out.println(keyCard.getName()); + System.out.println("Pre Colors: " + colors.toEnumSet().toString()); + } + findBasicLandSets(); + } + + + + @Override + public CardPool getDeck(final int size, final boolean forAi) { + return buildDeck().getMain(); + } + + /** + *

+ * buildDeck. + *

+ * + * @return the new Deck. + */ + @SuppressWarnings("unused") + public Deck buildDeck() { + // 1. Prepare + hasColor = Predicates.or(new MatchColorIdentity(colors), COLORLESS_CARDS); + if (logColorsToConsole) { + System.out.println(keyCard.getName()); + System.out.println("Colors: " + colors.toEnumSet().toString()); + } + Iterable colorList = Iterables.filter(aiPlayables, + Predicates.compose(hasColor, PaperCard.FN_GET_RULES)); + rankedColorList = Lists.newArrayList(colorList); + onColorCreatures = Iterables.filter(rankedColorList, + Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES)); + onColorNonCreatures = Iterables.filter(rankedColorList, + Predicates.compose(CardRulesPredicates.Presets.IS_NON_CREATURE_SPELL, PaperCard.FN_GET_RULES)); + // Guava iterables do not copy the collection contents, instead they act + // as filters and iterate over _source_ collection each time. So even if + // aiPlayable has changed, there is no need to create a new iterable. + + // 2. Add any planeswalkers - removed - treat as non-creature + + // 3. Add creatures, trying to follow mana curve + + addManaCurveCreatures(onColorCreatures, 30); + if (logToConsole) { + System.out.println("Post Creatures : " + deckList.size()); + } + + // 4.Try to fill up to num needed with on-color non-creature cards + addNonCreatures(onColorNonCreatures, numSpellsNeeded - deckList.size()); + if (logToConsole) { + System.out.println("Post Spells : " + deckList.size()); + } + + // 5.If we couldn't get enough, try to fill up with on-color creature cards + addCreatures(onColorCreatures, numSpellsNeeded - deckList.size()); + if (logToConsole) { + System.out.println("Post more creatures : " + deckList.size()); + } + + // 6. If there are still on-color cards, and the average cmc is low, add + // extras. + double avCMC=getAverageCMC(deckList); + int maxCMC=getMaxCMC(deckList); + if (deckList.size() == numSpellsNeeded && avCMC < 4) { + addLowCMCCard(); + } + if (deckList.size() >= numSpellsNeeded && avCMC < 3 && maxCMC<6) { + addLowCMCCard(); + } + if (deckList.size() >= numSpellsNeeded && avCMC < 2.5 && maxCMC<5) { + addLowCMCCard(); + } + if (logToConsole) { + System.out.println("Post lowcoc : " + deckList.size()); + } + + // 7. If not enough cards yet, try to add a third color, + // to try and avoid adding purely random cards. + addThirdColorCards(numSpellsNeeded - deckList.size()); + if (logColorsToConsole) { + System.out.println("Post 3rd colour : " + deckList.size()); + System.out.println("Colors: " + colors.toEnumSet().toString()); + } + + // 8. Check for DeckNeeds cards. + //checkRemRandomDeckCards(); - no need + + // 9. If there are still less than 22 non-land cards add off-color + // cards. This should be avoided. + addRandomCards(numSpellsNeeded - deckList.size()); + if (logToConsole) { + System.out.println("Post Randoms : " + deckList.size()); + } + //update colors + FullDeckColors finalDeckColors = new FullDeckColors(); + for(PaperCard c:deckList){ + if(finalDeckColors.canChoseMoreColors()){ + finalDeckColors.addColorsOf(c); + } + } + colors = finalDeckColors.getChosenColors(); + if (logColorsToConsole) { + System.out.println("Final Colors: " + colors.toEnumSet().toString()); + } + // 10. Add non-basic lands that were drafted. + addWastesIfRequired(); + List duals = getDualLandList(); + addNonBasicLands(); + if (logToConsole) { + System.out.println("Post Nonbasic lands : " + deckList.size()); + } + + checkEvolvingWilds(); + + // 11. Fill up with basic lands. + final int[] clrCnts = calculateLandNeeds(); + + // Add dual lands + if (clrCnts.length>1) { + + for (String s : duals) { + this.cardCounts.put(s, 0); + } + } + + + if (landsNeeded > 0) { + addLands(clrCnts); + } + if (logToConsole) { + System.out.println("Post Lands : " + deckList.size()); + } + fixDeckSize(clrCnts); + if (logToConsole) { + System.out.println("Post Size fix : " + deckList.size()); + } + + //Create Deck + final Deck result = new Deck(generateName()); + result.getMain().add(deckList); + + //Add remaining non-land colour matching cards to sideboard + final CardPool cp = result.getOrCreate(DeckSection.Sideboard); + Iterable potentialSideboard = Iterables.filter(aiPlayables, + Predicates.and(Predicates.compose(hasColor, PaperCard.FN_GET_RULES), + Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES))); + int i=0; + while(i<15 && potentialSideboard.iterator().hasNext()){ + PaperCard sbCard = potentialSideboard.iterator().next(); + cp.add(sbCard); + aiPlayables.remove(sbCard); + + ++i; + } + if (logToConsole) { + debugFinalDeck(); + } + return result; + + } + + /** + * If evolving wilds is in the deck and there are fewer than 4 spaces for basic lands - remove evolving wilds + */ + protected void checkEvolvingWilds(){ + List evolvingWilds = Lists.newArrayList(Iterables.filter(deckList,PaperCard.Predicates.name("Evolving Wilds"))); + if((evolvingWilds.size()>0 && landsNeeded<4 ) || colors.countColors()<2){ + deckList.removeAll(evolvingWilds); + landsNeeded=landsNeeded+evolvingWilds.size(); + aiPlayables.addAll(evolvingWilds); + } + } + + + protected void addLowCMCCard(){ + final Iterable nonLands = Iterables.filter(rankedColorList, + Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); + final PaperCard card = Iterables.getFirst(nonLands, null); + if (card != null) { + deckList.add(card); + aiPlayables.remove(card); + landsNeeded--; + if (logToConsole) { + System.out.println("Low CMC: " + card.getName()); + } + } + } + + /** + * Set the basic land pool + * @param edition + * @return + */ + protected boolean setBasicLandPool(String edition){ + Predicate isSetBasicLand; + if (edition !=null){ + isSetBasicLand = Predicates.and(IPaperCard.Predicates.printedInSet(edition), + Predicates.compose(CardRulesPredicates.Presets.IS_BASIC_LAND, PaperCard.FN_GET_RULES)); + }else{ + isSetBasicLand = Predicates.compose(CardRulesPredicates.Presets.IS_BASIC_LAND, PaperCard.FN_GET_RULES); + } + + landPool = new DeckGenPool(format.getCardPool(fullCardDB).getAllCards(isSetBasicLand)); + return landPool.contains("Plains"); + } + + /** + * Generate a descriptive name. + * + * @return name + */ + private String generateName() { + return keyCard.getName() +" based commander deck"; + } + + /** + * Print out listing of all cards for debugging. + */ + private void debugFinalDeck() { + int i = 0; + System.out.println("DECK"); + for (final PaperCard c : deckList) { + i++; + System.out.println(i + ". " + c.toString() + ": " + c.getRules().getManaCost().toString()); + } + i = 0; + System.out.println("NOT PLAYABLE"); + for (final PaperCard c : availableList) { + i++; + System.out.println(i + ". " + c.toString() + ": " + c.getRules().getManaCost().toString()); + } + i = 0; + System.out.println("NOT PICKED"); + for (final PaperCard c : aiPlayables) { + i++; + System.out.println(i + ". " + c.toString() + ": " + c.getRules().getManaCost().toString()); + } + } + + /** + * If the deck does not have 40 cards, fix it. This method should not be + * called if the stuff above it is working correctly. + * + * @param clrCnts + * color counts needed + */ + private void fixDeckSize(final int[] clrCnts) { + while (deckList.size() > 99) { + if (logToConsole) { + System.out.println("WARNING: Fixing deck size, currently " + deckList.size() + " cards."); + } + final PaperCard c = deckList.get(MyRandom.getRandom().nextInt(deckList.size() - 1)); + deckList.remove(c); + aiPlayables.add(c); + if (logToConsole) { + System.out.println(" - Removed " + c.getName() + " randomly."); + } + } + + while (deckList.size() < 99) { + if (logToConsole) { + System.out.println("WARNING: Fixing deck size, currently " + deckList.size() + " cards."); + } + if (aiPlayables.size() > 1) { + final PaperCard c = aiPlayables.get(MyRandom.getRandom().nextInt(aiPlayables.size() - 1)); + deckList.add(c); + aiPlayables.remove(c); + if (logToConsole) { + System.out.println(" - Added " + c.getName() + " randomly."); + } + } else if (aiPlayables.size() == 1) { + final PaperCard c = aiPlayables.get(0); + deckList.add(c); + aiPlayables.remove(c); + if (logToConsole) { + System.out.println(" - Added " + c.getName() + " randomly."); + } + } else { + // if no playable cards remain fill up with basic lands + for (int i = 0; i < 5; i++) { + if (clrCnts[i] > 0) { + final PaperCard cp = getBasicLand(i); + deckList.add(cp); + if (logToConsole) { + System.out.println(" - Added " + cp.getName() + " as last resort."); + } + break; + } + } + } + } + } + + /** + * Find the sets that have basic lands for the available cards. + */ + private void findBasicLandSets() { + final Set sets = new HashSet<>(); + for (final PaperCard cp : aiPlayables) { + final CardEdition ee = FModel.getMagicDb().getEditions().get(cp.getEdition()); + if( !sets.contains(cp.getEdition()) && CardEdition.Predicates.hasBasicLands.apply(ee)) { + sets.add(cp.getEdition()); + } + } + setsWithBasicLands.addAll(sets); + if (setsWithBasicLands.isEmpty()) { + setsWithBasicLands.add("BFZ"); + } + } + + /** + * Add lands to fulfill the given color counts. + * + * @param clrCnts + * counts of lands needed, by color + */ + private void addLands(final int[] clrCnts) { + // basic lands that are available in the deck + final Iterable basicLands = Iterables.filter(aiPlayables, Predicates.compose(CardRulesPredicates.Presets.IS_BASIC_LAND, PaperCard.FN_GET_RULES)); + final Set snowLands = new HashSet(); + + // total of all ClrCnts + int totalColor = 0; + int numColors = 0; + for (int i = 0; i < 5; i++) { + totalColor += clrCnts[i]; + if (clrCnts[i] > 0) { + numColors++; + } + } + if (totalColor == 0) { + throw new RuntimeException("Add Lands to empty deck list!"); + } + + // do not update landsNeeded until after the loop, because the + // calculation involves landsNeeded + for (int i = 0; i < 5; i++) { + if (clrCnts[i] > 0) { + // calculate number of lands for each color + float p = (float) clrCnts[i] / (float) totalColor; + if (numColors == 2) { + // In the normal two-color case, constrain to within 40% and 60% so that the AI + // doesn't put too few lands of the lesser color, risking getting screwed on that color. + // Don't do this for the odd case where a third color had to be added to the deck. + p = Math.min(Math.max(p, 0.4f), 0.6f); + } + int nLand = Math.round(landsNeeded * p); // desired truncation to int + if (logToConsole) { + System.out.printf("Basics[%s]: %d/%d = %f%% = %d cards%n", MagicColor.Constant.BASIC_LANDS.get(i), clrCnts[i], totalColor, 100*p, nLand); + } + + // if appropriate snow-covered lands are available, add them + for (final PaperCard cp : basicLands) { + if (cp.getName().equals(MagicColor.Constant.SNOW_LANDS.get(i))) { + snowLands.add(cp); + nLand--; + } + } + + for (int j = 0; j < nLand; j++) { + deckList.add(getBasicLand(i)); + } + } + } + + // A common problem at this point is that p in the above loop was exactly 1/2, + // and nLand rounded up for both colors, so that one too many lands was added. + // So if the deck size is > 60, remove the last land added. + // Otherwise, the fixDeckSize() method would remove random cards. + while (deckList.size() > 99) { + deckList.remove(deckList.size() - 1); + } + + deckList.addAll(snowLands); + aiPlayables.removeAll(snowLands); + } + + /** + * Get basic land. + * + * @param basicLand + * the set to take basic lands from (pass 'null' for random). + * @return card + */ + private PaperCard getBasicLand(final int basicLand) { + String set; + if (setsWithBasicLands.size() > 1) { + set = setsWithBasicLands.get(MyRandom.getRandom().nextInt(setsWithBasicLands.size() - 1)); + } else { + set = setsWithBasicLands.get(0); + } + return FModel.getMagicDb().getCommonCards().getCard(MagicColor.Constant.BASIC_LANDS.get(basicLand), set); + } + + /** + * Only adds wastes if present in the card pool but if present adds them all + */ + private void addWastesIfRequired(){ + List toAdd = Lists.newArrayList(Iterables.filter(aiPlayables,PaperCard.Predicates.name("Wastes"))); + deckList.addAll(toAdd); + aiPlayables.removeAll(toAdd); + rankedColorList.removeAll(toAdd); + landsNeeded = landsNeeded - toAdd.size(); + } + + /** + * Attempt to optimize basic land counts according to color representation. + * Only consider colors that are supposed to be in the deck. It's not worth + * putting one land in for that random off-color card we had to stick in at + * the end... + * + * @return CCnt + */ + private int[] calculateLandNeeds() { + final int[] clrCnts = { 0,0,0,0,0 }; + // count each card color using mana costs + for (final PaperCard cp : deckList) { + final ManaCost mc = cp.getRules().getManaCost(); + + // count each mana symbol in the mana cost + for (final ManaCostShard shard : mc) { + for ( int i = 0 ; i < MagicColor.WUBRG.length; i++ ) { + final byte c = MagicColor.WUBRG[i]; + + if ( shard.canBePaidWithManaOfColor(c) && colors.hasAnyColor(c)) { + clrCnts[i]++; + } + } + } + } + return clrCnts; + } + + /** + * Add non-basic lands to the deck. + */ + private void addNonBasicLands() { + final Iterable lands = Iterables.filter(aiPlayables, + Predicates.compose(CardRulesPredicates.Presets.IS_NONBASIC_LAND, PaperCard.FN_GET_RULES)); + List landsToAdd = new ArrayList<>(); + int minBasics=r.nextInt(6)+3;//Keep a minimum number of basics to ensure playable decks + for (final PaperCard card : lands) { + if (landsNeeded > minBasics) { + // Throw out any dual-lands for the wrong colors. Assume + // everything else is either + // (a) dual-land of the correct two colors, or + // (b) a land that generates colorless mana and has some other + // beneficial effect. + if (!card.getRules().getColorIdentity().isColorless() && card.getRules().getColorIdentity().getSharedColors(colors).countColors()==0){ + //skip as does not match colours + if (logToConsole) { + System.out.println("Excluding NonBasicLand: " + card.getName()); + } + continue; + } + if (!inverseDLands.contains(card.getName())&&!dLands.contains(card.getName())&&r.nextInt(100)<90) { + landsToAdd.add(card); + landsNeeded--; + if (logToConsole) { + System.out.println("NonBasicLand[" + landsNeeded + "]:" + card.getName()); + } + } + } + } + deckList.addAll(landsToAdd); + aiPlayables.removeAll(landsToAdd); + } + + /** + * Add a third color to the deck. + * + * @param num + * number to add + */ + private void addThirdColorCards(int num) { + if (num > 0) { + final Iterable others = Iterables.filter(aiPlayables, + Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); + // We haven't yet ranked the off-color cards. + // Compare them to the cards already in the deckList. + //List rankedOthers = CardRanker.rankCardsInPack(others, deckList, colors, true); + List toAdd = new ArrayList<>(); + for (final PaperCard card : others) { + // Want a card that has just one "off" color. + final ColorSet off = colors.getOffColors(card.getRules().getColor()); + if (off.isMonoColor()) { + colors = ColorSet.fromMask(colors.getColor() | off.getColor()); + break; + } + } + + hasColor = Predicates.and(CardRulesPredicates.Presets.IS_NON_LAND,Predicates.or(new MatchColorIdentity(colors), + DeckGeneratorBase.COLORLESS_CARDS)); + final Iterable threeColorList = Iterables.filter(aiPlayables, + Predicates.compose(hasColor, PaperCard.FN_GET_RULES)); + for (final PaperCard card : threeColorList) { + if (num > 0) { + toAdd.add(card); + num--; + if (logToConsole) { + System.out.println("Third Color[" + num + "]:" + card.getName() + "(" + + card.getRules().getManaCost() + ")"); + } + } else { + break; + } + } + deckList.addAll(toAdd); + aiPlayables.removeAll(toAdd); + } + } + + /** + * Add random cards to the deck. + * + * @param num + * number to add + */ + private void addRandomCards(int num) { + final Iterable others = Iterables.filter(aiPlayables, + Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); + List toAdd = new ArrayList<>(); + for (final PaperCard card : others) { + if (num > 0) { + toAdd.add(card); + num--; + if (logToConsole) { + System.out.println("Random[" + num + "]:" + card.getName() + "(" + + card.getRules().getManaCost() + ")"); + } + } else { + break; + } + } + deckList.addAll(toAdd); + aiPlayables.removeAll(toAdd); + rankedColorList.removeAll(toAdd); + } + + /** + * Add highest ranked non-creatures to the deck. + * + * @param nonCreatures + * cards to choose from + * @param num + * number to add + */ + private void addNonCreatures(final Iterable nonCreatures, int num) { + List toAdd = new ArrayList<>(); + for (final PaperCard card : nonCreatures) { + if (num > 0) { + toAdd.add(card); + num--; + if (logToConsole) { + System.out.println("Others[" + num + "]:" + card.getName() + " (" + + card.getRules().getManaCost() + ")"); + } + } else { + break; + } + } + deckList.addAll(toAdd); + aiPlayables.removeAll(toAdd); + rankedColorList.removeAll(toAdd); + } + + /** + * Add creatures to the deck. + * + * @param creatures + * cards to choose from + * @param num + * number to add + */ + private void addCreatures(final Iterable creatures, int num) { + List creaturesToAdd = new ArrayList<>(); + for (final PaperCard card : creatures) { + if (num > 0) { + creaturesToAdd.add(card); + num--; + if (logToConsole) { + System.out.println("Creature[" + num + "]:" + card.getName() + " (" + card.getRules().getManaCost() + ")"); + } + } else { + break; + } + } + deckList.addAll(creaturesToAdd); + aiPlayables.removeAll(creaturesToAdd); + rankedColorList.removeAll(creaturesToAdd); + } + + /** + * Add creatures to the deck, trying to follow some mana curve. Trying to + * have generous limits at each cost, but perhaps still too strict. But + * we're trying to prevent the AI from adding everything at a single cost. + * + * @param creatures + * cards to choose from + * @param num + * number to add + */ + private void addManaCurveCreatures(final Iterable creatures, int num) { + // Add the deck card + if(keyCard.getRules().getMainPart().getType().isCreature()) { + keyCards = Iterables.filter(aiPlayables,PaperCard.Predicates.name(keyCard.getName())); + final List keyCardList = Lists.newArrayList(keyCards); + deckList.addAll(keyCardList); + aiPlayables.removeAll(keyCardList); + rankedColorList.removeAll(keyCardList); + } + final Map targetCMCs = new HashMap<>(); + targetCMCs.put(1,r.nextInt(4)+2);//2 + targetCMCs.put(2,r.nextInt(5)+5);//6 + targetCMCs.put(3,r.nextInt(5)+6);//7 + targetCMCs.put(4,r.nextInt(3)+3);//4 + targetCMCs.put(5,r.nextInt(3)+3);//3 + targetCMCs.put(6,r.nextInt(3)+1);//2 + + + final Map creatureCosts = new HashMap(); + for (int i = 1; i < 7; i++) { + creatureCosts.put(i, 0); + } + final Predicate filter = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, + PaperCard.FN_GET_RULES); + for (final IPaperCard creature : Iterables.filter(deckList, filter)) { + int cmc = creature.getRules().getManaCost().getCMC(); + if (cmc < 1) { + cmc = 1; + } else if (cmc > 6) { + cmc = 6; + } + creatureCosts.put(cmc, creatureCosts.get(cmc) + 1); + } + + List creaturesToAdd = new ArrayList<>(); + for (final PaperCard card : creatures) { + int cmc = card.getRules().getManaCost().getCMC(); + if (cmc < 1) { + cmc = 1; + } else if (cmc > 6) { + cmc = 6; + } + final Integer currentAtCmc = creatureCosts.get(cmc); + boolean willAddCreature = false; + if (cmc <= 1 && currentAtCmc < targetCMCs.get(1)) { + willAddCreature = true; + } else if (cmc == 2 && currentAtCmc < targetCMCs.get(2)) { + willAddCreature = true; + } else if (cmc == 3 && currentAtCmc < targetCMCs.get(3)) { + willAddCreature = true; + } else if (cmc == 4 && currentAtCmc < targetCMCs.get(4)) { + willAddCreature = true; + } else if (cmc == 5 && currentAtCmc < targetCMCs.get(5)) { + willAddCreature = true; + } else if (cmc >= 6 && currentAtCmc < targetCMCs.get(6)) { + willAddCreature = true; + } + + if (willAddCreature) { + creaturesToAdd.add(card); + num--; + creatureCosts.put(cmc, creatureCosts.get(cmc) + 1); + if (logToConsole) { + System.out.println("Creature[" + num + "]:" + card.getName() + " (" + card.getRules().getManaCost() + ")"); + } + } else { + if (logToConsole) { + System.out.println(card.getName() + " not added because CMC " + card.getRules().getManaCost().getCMC() + + " has " + currentAtCmc + " already."); + } + } + if (num <= 0) { + break; + } + } + deckList.addAll(creaturesToAdd); + aiPlayables.removeAll(creaturesToAdd); + rankedColorList.removeAll(creaturesToAdd); + } + + /** + * Calculate average CMC. + * + * @param cards + * cards to choose from + * @return the average + */ + private static double getAverageCMC(final List cards) { + double sum = 0.0; + for (final IPaperCard cardPrinted : cards) { + sum += cardPrinted.getRules().getManaCost().getCMC(); + } + return sum / cards.size(); + } + + /** + * Calculate max CMC. + * + * @param cards + * cards to choose from + * @return the average + */ + private static int getMaxCMC(final List cards) { + int max = 0; + for (final IPaperCard cardPrinted : cards) { + if(cardPrinted.getRules().getManaCost().getCMC()>max) { + max = cardPrinted.getRules().getManaCost().getCMC(); + } + } + return max; + } + + /** + * @return the colors + */ + public ColorSet getColors() { + return colors; + } + + /** + * @param colors0 + * the colors to set + */ + public void setColors(final ColorSet colors0) { + colors = colors0; + } + + /** + * @return the aiPlayables + */ + public List getAiPlayables() { + return aiPlayables; + } + +}