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.

This commit is contained in:
austinio7116
2018-02-18 18:05:08 +00:00
committed by maustin
parent 4d5a1a152f
commit f237a8d29a
16 changed files with 1459 additions and 69 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
/**
* <p>
* Generate3ColorDeck class.
* </p>
*
* @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<ImmutablePair<FilterCMC, Integer>> 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<PaperCard> 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<String> 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;
}
}

View File

@@ -25,8 +25,12 @@ public class DecksComboBox extends FComboBoxWrapper<DeckType> {
addActionListener(getDeckTypeComboListener());
}
public void refresh(final DeckType deckType) {
setModel(new DefaultComboBoxModel<DeckType>(DeckType.ConstructedOptions));
public void refresh(final DeckType deckType, final boolean isForCommander) {
if(isForCommander){
setModel(new DefaultComboBoxModel<DeckType>(DeckType.CommanderOptions));
}else {
setModel(new DefaultComboBoxModel<DeckType>(DeckType.ConstructedOptions));
}
setSelectedItem(deckType);
}

View File

@@ -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
}

View File

@@ -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<DeckProxy> commanderDecks, FList<Object> deckList, String... initialItems) {
@@ -56,8 +58,8 @@ public class CLobby {
final Iterable<DeckProxy> 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;
}
}

View File

@@ -116,6 +116,8 @@ public class VLobby implements ILobbyView {
private final List<FList<Object>> tinyLeadersDeckLists = new ArrayList<FList<Object>>();
private final List<FPanel> tinyLeadersDeckPanels = new ArrayList<FPanel>(MAX_PLAYERS);
private final List<FDeckChooser> commanderDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FDeckChooser> tinyLeadersDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS);
private final List<FList<Object>> schemeDeckLists = new ArrayList<FList<Object>>();
private final List<FPanel> schemeDeckPanels = new ArrayList<FPanel>(MAX_PLAYERS);
@@ -133,6 +135,16 @@ public class VLobby implements ILobbyView {
private final Vector<Object> humanListData = new Vector<Object>();
private final Vector<Object> aiListData = new Vector<Object>();
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<DeckProxy> 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<DeckProxy> 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<FList<Object>> 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();
}

View File

@@ -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<CSubmenuGauntletBuild> {
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<String> lstRight = new FList<String>();
private final FScrollPane scrRight = new FScrollPane(lstRight, true,

View File

@@ -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<CSubmenuGauntletContes
private final SkinnedPanel pnlLoad = new SkinnedPanel(new MigLayout("insets 0, gap 0, wrap"));
private final ContestGauntletLister gauntletList = new ContestGauntletLister();
private final FDeckChooser lstDecks = new FDeckChooser(null, false);
private final FDeckChooser lstDecks = new FDeckChooser(null, false, GameType.Constructed, false);
private final FScrollPane scrLeft = new FScrollPane(gauntletList, false,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

View File

@@ -2,6 +2,7 @@ package forge.screens.home.gauntlet;
import forge.deck.DeckType;
import forge.deckchooser.FDeckChooser;
import forge.game.GameType;
import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID;
@@ -59,7 +60,7 @@ public enum VSubmenuGauntletQuick implements IVSubmenu<CSubmenuGauntletQuick> {
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();

View File

@@ -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;

View File

@@ -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<GameFormat,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>();
public static HashMap<String,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>();
public static void initialize(){
HashMap<String,List<Map.Entry<PaperCard,Integer>>> standardMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getStandard());
HashMap<String,List<Map.Entry<PaperCard,Integer>>> modernMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getModern());
if(standardMap==null || modernMap==null){
HashMap<String,List<Map.Entry<PaperCard,Integer>>> standardMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getStandard().getName());
HashMap<String,List<Map.Entry<PaperCard,Integer>>> modernMap = CardThemedMatrixIO.loadMatrix(FModel.getFormats().getModern().getName());
HashMap<String,List<Map.Entry<PaperCard,Integer>>> 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<String,List<Map.Entry<PaperCard,Integer>>> 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<String,List<Map.Entry<PaperCard,Integer>>> initializeCommanderFormat(){
IStorage<Deck> decks = new StorageImmediatelySerialized<Deck>("Generator", new DeckStorage(new File(ForgeConstants.DECK_GEN_DIR+ForgeConstants.PATH_SEPARATOR+DeckFormat.Commander.toString()),
ForgeConstants.DECK_GEN_DIR, false),
true);
final Iterable<PaperCard> cards = Iterables.filter(FModel.getMagicDb().getCommonCards().getUniqueCards()
, Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND_NOT_WASTES), PaperCard.FN_GET_RULES));
List<PaperCard> cardList = Lists.newArrayList(cards);
cardList.add(FModel.getMagicDb().getCommonCards().getCard("Wastes"));
Map<String, Integer> cardIntegerMap = new HashMap<>();
Map<Integer, PaperCard> integerCardMap = new HashMap<>();
Map<String, Integer> legendIntegerMap = new HashMap<>();
Map<Integer, PaperCard> integerLegendMap = new HashMap<>();
for (int i=0; i<cardList.size(); ++i){
cardIntegerMap.put(cardList.get(i).getName(),i);
integerCardMap.put(i,cardList.get(i));
}
List<PaperCard> legends = Lists.newArrayList(Iterables.filter(cardList,Predicates.compose(
new Predicate<CardRules>() {
@Override
public boolean apply(CardRules rules) {
return DeckFormat.Commander.isLegalCommander(rules);
}
}, PaperCard.FN_GET_RULES)));
for (int i=0; i<legends.size(); ++i){
legendIntegerMap.put(legends.get(i).getName(),i);
integerLegendMap.put(i,legends.get(i));
}
int[][] matrix = new int[legends.size()][cardList.size()];
for (PaperCard card:legends){
for (Deck deck:decks){
if (deck.getCommanders().size()==1&&deck.getCommanders().contains(card)){
for (PaperCard pairCard:Iterables.filter(deck.getMain().toFlatList(),
Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND_NOT_WASTES), PaperCard.FN_GET_RULES))){
if (!pairCard.getName().equals(card.getName())&&
new DeckGeneratorBase.MatchColorIdentity(card.getRules().getColorIdentity()).apply(pairCard.getRules())){
try {
int old = matrix[legendIntegerMap.get(card.getName())][cardIntegerMap.get(pairCard.getName())];
matrix[legendIntegerMap.get(card.getName())][cardIntegerMap.get(pairCard.getName())] = old + 1;
}catch (NullPointerException ne){
//Todo: Not sure what was failing here
}
}
}
}
}
}
HashMap<String,List<Map.Entry<PaperCard,Integer>>> 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<Map.Entry<PaperCard,Integer>> 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<PaperCard, Integer>(cardToAdd,distances[indices[cardList.size()-1-k]]));
};
/*if(excludeThisCard){
continue;
}*/
cardPools.put(card.getName(),deckPool);
}
}
return cardPools;
}
public static class ArrayIndexComparator implements Comparator<Integer>
{
private final Integer[] array;

View File

@@ -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<CardThemedDeckGenerator> {
public static List<DeckProxy> getMatrixDecks(GameFormat format, boolean isForAi){
final List<DeckProxy> decks = new ArrayList<DeckProxy>();
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<Car
public boolean isGeneratedDeck() {
return true;
}
public String getImageKey(boolean altState) {
/* Predicate<PaperCard> cardFilter = Predicates.and(format.getFilterPrinted(),PaperCard.Predicates.name(name));
List<PaperCard> cards=FModel.getMagicDb().getCommonCards().getAllCards(cardFilter);
return cards.get(cards.size()-1).getImageKey(altState);*/
return FModel.getMagicDb().getCommonCards().getUniqueByName(name).getImageKey(altState);
}
}

View File

@@ -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<CommanderDeckGenerator> {
public static List<DeckProxy> getCommanderDecks(DeckFormat format, boolean isForAi, boolean isCardGen){
ItemPool uniqueCards;
if(isCardGen){
uniqueCards = new ItemPool<PaperCard>(PaperCard.class);
Iterable<String> 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<CardRules> canPlay = isForAi ? DeckGeneratorBase.AI_CAN_PLAY : DeckGeneratorBase.HUMAN_CAN_PLAY;
@SuppressWarnings("unchecked")
Iterable<PaperCard> legends = Iterables.filter(uniqueCards.toFlatList(), Predicates.compose(Predicates.and(
new Predicate<CardRules>() {
@Override
public boolean apply(CardRules rules) {
return format.isLegalCommander(rules);
}
},
canPlay), PaperCard.FN_GET_RULES));
final List<DeckProxy> decks = new ArrayList<DeckProxy>();
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);
}
}

View File

@@ -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) {

View File

@@ -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<String> keys = new ArrayList<>(CardRelationMatrixGenerator.cardPools.get(format).keySet());
List<String> keys = new ArrayList<>(CardRelationMatrixGenerator.cardPools.get(format.getName()).keySet());
String randomKey = keys.get( random.nextInt(keys.size()) );
Predicate<PaperCard> 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<Map.Entry<PaperCard,Integer>> 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<Map.Entry<PaperCard,Integer>> potentialSecondCards = CardRelationMatrixGenerator.cardPools.get(format).get(secondKeycard.getName());
List<Map.Entry<PaperCard,Integer>> 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<Map.Entry<PaperCard,Integer>> 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<PaperCard> preSelectedCards = new ArrayList<>();
for(Map.Entry<PaperCard,Integer> pair:potentialCards){
preSelectedCards.add(pair.getKey());
}
//randomly remove cards
int removeCount=0;
int i=0;
List<PaperCard> 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<String> comColors = new ArrayList<String>(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<ManaCostShard, Integer> suggestBasicLandCount(Deck d) {
int W=0, U=0, R=0, B=0, G=0, total=0;
List<PaperCard> cards = d.getOrCreate(DeckSection.Main).toFlatList();

View File

@@ -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<String,List<Map.Entry<PaperCard,Integer>>> map){
public static void saveMatrix(String format, HashMap<String,List<Map.Entry<PaperCard,Integer>>> map){
File file = getMatrixFile(format);
ObjectOutputStream s = null;
try {
@@ -43,7 +43,7 @@ public class CardThemedMatrixIO {
}
}
public static HashMap<String,List<Map.Entry<PaperCard,Integer>>> loadMatrix(GameFormat format){
public static HashMap<String,List<Map.Entry<PaperCard,Integer>>> loadMatrix(String format){
try {
FileInputStream fin = new FileInputStream(getMatrixFile(format));
ObjectInputStream s = new ObjectInputStream(fin);

View File

@@ -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<CardRules> hasColor;
protected final List<PaperCard> availableList;
protected final List<PaperCard> aiPlayables;
protected final List<PaperCard> deckList = new ArrayList<>();
protected final List<String> setsWithBasicLands = new ArrayList<>();
protected List<PaperCard> rankedColorList;
// Views for aiPlayable
protected Iterable<PaperCard> onColorCreatures;
protected Iterable<PaperCard> onColorNonCreatures;
protected Iterable<PaperCard> 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<PaperCard> dList, boolean isForAI) {
super(FModel.getMagicDb().getCommonCards(), DeckFormat.Commander);
this.availableList = dList;
keyCard=keyCard0;
// remove Unplayables
if(isForAI) {
final Iterable<PaperCard> 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();
}
/**
* <p>
* buildDeck.
* </p>
*
* @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<PaperCard> 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<String> 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<PaperCard> 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<PaperCard> 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<PaperCard> 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<PaperCard> 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<String> 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<PaperCard> basicLands = Iterables.filter(aiPlayables, Predicates.compose(CardRulesPredicates.Presets.IS_BASIC_LAND, PaperCard.FN_GET_RULES));
final Set<PaperCard> snowLands = new HashSet<PaperCard>();
// 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<PaperCard> 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<PaperCard> lands = Iterables.filter(aiPlayables,
Predicates.compose(CardRulesPredicates.Presets.IS_NONBASIC_LAND, PaperCard.FN_GET_RULES));
List<PaperCard> 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<PaperCard> 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<PaperCard> rankedOthers = CardRanker.rankCardsInPack(others, deckList, colors, true);
List<PaperCard> 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<PaperCard> 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<PaperCard> others = Iterables.filter(aiPlayables,
Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES));
List <PaperCard> 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<PaperCard> nonCreatures, int num) {
List<PaperCard> 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<PaperCard> creatures, int num) {
List<PaperCard> 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<PaperCard> creatures, int num) {
// Add the deck card
if(keyCard.getRules().getMainPart().getType().isCreature()) {
keyCards = Iterables.filter(aiPlayables,PaperCard.Predicates.name(keyCard.getName()));
final List<PaperCard> keyCardList = Lists.newArrayList(keyCards);
deckList.addAll(keyCardList);
aiPlayables.removeAll(keyCardList);
rankedColorList.removeAll(keyCardList);
}
final Map<Integer,Integer> 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<Integer, Integer> creatureCosts = new HashMap<Integer, Integer>();
for (int i = 1; i < 7; i++) {
creatureCosts.put(i, 0);
}
final Predicate<PaperCard> 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<PaperCard> 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<PaperCard> 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<PaperCard> 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<PaperCard> getAiPlayables() {
return aiPlayables;
}
}