* Better sideboarding: decks have no extra fields for changed parts, they are copied on sideboarding, new deck composition moved outside of GuiChoose.java

* Decks have no extra sections for variants (sideboard works in most cases)
* Deck Format restrictions are in a separate class not related to GameType
+ DeckSection has its own toString overload
+ DualListBox operates CardPrinted properly (sideboarding dialog uses cardprinted instances)
+ ItemPool.remove method returns true if any elements were removed.
This commit is contained in:
Maxmtg
2013-01-13 17:19:07 +00:00
parent b983efb5a5
commit cc8c27ecb6
26 changed files with 395 additions and 430 deletions

1
.gitattributes vendored
View File

@@ -13476,6 +13476,7 @@ src/main/java/forge/control/package-info.java -text
src/main/java/forge/deck/CardCollections.java -text
src/main/java/forge/deck/Deck.java svneol=native#text/plain
src/main/java/forge/deck/DeckBase.java -text
src/main/java/forge/deck/DeckFormat.java -text
src/main/java/forge/deck/DeckGroup.java -text
src/main/java/forge/deck/DeckRecognizer.java -text
src/main/java/forge/deck/DeckSection.java -text

View File

@@ -59,17 +59,9 @@ public class Deck extends DeckBase {
*/
private static final long serialVersionUID = -7478025567887481994L;
private transient boolean isEdited = false;
private final DeckSection main;
private final DeckSection sideboard;
private final DeckSection planes;
private final DeckSection schemes;
private transient DeckSection mainEdited;
private transient DeckSection sideboardEdited;
private final DeckSection sideboard; // commander, planes or schemes are also stored here
private CardPrinted avatar;
private CardPrinted commander;
// gameType is from Constant.GameType, like GameType.Regular
/**
@@ -90,12 +82,7 @@ public class Deck extends DeckBase {
super(name0);
this.main = new DeckSection();
this.sideboard = new DeckSection();
this.mainEdited = new DeckSection();
this.sideboardEdited = new DeckSection();
this.avatar = null;
this.commander = null;
this.planes = new DeckSection();
this.schemes = new DeckSection();
}
/**
@@ -116,26 +103,13 @@ public class Deck extends DeckBase {
return this.getName();
}
/**
* <p>
* Getter for the field <code>main</code>.
* </p>
*
* @return a {@link java.util.List} object.
*/
public DeckSection getMain() {
return isEdited ? this.mainEdited : this.main;
return this.main;
}
/**
* <p>
* Getter for the field <code>sideboard</code>.
* </p>
*
* @return a {@link java.util.List} object.
*/
// variants' extra deck sections used instead of sideboard (planes, commander, schemes) are here as well as usual sideboards
public DeckSection getSideboard() {
return isEdited ? this.sideboardEdited : this.sideboard;
return this.sideboard;
}
/*
@@ -145,7 +119,7 @@ public class Deck extends DeckBase {
*/
@Override
public ItemPoolView<CardPrinted> getCardPool() {
return isEdited ? this.mainEdited : this.main;
return this.main;
}
/* (non-Javadoc)
@@ -158,13 +132,6 @@ public class Deck extends DeckBase {
result.main.addAll(this.main);
result.sideboard.addAll(this.sideboard);
result.avatar = this.avatar;
result.commander = this.commander;
//This if clause is really only necessary when cloning decks that were
//around before schemes.
if (this.schemes != null) {
result.schemes.addAll(this.schemes);
}
}
/*
@@ -219,14 +186,14 @@ public class Deck extends DeckBase {
d.getMain().set(Deck.readCardList(sections.get("main")));
d.getSideboard().set(Deck.readCardList(sections.get("sideboard")));
List<String> cmd = Deck.readCardList(sections.get("commander"));
String cmdName = cmd.isEmpty() ? null : cmd.get(0);
d.commander = CardDb.instance().isCardSupported(cmdName) ? CardDb.instance().getCard(cmdName) : null;
// try also earlier deck formats
if ( d.getSideboard().isEmpty() ) { d.getSideboard().set(Deck.readCardList(sections.get("schemes"))); }
if ( d.getSideboard().isEmpty() ) { d.getSideboard().set(Deck.readCardList(sections.get("planes"))); }
if ( d.getSideboard().isEmpty() ) { d.getSideboard().set(Deck.readCardList(sections.get("commander"))); }
List<String> av = Deck.readCardList(sections.get("avatar"));
String avName = av.isEmpty() ? null : av.get(0);
d.avatar = CardDb.instance().isCardSupported(avName) ? CardDb.instance().getCard(avName) : null;
d.getPlanes().set(Deck.readCardList(sections.get("planes")));
d.getSchemes().set(Deck.readCardList(sections.get("schemes")));
return d;
}
@@ -303,20 +270,10 @@ public class Deck extends DeckBase {
out.add(String.format("%s", "[sideboard]"));
out.addAll(Deck.writeCardPool(this.getSideboard()));
if (getCommander() != null) {
out.add(String.format("%s", "[commander]"));
out.add(Deck.serializeSingleCard(getCommander(), 1));
}
if (getAvatar() != null) {
out.add(String.format("%s", "[avatar]"));
out.add(Deck.serializeSingleCard(getAvatar(), 1));
}
out.add(String.format("%s", "[planes]"));
out.addAll(Deck.writeCardPool(this.getPlanes()));
out.add(String.format("%s", "[schemes]"));
out.addAll(Deck.writeCardPool(this.getSchemes()));
return out;
}
@@ -324,22 +281,9 @@ public class Deck extends DeckBase {
* @return the commander
*/
public CardPrinted getCommander() {
return commander;
return sideboard != null && !sideboard.isEmpty() ? sideboard.get(0) : null;
}
/**
* @return the planes
*/
public DeckSection getPlanes() {
return planes;
}
/**
* @return the schemes
*/
public DeckSection getSchemes() {
return schemes;
}
/**
* @return the avatar
@@ -354,40 +298,4 @@ public class Deck extends DeckBase {
return arg1.getName();
}
};
public void clearDeckEdits() {
isEdited = false;
if (mainEdited != null) {
mainEdited.clear();
} else {
mainEdited = new DeckSection();
}
if (sideboardEdited != null) {
sideboardEdited.clear();
} else {
sideboardEdited = new DeckSection();
}
}
public void startDeckEdits() {
isEdited = true;
if (mainEdited.countAll() == 0) {
mainEdited.add(main.toFlatList());
}
if (sideboardEdited.countAll() == 0) {
sideboardEdited.add(sideboard.toFlatList());
}
}
public DeckSection getOriginalMain() {
return this.main;
}
public DeckSection getOriginalSideboard() {
return this.sideboard;
}
public boolean isEditedDeck() {
return this.isEdited;
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.lang.math.IntRange;
import forge.card.CardCoreType;
import forge.item.CardDb;
import forge.item.CardPrinted;
import forge.util.Aggregates;
/**
* GameType is an enum to determine the type of current game. :)
*/
public enum DeckFormat {
// Main board: allowed size SB: restriction Max distinct non basic cards
Constructed ( new IntRange(60, Integer.MAX_VALUE), new IntRange(15), 4),
Limited ( new IntRange(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE),
Commander ( new IntRange(99 /* commndr in SB*/), new IntRange(1), 1),
Vanguard ( new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4),
Planechase ( new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4),
Archenemy ( new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4);
private final IntRange mainRange;
private final IntRange sideRange; // null => no check
private final int maxCardCopies;
/**
* Instantiates a new game type.
*
* @param isLimited
* the is limited
*/
DeckFormat(IntRange main, IntRange side, int maxCopies) {
mainRange = main;
sideRange = side;
maxCardCopies = maxCopies;
}
/**
* Smart value of.
*
* @param value the value
* @param defaultValue the default value
* @return the game type
*/
public static DeckFormat smartValueOf(final String value, DeckFormat defaultValue) {
if (null == value) {
return defaultValue;
}
final String valToCompate = value.trim();
for (final DeckFormat v : DeckFormat.values()) {
if (v.name().compareToIgnoreCase(valToCompate) == 0) {
return v;
}
}
throw new IllegalArgumentException("No element named " + value + " in enum GameType");
}
/**
* @return the sideRange
*/
public IntRange getSideRange() {
return sideRange;
}
/**
* @return the mainRange
*/
public IntRange getMainRange() {
return mainRange;
}
/**
* @return the maxCardCopies
*/
public int getMaxCardCopies() {
return maxCardCopies;
}
@SuppressWarnings("incomplete-switch")
public String getDeckConformanceProblem(Deck deck) {
int deckSize = deck.getMain().countAll();
int min = getMainRange().getMinimumInteger();
int max = getMainRange().getMaximumInteger();
if (deckSize < min) {
return String.format("should have a minimum of %d cards", min);
}
if (deckSize > max) {
return String.format("should not exceed a maximum of %d cards", max);
}
switch(this) {
case Commander: //Must contain exactly 1 legendary Commander and no sideboard.
//TODO:Enforce color identity
if (null == deck.getCommander()) {
return "is missing a commander";
}
if (!deck.getCommander().getCard().getType().isLegendary()) {
return "has a commander that is not a legendary creature";
}
//No sideboarding in Commander
if (!deck.getSideboard().isEmpty()) {
return "has sideboard";
}
break;
case Planechase: //Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton.
if (deck.getSideboard().countAll() < 10) {
return "should gave at least 10 planes";
}
int phenoms = 0;
for (Entry<CardPrinted, Integer> cp : deck.getSideboard()) {
if (cp.getKey().getCard().getType().typeContains(CardCoreType.Phenomenon)) {
phenoms++;
}
if (cp.getValue() > 1) {
return "must not contain multiple copies of any Phenomena";
}
}
if (phenoms > 2) {
return "must not contain more than 2 Phenomena";
}
break;
case Archenemy: //Must contain at least 20 schemes, max 2 of each.
if (deck.getSideboard().countAll() < 20) {
return "must contain at least 20 schemes";
}
for (Entry<CardPrinted, Integer> cp : deck.getSideboard()) {
if (cp.getValue() > 2) {
return "must not contain more than 2 copies of any Scheme";
}
}
break;
}
int maxCopies = getMaxCardCopies();
if (maxCopies < Integer.MAX_VALUE) {
//Must contain no more than 4 of the same card
//shared among the main deck and sideboard, except
//basic lands and Relentless Rats
DeckSection tmp = new DeckSection(deck.getMain());
tmp.addAll(deck.getSideboard());
if (null != deck.getCommander() && this == Commander) {
tmp.add(deck.getCommander());
}
List<String> limitExceptions = Arrays.asList("Relentless Rats");
// should group all cards by name, so that different editions of same card are really counted as the same card
for (Entry<String, Integer> cp : Aggregates.groupSumBy(tmp, CardPrinted.FN_GET_NAME)) {
CardPrinted simpleCard = CardDb.instance().getCard(cp.getKey());
boolean canHaveMultiple = simpleCard.getCard().getType().isBasicLand() || limitExceptions.contains(cp.getKey());
if (!canHaveMultiple && cp.getValue() > maxCopies) {
return String.format("must not contain more than %d of '%s' card", maxCopies, cp.getKey());
}
}
// The sideboard must contain either 0 or 15 cards
int sideboardSize = deck.getSideboard().countAll();
IntRange sbRange = getSideRange();
if (sbRange != null && sideboardSize > 0 && !sbRange.containsInteger(sideboardSize)) {
return sbRange.getMinimumInteger() == sbRange.getMaximumInteger()
? String.format("must have a sideboard of %d cards or no sideboard at all", sbRange.getMaximumInteger())
: String.format("must have a sideboard of %d to %d cards or no sideboard at all", sbRange.getMinimumInteger(), sbRange.getMaximumInteger());
}
}
return null;
}
}

View File

@@ -128,4 +128,33 @@ public class DeckSection extends ItemPool<CardPrinted> {
this.add(CardDb.instance().getCard(cardName));
}
/**
* returns n-th card from this DeckSection. LINEAR time.
* @param i
* @return
*/
public CardPrinted get(int n) {
for(Entry<CardPrinted, Integer> e : this)
{
n -= e.getValue();
if ( n <= 0 ) return e.getKey();
}
return null;
}
@Override
public String toString() {
if (this.isEmpty()) return "[]";
boolean isFirst = true;
StringBuilder sb = new StringBuilder();
sb.append('[');
for (Entry<CardPrinted, Integer> e : this) {
if ( isFirst ) isFirst = false;
else sb.append(", ");
sb.append(e.getValue()).append(" x ").append(e.getKey().getName());
}
return sb.append(']').toString();
}
}

View File

@@ -320,9 +320,9 @@ public class DeckgenUtil {
int attemptsLeft = 100; // to avoid endless loop
while (schemesToAdd > 0 && attemptsLeft > 0) {
CardPrinted cp = Aggregates.random(allSchemes);
int appearances = res.getSchemes().count(cp) + 1;
int appearances = res.getSideboard().count(cp) + 1;
if (appearances < 2) {
res.getSchemes().add(cp);
res.getSideboard().add(cp);
schemesToAdd--;
} else {
attemptsLeft--;

View File

@@ -17,7 +17,7 @@
*/
package forge.deck.io;
import forge.game.GameType;
import forge.deck.DeckFormat;
import forge.game.player.PlayerType;
import forge.util.FileSection;
@@ -39,7 +39,7 @@ public class DeckFileHeader {
private static final String CSTM_POOL = "Custom Pool";
private static final String PLAYER_TYPE = "PlayerType";
private final GameType deckType;
private final DeckFormat deckType;
private final PlayerType playerType;
private final boolean customPool;
@@ -55,11 +55,10 @@ public class DeckFileHeader {
public DeckFileHeader(final FileSection kvPairs) {
this.name = kvPairs.get(DeckFileHeader.NAME);
this.comment = kvPairs.get(DeckFileHeader.COMMENT);
this.deckType = GameType.smartValueOf(kvPairs.get(DeckFileHeader.DECK_TYPE), GameType.Constructed);
this.deckType = DeckFormat.smartValueOf(kvPairs.get(DeckFileHeader.DECK_TYPE), DeckFormat.Constructed);
this.customPool = kvPairs.getBoolean(DeckFileHeader.CSTM_POOL);
this.playerType = "computer".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER))
|| "ai".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER_TYPE)) ? PlayerType.COMPUTER
: PlayerType.HUMAN;
boolean isForAi = "computer".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER)) || "ai".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER_TYPE));
this.playerType = isForAi ? PlayerType.COMPUTER : PlayerType.HUMAN;
}
/**
@@ -103,7 +102,7 @@ public class DeckFileHeader {
*
* @return the deck type
*/
public final GameType getDeckType() {
public final DeckFormat getDeckType() {
return this.deckType;
}

View File

@@ -214,7 +214,7 @@ public class OldDeckParser {
}
break;
case Sealed:
case Limited:
final boolean isAi = dh.getPlayerType() == PlayerType.COMPUTER;
name = name.startsWith("AI_") ? name.replace("AI_", "") : name;

View File

@@ -138,12 +138,6 @@ public class GameNew {
final Random generator = MyRandom.getRandom();
boolean useAnte = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ANTE);
if (!Singletons.getModel().getMatch().getPlayedGames().isEmpty()) {
deck.startDeckEdits();
sideboard(player, deck, canRandomFoil, generator, useAnte);
} else {
deck.clearDeckEdits();
}
prepareGameLibrary(player, deck, removedAnteCards, rAICards, canRandomFoil, generator, useAnte);
// Shuffling
@@ -157,42 +151,54 @@ public class GameNew {
}
}
private static boolean sideboard(final Player player, final Deck deck, boolean canRandomFoil, Random generator, boolean useAnte) {
private static Deck sideboard(final Player player, final Deck deck) {
final GameType gameType = Singletons.getModel().getMatch().getGameType();
boolean hasSideboard = (deck.getSideboard().countAll() > 0);
boolean deckHasSideboard = (deck.getSideboard().countAll() > 0);
DeckSection sideboard = deck.getSideboard();
int sideboardSize = (gameType == GameType.Draft || gameType == GameType.Sealed) ? -1 : sideboard.countAll();
if (!hasSideboard) {
return false;
if (!deckHasSideboard || !gameType.isSideboardingAllowed()) {
return deck;
}
if (player.isComputer()) {
if (player.isComputer())
// Here is where the AI could sideboard, but needs to gather hints during the first game about what to SB
return deck;
return false;
} else {
// Human Sideboarding
boolean validDeck = false;
int deckMinSize = Math.min(deck.getMain().countAll(), gameType.getMainRange().getMinimumInteger());
// Human Sideboarding
while (!validDeck) {
GuiChoose.getOrderChoices("Sideboard", "Main Deck", sideboardSize,
deck.getSideboard().toForgeCardList(), deck.getMain().toForgeCardList(), null, true, deck);
int deckMinSize = Math.min(deck.getMain().countAll(), gameType.getDecksFormat().getMainRange().getMinimumInteger());
//IntRange sbRange = gameType.getDecksFormat().getSideRange();
int sideboardSize = sideboard.countAll();
if (deck.getMain().countAll() >= deckMinSize) {
validDeck = true;
} else {
StringBuilder errMsg = new StringBuilder("Too few cards in your main deck (minimum ");
errMsg.append(deckMinSize);
errMsg.append("), please make modifications to your deck again.");
JOptionPane.showMessageDialog(null, errMsg.toString(), "Invalid deck", JOptionPane.ERROR_MESSAGE);
}
DeckSection newSb = new DeckSection();
List<CardPrinted> newMain = null;
while (newMain == null || newMain.size() < deckMinSize) {
if ( newMain != null ) {
String errMsg = String.format("Too few cards in your main deck (minimum %d), please make modifications to your deck again.", deckMinSize);
JOptionPane.showMessageDialog(null, errMsg, "Invalid deck", JOptionPane.ERROR_MESSAGE);
}
newMain = GuiChoose.getOrderChoices("Sideboard", "Main Deck", sideboardSize, deck.getSideboard().toFlatList(), deck.getMain().toFlatList(), null, true);
}
return true;
newSb.clear();
newSb.addAll(deck.getMain());
newSb.addAll(deck.getSideboard());
for(CardPrinted c : newMain)
newSb.remove(c);
Deck res = (Deck) deck.copyTo(deck.getName());
res.getMain().clear();
res.getMain().add(newMain);
res.getSideboard().clear();
res.getSideboard().addAll(newSb);
return res;
}
/**
@@ -252,7 +258,16 @@ public class GameNew {
}
prepareSingleLibrary(player, p.getValue().getDeck(), removedAnteCards, rAICards, canRandomFoil);
Deck toUse;
boolean isFirstGame = Singletons.getModel().getMatch().getPlayedGames().isEmpty();
if (!isFirstGame) {
toUse = sideboard(player, p.getValue().getCurrentDeck());
} else {
p.getValue().restoreOriginalDeck();
toUse = p.getValue().getCurrentDeck();
}
prepareSingleLibrary(player, toUse, removedAnteCards, rAICards, canRandomFoil);
player.updateObservers();
bf.updateObservers();
player.getZone(ZoneType.Hand).updateObservers();

View File

@@ -1,229 +1,51 @@
/*
* 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.game;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
import forge.deck.DeckFormat;
import org.apache.commons.lang.math.IntRange;
import forge.card.CardCoreType;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.item.CardDb;
import forge.item.CardPrinted;
import forge.util.Aggregates;
/**
* GameType is an enum to determine the type of current game. :)
/**
* TODO: Write javadoc for this type.
*
*/
public enum GameType {
// Limited Main board: allowed size SB: allowed size Max distinct non basic cards
Constructed ( false, new IntRange(60, Integer.MAX_VALUE), new IntRange(15), 4),
Sealed ( true, new IntRange(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE),
Draft ( true, new IntRange(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE),
Commander ( false, new IntRange(99 /* +cmndr aside */), new IntRange(0), 1),
Quest ( true, new IntRange(40, Integer.MAX_VALUE), new IntRange(15), 4),
Vanguard ( false, new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4),
Planechase ( false, new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4),
Archenemy ( false, new IntRange(60, Integer.MAX_VALUE), new IntRange(0), 4),
Gauntlet ( true, new IntRange(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE);
// deck composition rules, isPoolRestricted, can sideboard between matches
Sealed ( DeckFormat.Limited, true, true ),
Draft ( DeckFormat.Limited, true, true ),
Gauntlet ( DeckFormat.Limited, true, true ),
Quest ( DeckFormat.Constructed, true, true ),
Constructed ( DeckFormat.Constructed, false, true ),
Archenemy ( DeckFormat.Archenemy, false, false ),
Planechase ( DeckFormat.Planechase, false, false ),
Vanguard ( DeckFormat.Vanguard, true, true );
private final boolean bLimited;
private final IntRange mainRange;
private final IntRange sideRange; // null => no check
private final int maxCardCopies;
/**
* Checks if is limited.
*
* @return true, if is limited
*/
public final boolean isLimited() {
return this.bLimited;
}
/**
* Instantiates a new game type.
*
* @param isLimited
* the is limited
*/
GameType(final boolean isLimited, IntRange main, IntRange side, int maxCopies) {
this.bLimited = isLimited;
mainRange = main;
sideRange = side;
maxCardCopies = maxCopies;
private final DeckFormat decksFormat;
private final boolean bCardpoolLimited;
private final boolean canSideboard;
GameType(DeckFormat formatType, boolean isDeckBuilderLimited, boolean sideboardingAllowed ) {
bCardpoolLimited = isDeckBuilderLimited;
decksFormat = formatType;
canSideboard = sideboardingAllowed;
}
/**
* Smart value of.
*
* @param value the value
* @param defaultValue the default value
* @return the game type
* @return the decksFormat
*/
public static GameType smartValueOf(final String value, GameType defaultValue) {
if (null == value) {
return defaultValue;
}
final String valToCompate = value.trim();
for (final GameType v : GameType.values()) {
if (v.name().compareToIgnoreCase(valToCompate) == 0) {
return v;
}
}
throw new IllegalArgumentException("No element named " + value + " in enum GameType");
public DeckFormat getDecksFormat() {
return decksFormat;
}
/**
* @return the sideRange
* @return the isCardpoolLimited
*/
public IntRange getSideRange() {
return sideRange;
public boolean isCardpoolLimited() {
return bCardpoolLimited;
}
/**
* @return the mainRange
* @return the canSideboard
*/
public IntRange getMainRange() {
return mainRange;
}
/**
* @return the maxCardCopies
*/
public int getMaxCardCopies() {
return maxCardCopies;
}
@SuppressWarnings("incomplete-switch")
public String getDeckConformanceProblem(Deck deck) {
int deckSize = deck.getMain().countAll();
int min = getMainRange().getMinimumInteger();
int max = getMainRange().getMaximumInteger();
if (deckSize < min) {
return String.format("should have a minimum of %d cards", min);
}
if (deckSize > max) {
return String.format("should not exceed a maximum of %d cards", max);
}
switch(this) {
case Commander: //Must contain exactly 1 legendary Commander and no sideboard.
//TODO:Enforce color identity
if (null == deck.getCommander()) {
return "is missing a commander";
}
if (!deck.getCommander().getCard().getType().isLegendary()) {
return "has a commander that is not a legendary creature";
}
//No sideboarding in Commander
if (!deck.getSideboard().isEmpty()) {
return "has sideboard";
}
break;
case Planechase: //Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton.
if (deck.getPlanes().countAll() < 10) {
return "should gave at least 10 planes";
}
int phenoms = 0;
for (Entry<CardPrinted, Integer> cp : deck.getPlanes()) {
if (cp.getKey().getCard().getType().typeContains(CardCoreType.Phenomenon)) {
phenoms++;
}
if (cp.getValue() > 1) {
return "must not contain multiple copies of any Phenomena";
}
}
if (phenoms > 2) {
return "must not contain more than 2 Phenomena";
}
break;
case Archenemy: //Must contain at least 20 schemes, max 2 of each.
if (deck.getSchemes().countAll() < 20) {
return "must contain at least 20 schemes";
}
for (Entry<CardPrinted, Integer> cp : deck.getSchemes()) {
if (cp.getValue() > 2) {
return "must not contain more than 2 copies of any Scheme";
}
}
break;
}
int maxCopies = getMaxCardCopies();
if (maxCopies < Integer.MAX_VALUE) {
//Must contain no more than 4 of the same card
//shared among the main deck and sideboard, except
//basic lands and Relentless Rats
DeckSection tmp = new DeckSection(deck.getMain());
tmp.addAll(deck.getSideboard());
if (null != deck.getCommander()) {
tmp.add(deck.getCommander());
}
List<String> limitExceptions = Arrays.asList("Relentless Rats");
// should group all cards by name, so that different editions of same card are really counted as the same card
for (Entry<String, Integer> cp : Aggregates.groupSumBy(tmp, CardPrinted.FN_GET_NAME)) {
CardPrinted simpleCard = CardDb.instance().getCard(cp.getKey());
boolean canHaveMultiple = simpleCard.getCard().getType().isBasicLand() || limitExceptions.contains(cp.getKey());
if (!canHaveMultiple && cp.getValue() > maxCopies) {
return String.format("must not contain more than %d of '%s' card", maxCopies, cp.getKey());
}
}
// The sideboard must contain either 0 or 15 cards
int sideboardSize = deck.getSideboard().countAll();
IntRange sbRange = getSideRange();
if (sbRange != null && sideboardSize > 0 && !sbRange.containsInteger(sideboardSize)) {
return sbRange.getMinimumInteger() == sbRange.getMaximumInteger()
? String.format("must have a sideboard of %d cards or no sideboard at all", sbRange.getMaximumInteger())
: String.format("must have a sideboard of %d to %d cards or no sideboard at all", sbRange.getMinimumInteger(), sbRange.getMaximumInteger());
}
}
return null;
public boolean isSideboardingAllowed() {
return canSideboard;
}
}

View File

@@ -257,9 +257,15 @@ public class MatchController {
*/
public Deck getPlayersDeck(LobbyPlayer lobbyPlayer) {
PlayerStartConditions cond = players.get(lobbyPlayer);
return cond == null ? null : cond.getDeck();
return cond == null ? null : cond.getCurrentDeck();
}
public Deck getPlayersOriginalDeck(LobbyPlayer lobbyPlayer) {
PlayerStartConditions cond = players.get(lobbyPlayer);
return cond == null ? null : cond.getOriginalDeck();
}
public Map<LobbyPlayer, PlayerStartConditions> getPlayers() {
return players;
}

View File

@@ -7,7 +7,9 @@ import forge.deck.Deck;
public class PlayerStartConditions {
private final Deck deck;
private final Deck originalDeck;
private Deck currentDeck;
private int startingLife = 20;
private int startingHand = 7;
private Supplier<Iterable<Card>> cardsOnBattlefield = null;
@@ -15,12 +17,18 @@ public class PlayerStartConditions {
private Supplier<Iterable<Card>> schemes = null;
public PlayerStartConditions(Deck deck0) {
deck = deck0;
originalDeck = deck0;
currentDeck = originalDeck;
}
public final Deck getDeck() {
return deck;
public final Deck getOriginalDeck() {
return originalDeck;
}
public final Deck getCurrentDeck() {
return currentDeck;
}
public final int getStartingLife() {
return startingLife;
}
@@ -78,5 +86,12 @@ public class PlayerStartConditions {
this.schemes = s;
}
/**
* TODO: Write javadoc for this method.
*/
public void restoreOriginalDeck() {
currentDeck = originalDeck;
}
}

View File

@@ -24,8 +24,8 @@ import forge.Card;
import forge.Singletons;
import forge.card.spellability.SpellAbility;
import forge.control.input.Input;
import forge.game.GameState;
import forge.game.GameType;
import forge.game.GameState;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.match.CMatchUI;

View File

@@ -23,6 +23,7 @@ import forge.gui.toolbox.FList;
import forge.gui.toolbox.FPanel;
import forge.gui.toolbox.FScrollPane;
import forge.gui.toolbox.FSkin;
import forge.item.CardPrinted;
// An input box for handling the order of choices.
// Left box has the original choices
@@ -168,6 +169,8 @@ public class DualListBox<T> extends FPanel {
card = (Card) obj;
} else if (obj instanceof SpellAbility) {
card = ((SpellAbility) obj).getSourceCard();
} else if (obj instanceof CardPrinted) {
card = ((CardPrinted) obj).toForgeCard();
}
GuiUtils.clearPanelSelections();

View File

@@ -13,7 +13,6 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import forge.Card;
import forge.deck.Deck;
import forge.gui.match.CMatchUI;
import forge.item.InventoryItem;
@@ -205,11 +204,11 @@ public class GuiChoose {
public static <T> List<T> getOrderChoices(final String title, final String top, int remainingObjects,
final List<T> sourceChoices, List<T> destChoices, Card referenceCard) {
return getOrderChoices(title, top, remainingObjects, sourceChoices, destChoices, referenceCard, false, null);
return getOrderChoices(title, top, remainingObjects, sourceChoices, destChoices, referenceCard, false);
}
public static <T> List<T> getOrderChoices(final String title, final String top, int remainingObjects,
final List<T> sourceChoices, List<T> destChoices, Card referenceCard, boolean sideboardingMode, Deck persistentDeck) {
final List<T> sourceChoices, List<T> destChoices, Card referenceCard, boolean sideboardingMode) {
// An input box for handling the order of choices.
final JFrame frame = new JFrame();
DualListBox<T> dual = new DualListBox<T>(remainingObjects, top, sourceChoices, destChoices, referenceCard, sideboardingMode);
@@ -235,13 +234,6 @@ public class GuiChoose {
List<T> objects = dual.getOrderedList();
if (sideboardingMode) {
persistentDeck.getMain().clear();
persistentDeck.getMain().add((List<Card>) dual.getOrderedList());
persistentDeck.getSideboard().clear();
persistentDeck.getSideboard().add((List<Card>) dual.getRemainingSourceList());
}
dialog.dispose();
GuiUtils.clearPanelSelections();
return objects;

View File

@@ -126,7 +126,7 @@ public final class CEditorScheme extends ACEditorBase<CardPrinted, Deck> {
schemes.add(cp);
}
this.getTableCatalog().setDeck(ItemPool.createFrom(schemes, CardPrinted.class));
this.getTableDeck().setDeck(this.controller.getModel().getSchemes());
this.getTableDeck().setDeck(this.controller.getModel().getSideboard());
}
/*

View File

@@ -337,7 +337,7 @@ public class SSubmenuQuestUtil {
System.out.println(msg);
return;
}
String errorMessage = GameType.Quest.getDeckConformanceProblem(deck);
String errorMessage = GameType.Quest.getDecksFormat().getDeckConformanceProblem(deck);
if (null != errorMessage) {
JOptionPane.showMessageDialog(null, "Your deck " + errorMessage + " Please edit or choose a different deck.", "Invalid deck", JOptionPane.ERROR_MESSAGE);
return;

View File

@@ -11,6 +11,7 @@ import forge.Command;
import forge.Singletons;
import forge.control.Lobby;
import forge.deck.Deck;
import forge.deck.DeckFormat;
import forge.game.GameType;
import forge.game.MatchController;
import forge.game.MatchStartHelper;
@@ -98,7 +99,7 @@ public enum CSubmenuConstructed implements ICDoc {
Deck humanDeck = VSubmenuConstructed.SINGLETON_INSTANCE.getDcHuman().getDeck();
if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) {
String errorMessage = GameType.Constructed.getDeckConformanceProblem(humanDeck);
String errorMessage = DeckFormat.Constructed.getDeckConformanceProblem(humanDeck);
if (null != errorMessage) {
JOptionPane.showMessageDialog(null, "Your deck " + errorMessage, "Invalid deck", JOptionPane.ERROR_MESSAGE);
return;

View File

@@ -14,6 +14,7 @@ import forge.Singletons;
import forge.control.FControl;
import forge.control.Lobby;
import forge.deck.Deck;
import forge.deck.DeckFormat;
import forge.deck.DeckGroup;
import forge.game.GameType;
import forge.game.MatchController;
@@ -100,7 +101,7 @@ public enum CSubmenuDraft implements ICDoc {
"No deck selected for human!\r\n(You may need to build a new deck.)",
"No deck", JOptionPane.ERROR_MESSAGE);
return;
} else if (null != GameType.Draft.getDeckConformanceProblem(humanDeck)) {
} else if (null != DeckFormat.Limited.getDeckConformanceProblem(humanDeck)) {
JOptionPane.showMessageDialog(null,
"The selected deck doesn't have enough cards to play (minimum 40)."
+ "\r\nUse the deck editor to choose the cards you want before starting.",

View File

@@ -19,6 +19,7 @@ import forge.Singletons;
import forge.control.FControl;
import forge.deck.Deck;
import forge.deck.DeckBase;
import forge.deck.DeckFormat;
import forge.deck.DeckGroup;
import forge.game.limited.ReadDraftRankings;
import forge.game.GameType;
@@ -112,7 +113,7 @@ public enum CSubmenuSealed implements ICDoc {
"Please build and/or select a deck for yourself.",
"No deck", JOptionPane.ERROR_MESSAGE);
return;
} else if (null != GameType.Sealed.getDeckConformanceProblem(human)) {
} else if (null != DeckFormat.Limited.getDeckConformanceProblem(human)) {
JOptionPane.showMessageDialog(null,
"The selected deck doesn't have enough cards to play (minimum 40)."
+ "\r\nUse the deck editor to choose the cards you want before starting.",
@@ -125,7 +126,6 @@ public enum CSubmenuSealed implements ICDoc {
Singletons.getModel().getGauntletMini().launch(matches, human, GameType.Sealed);
}
/** */
@SuppressWarnings("unchecked")
private <T extends DeckBase> void setupSealed() {

View File

@@ -4,13 +4,9 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import com.google.common.collect.Iterables;
import forge.Command;
import forge.GameActionUtil;
import forge.Singletons;
@@ -31,6 +27,7 @@ import forge.gui.toolbox.FDeckChooser;
import forge.item.CardPrinted;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.util.Aggregates;
/**
* Controls the constructed submenu in the home UI.
@@ -42,7 +39,7 @@ public enum CSubmenuArchenemy implements ICDoc {
/** */
SINGLETON_INSTANCE;
private final VSubmenuArchenemy view = VSubmenuArchenemy.SINGLETON_INSTANCE;
/* (non-Javadoc)
* @see forge.gui.home.ICSubmenu#initialize()
@@ -127,8 +124,6 @@ public enum CSubmenuArchenemy implements ICDoc {
final SwingWorker<Object, Void> worker = new SwingWorker<Object, Void>() {
@Override
public Object doInBackground() {
Random rnd = new Random();
String nl = System.getProperty("line.separator");
boolean usedDefaults = false;
List<Deck> playerDecks = new ArrayList<Deck>();
@@ -147,12 +142,12 @@ public enum CSubmenuArchenemy implements ICDoc {
Object obj = view.getArchenemySchemes().getSelectedValue();
boolean useDefault = VSubmenuArchenemy.SINGLETON_INSTANCE.getCbUseDefaultSchemes().isSelected();
useDefault &= !playerDecks.get(0).getSchemes().isEmpty();
useDefault &= !playerDecks.get(0).getSideboard().isEmpty();
System.out.println(useDefault);
if (useDefault) {
schemes = playerDecks.get(0).getSchemes().toFlatList();
schemes = playerDecks.get(0).getSideboard().toFlatList();
System.out.println(schemes.toString());
usedDefaults = true;
@@ -162,14 +157,14 @@ public enum CSubmenuArchenemy implements ICDoc {
String sel = (String) obj;
if (sel.equals("Random")) {
schemes = Iterables.get(view.getAllSchemeDecks(), rnd.nextInt(Iterables.size(view.getAllSchemeDecks()))).getSchemes().toFlatList();
schemes = Aggregates.random(view.getAllSchemeDecks()).getSideboard().toFlatList();
} else {
//Generate
schemes = DeckgenUtil.generateSchemeDeck().getSchemes().toFlatList();
schemes = DeckgenUtil.generateSchemeDeck().getSideboard().toFlatList();
}
} else {
schemes = ((Deck) obj).getSchemes().toFlatList();
schemes = ((Deck) obj).getSideboard().toFlatList();
}
}
if (schemes == null) {

View File

@@ -11,8 +11,8 @@ import forge.Card;
import forge.Singletons;
import forge.control.FControl;
import forge.deck.Deck;
import forge.game.GameOutcome;
import forge.game.GameType;
import forge.game.GameOutcome;
import forge.game.MatchController;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -133,10 +133,13 @@ public class ControlWinLose {
List<Card> compAntes = new ArrayList<Card>(loser.getCardsIn(ZoneType.Ante));
Deck cDeck = match.getPlayersDeck(loser.getLobbyPlayer());
Deck oDeck = match.getPlayersOriginalDeck(loser.getLobbyPlayer());
for (Card c : compAntes) {
CardPrinted toRemove = CardDb.instance().getCard(c);
cDeck.getMain().remove(toRemove);
if ( cDeck != oDeck )
oDeck.getMain().remove(toRemove);
}
}

View File

@@ -119,10 +119,6 @@ public class QuestWinLose extends ControlWinLose {
qData.getCards().resetNewList();
QuestController qc = Singletons.getModel().getQuest();
if (match.isMatchOver()) {
restoreQuestDeckEdits();
}
LobbyPlayer questPlayer = Singletons.getControl().getLobby().getQuestPlayer();
if (isAnte) {
//do per-game actions
@@ -246,18 +242,6 @@ public class QuestWinLose extends ControlWinLose {
new QuestWinLoseCardViewer(antesWon), QuestWinLose.CONSTRAINTS_CARDS);
}
/**
* <p>
* restoreQuestDeckEdits
* </p>
* Reverts the persistent sideboard changes in quest decks.
*/
private void restoreQuestDeckEdits() {
for (LobbyPlayer p : Singletons.getModel().getMatch().getPlayers().keySet()) {
Singletons.getModel().getMatch().getPlayersDeck(p).clearDeckEdits();
}
}
/**
* <p>
* actionOnQuit.

View File

@@ -187,8 +187,8 @@ public class ItemPool<T extends InventoryItem> extends ItemPoolView<T> {
* @param card
* a T
*/
public void remove(final T card) {
this.remove(card, 1);
public boolean remove(final T card) {
return this.remove(card, 1);
}
/**
@@ -200,10 +200,10 @@ public class ItemPool<T extends InventoryItem> extends ItemPoolView<T> {
* @param amount
* a int
*/
public void remove(final T card, final int amount) {
public boolean remove(final T card, final int amount) {
final int count = this.count(card);
if ((count == 0) || (amount <= 0)) {
return;
return false;
}
if (count <= amount) {
this.getCards().remove(card);
@@ -211,6 +211,7 @@ public class ItemPool<T extends InventoryItem> extends ItemPoolView<T> {
this.getCards().put(card, count - amount);
}
this.setListInSync(false);
return true;
}
/**

View File

@@ -336,7 +336,6 @@ public final class QuestUtilCards {
final int leftInPool = this.qa.getCardPool().count(card);
// remove sold cards from all decks:
for (final Deck deck : this.qc.getMyDecks()) {
deck.clearDeckEdits();
deck.getMain().remove(card, deck.getMain().count(card) - leftInPool);
}
}

View File

@@ -57,7 +57,6 @@ import forge.Singletons;
import forge.card.CardEdition;
import forge.deck.DeckSection;
import forge.error.ErrorViewer;
import forge.game.GameType;
import forge.quest.data.GameFormatQuest;
import forge.item.BoosterPack;
import forge.item.CardDb;
@@ -99,7 +98,6 @@ public class QuestDataIO {
final XStream xStream = isIgnoring ? new IgnoringXStream() : new XStream();
xStream.registerConverter(new ItemPoolToXml());
xStream.registerConverter(new DeckSectionToXml());
xStream.registerConverter(new GameTypeToXml());
xStream.registerConverter(new GameFormatQuestToXml());
xStream.registerConverter(new QuestModeToXml());
xStream.autodetectAnnotations(true);
@@ -447,25 +445,6 @@ public class QuestDataIO {
}
}
private static class GameTypeToXml implements Converter {
@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(final Class clasz) {
return clasz.equals(GameType.class);
}
@Override
public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
// not used
}
@Override
public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
final String value = reader.getValue();
return GameType.smartValueOf(value, GameType.Quest);
}
}
private static class QuestModeToXml implements Converter {
@SuppressWarnings("rawtypes")
@Override

View File

@@ -156,7 +156,7 @@ public class FileSection {
*/
@SuppressWarnings("unchecked")
public static Map<String, List<String>> parseSections(final List<String> source) {
final Map<String, List<String>> result = new HashMap<String, List<String>>();
final Map<String, List<String>> result = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
String currentSection = "";
List<String> currentList = null;