From 7885a4216843d4d9ce2f95964dc7645c5cc62c46 Mon Sep 17 00:00:00 2001 From: John Hitchings Date: Sun, 17 Mar 2019 22:48:49 -0700 Subject: [PATCH 1/5] Refactor mulligans out of GameImpl and add Paris, Vancouver, London, and Canadian Highlander. Refactor GameImpls to use their specific mulligan. --- .../src/mage/game/FreeForAll.java | 3 +- .../src/mage/game/MomirDuel.java | 3 +- .../src/mage/game/MomirGame.java | 3 +- .../src/mage/game/TwoPlayerDuel.java | 8 +- Mage/src/main/java/mage/game/Game.java | 5 + .../mage/game/GameCanadianHighlanderImpl.java | 113 +--------------- .../java/mage/game/GameCommanderImpl.java | 3 +- Mage/src/main/java/mage/game/GameImpl.java | 103 +++------------ .../java/mage/game/GameTinyLeadersImpl.java | 3 +- .../mulligan/CanadianHighlanderMulligan.java | 124 ++++++++++++++++++ .../mage/game/mulligan/LondonMulligan.java | 110 ++++++++++++++++ .../java/mage/game/mulligan/Mulligan.java | 70 ++++++++++ .../mage/game/mulligan/ParisMulligan.java | 67 ++++++++++ .../mage/game/mulligan/VancouverMulligan.java | 31 +++++ 14 files changed, 444 insertions(+), 202 deletions(-) create mode 100644 Mage/src/main/java/mage/game/mulligan/CanadianHighlanderMulligan.java create mode 100644 Mage/src/main/java/mage/game/mulligan/LondonMulligan.java create mode 100644 Mage/src/main/java/mage/game/mulligan/Mulligan.java create mode 100644 Mage/src/main/java/mage/game/mulligan/ParisMulligan.java create mode 100644 Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java index f14cacd16bc..7ca787a1241 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java @@ -4,6 +4,7 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.VancouverMulligan; /** * @@ -14,7 +15,7 @@ public class FreeForAll extends GameImpl { private int numPlayers; public FreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); } public FreeForAll(final FreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java index 8ebf139746e..61705e32feb 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java @@ -13,6 +13,7 @@ import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; +import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; import mage.players.Player; @@ -23,7 +24,7 @@ import mage.players.Player; public class MomirDuel extends GameImpl { public MomirDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); } public MomirDuel(final MomirDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java index 542b4925c2e..b6efd57c2a3 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java @@ -13,6 +13,7 @@ import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; +import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; import mage.players.Player; @@ -25,7 +26,7 @@ public class MomirGame extends GameImpl { private int numPlayers; public MomirGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); } public MomirGame(final MomirGame game) { diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java index d3fe05e57ce..2443e9b4d45 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java @@ -6,12 +6,18 @@ import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; +import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; public class TwoPlayerDuel extends GameImpl { public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + this(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + } + + public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public TwoPlayerDuel(final TwoPlayerDuel game) { diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 83640eaf629..4772323460b 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -25,7 +25,9 @@ import mage.game.events.GameEvent; import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; +import mage.game.match.Match; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -472,4 +474,7 @@ public interface Game extends MageItem, Serializable { int damagePlayerOrPlaneswalker(UUID playerOrWalker, int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable); int damagePlayerOrPlaneswalker(UUID playerOrWalker, int damage, UUID sourceId, Game game, boolean combatDamage, boolean preventable, List appliedEffects); + + Mulligan getMulligan(); + } diff --git a/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java b/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java index 03b905bcc01..b9719e1a6b1 100644 --- a/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java @@ -5,16 +5,15 @@ import java.util.*; import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; +import mage.game.mulligan.CanadianHighlanderMulligan; +import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; public abstract class GameCanadianHighlanderImpl extends GameImpl { - protected boolean startingPlayerSkipsDraw = true; - protected Map usedMulligans = new LinkedHashMap<>(); - public GameCanadianHighlanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, 0, startLife); + super(attackOption, range, new CanadianHighlanderMulligan(freeMulligans), startLife); } public GameCanadianHighlanderImpl(final GameCanadianHighlanderImpl game) { @@ -27,110 +26,4 @@ public abstract class GameCanadianHighlanderImpl extends GameImpl { state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW)); } - private String getNextMulligan(String mulligan) { - switch (mulligan) { - case "7": - return "6a"; - case "6a": - return "6b"; - case "6b": - return "5a"; - case "5a": - return "5b"; - case "5b": - return "4a"; - case "4a": - return "4b"; - case "4b": - return "3a"; - case "3a": - return "3b"; - case "3b": - return "2a"; - case "2a": - return "2b"; - case "2b": - return "1a"; - case "1a": - return "1b"; - } - return "0"; - } - - private int getNextMulliganNum(String mulligan) { - switch (mulligan) { - case "7": - return 6; - case "6a": - return 6; - case "6b": - return 5; - case "5a": - return 5; - case "5b": - return 4; - case "4a": - return 4; - case "4b": - return 3; - case "3a": - return 3; - case "3b": - return 2; - case "2a": - return 2; - case "2b": - return 1; - case "1a": - return 1; - } - return 0; - } - - @Override - public int mulliganDownTo(UUID playerId) { - Player player = getPlayer(playerId); - int deduction = 1; - int numToMulliganTo = -1; - if (usedMulligans != null) { - String mulliganCode = "7"; - if (usedMulligans.containsKey(player.getId())) { - mulliganCode = usedMulligans.get(player.getId()); - } - numToMulliganTo = getNextMulliganNum(mulliganCode); - } - if (numToMulliganTo == -1) { - return player.getHand().size() - deduction; - } - return numToMulliganTo; - } - - @Override - public void mulligan(UUID playerId) { - Player player = getPlayer(playerId); - int numCards = player.getHand().size(); - int numToMulliganTo = numCards; - player.getLibrary().addAll(player.getHand().getCards(this), this); - player.getHand().clear(); - player.shuffleLibrary(null, this); - if (usedMulligans != null) { - String mulliganCode = "7"; - if (usedMulligans.containsKey(player.getId())) { - mulliganCode = usedMulligans.get(player.getId()); - } - numToMulliganTo = getNextMulliganNum(mulliganCode); - usedMulligans.put(player.getId(), getNextMulligan(mulliganCode)); - } - fireInformEvent(new StringBuilder(player.getLogName()) - .append(" mulligans to ") - .append(Integer.toString(numToMulliganTo)) - .append(numToMulliganTo == 1 ? " card" : " cards").toString()); - player.drawCards(numToMulliganTo, this); - } - - @Override - public void endMulligan(UUID playerId) { - super.endMulligan(playerId); - } - } diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index 265dda7dc0d..f3034597ccd 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -12,6 +12,7 @@ import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.constants.Zone; +import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; @@ -25,7 +26,7 @@ public abstract class GameCommanderImpl extends GameImpl { protected boolean startingPlayerSkipsDraw = true; public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); } public GameCommanderImpl(final GameCommanderImpl game) { diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 25ec612248d..663711bc2ee 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -40,6 +40,8 @@ import mage.game.command.Emblem; import mage.game.command.Plane; import mage.game.events.*; import mage.game.events.TableEvent.EventType; +import mage.game.mulligan.LondonMulligan; +import mage.game.mulligan.Mulligan; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -108,8 +110,8 @@ public abstract class GameImpl implements Game, Serializable { protected UUID winnerId; protected RangeOfInfluence range; - protected int freeMulligans; - protected Map usedFreeMulligans = new LinkedHashMap<>(); + protected Mulligan mulligan; + protected MultiplayerAttackOption attackOption; protected GameOptions gameOptions; protected String startMessage; @@ -142,10 +144,10 @@ public abstract class GameImpl implements Game, Serializable { // used to proceed player conceding requests private final LinkedList concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game - public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { + public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { this.id = UUID.randomUUID(); this.range = range; - this.freeMulligans = freeMulligans; + this.mulligan = mulligan; this.attackOption = attackOption; this.state = new GameState(); this.startLife = startLife; @@ -163,7 +165,7 @@ public abstract class GameImpl implements Game, Serializable { this.startingPlayerId = game.startingPlayerId; this.winnerId = game.winnerId; this.range = game.range; - this.freeMulligans = game.freeMulligans; + this.mulligan = game.getMulligan().copy(); this.attackOption = game.attackOption; this.state = game.state.copy(); this.gameCards = game.gameCards; @@ -968,50 +970,7 @@ public abstract class GameImpl implements Game, Serializable { } //20091005 - 103.4 - List keepPlayers = new ArrayList<>(); - List mulliganPlayers = new ArrayList<>(); - do { - mulliganPlayers.clear(); - for (UUID playerId : state.getPlayerList(startingPlayerId)) { - if (!keepPlayers.contains(playerId)) { - Player player = getPlayer(playerId); - boolean keep = true; - while (true) { - if (player.getHand().isEmpty()) { - break; - } - GameEvent event = new GameEvent(GameEvent.EventType.CAN_TAKE_MULLIGAN, null, null, playerId); - if (!replaceEvent(event)) { - fireEvent(event); - getState().setChoosingPlayerId(playerId); - if (player.chooseMulligan(this)) { - keep = false; - } - break; - } - } - if (keep) { - endMulligan(player.getId()); - keepPlayers.add(playerId); - fireInformEvent(player.getLogName() + " keeps hand"); - } else { - mulliganPlayers.add(playerId); - fireInformEvent(player.getLogName() + " decides to take mulligan"); - } - } - } - for (UUID mulliganPlayerId : mulliganPlayers) { - mulligan(mulliganPlayerId); - } - saveState(false); - } while (!mulliganPlayers.isEmpty()); - // new scry rule - for (UUID playerId : state.getPlayerList(startingPlayerId)) { - Player player = getPlayer(playerId); - if (player != null && player.getHand().size() < startingHandSize) { - player.scry(1, null, this); - } - } + mulligan.executeMulliganPhase(this, startingHandSize); getState().setChoosingPlayerId(null); state.resetWatchers(); // watcher objects from cards are reused during match so reset all card watchers already added @@ -1169,51 +1128,17 @@ public abstract class GameImpl implements Game, Serializable { @Override public int mulliganDownTo(UUID playerId) { - Player player = getPlayer(playerId); - int deduction = 1; - if (freeMulligans > 0) { - if (usedFreeMulligans != null && usedFreeMulligans.containsKey(player.getId())) { - int used = usedFreeMulligans.get(player.getId()); - if (used < freeMulligans) { - deduction = 0; - } - } else { - deduction = 0; - } - } - return player.getHand().size() - deduction; + return mulligan.mulliganDownTo(this, playerId); } @Override public void endMulligan(UUID playerId) { + mulligan.endMulligan(this, playerId); } @Override public void mulligan(UUID playerId) { - Player player = getPlayer(playerId); - int numCards = player.getHand().size(); - player.getLibrary().addAll(player.getHand().getCards(this), this); - player.getHand().clear(); - player.shuffleLibrary(null, this); - int deduction = 1; - if (freeMulligans > 0) { - if (usedFreeMulligans.containsKey(player.getId())) { - int used = usedFreeMulligans.get(player.getId()); - if (used < freeMulligans) { - deduction = 0; - usedFreeMulligans.put(player.getId(), used + 1); - } - } else { - deduction = 0; - usedFreeMulligans.put(player.getId(), 1); - } - } - fireInformEvent(new StringBuilder(player.getLogName()) - .append(" mulligans") - .append(deduction == 0 ? " for free and draws " : " down to ") - .append((numCards - deduction)) - .append(numCards - deduction == 1 ? " card" : " cards").toString()); - player.drawCards(numCards - deduction, this); + mulligan.mulligan(this, playerId); } @Override @@ -3249,4 +3174,10 @@ public abstract class GameImpl implements Game, Serializable { } return 0; } + + @Override + public Mulligan getMulligan() { + return mulligan; + } + } diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index eecbddd5f11..c1dd6232f6a 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -16,6 +16,7 @@ import mage.cards.CardSetInfo; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.*; +import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; @@ -31,7 +32,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl { protected boolean startingPlayerSkipsDraw = true; public GameTinyLeadersImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); } public GameTinyLeadersImpl(final GameTinyLeadersImpl game) { diff --git a/Mage/src/main/java/mage/game/mulligan/CanadianHighlanderMulligan.java b/Mage/src/main/java/mage/game/mulligan/CanadianHighlanderMulligan.java new file mode 100644 index 00000000000..320f39897c9 --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/CanadianHighlanderMulligan.java @@ -0,0 +1,124 @@ +package mage.game.mulligan; + +import mage.game.Game; +import mage.players.Player; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +public class CanadianHighlanderMulligan extends VancouverMulligan { + + protected Map usedMulligans = new LinkedHashMap<>(); + + public CanadianHighlanderMulligan(int freeMulligans) { + super(freeMulligans); + } + + @Override + public CanadianHighlanderMulligan copy() { + return new CanadianHighlanderMulligan(getFreeMulligans()); + } + + private String getNextMulligan(String mulligan) { + switch (mulligan) { + case "7": + return "6a"; + case "6a": + return "6b"; + case "6b": + return "5a"; + case "5a": + return "5b"; + case "5b": + return "4a"; + case "4a": + return "4b"; + case "4b": + return "3a"; + case "3a": + return "3b"; + case "3b": + return "2a"; + case "2a": + return "2b"; + case "2b": + return "1a"; + case "1a": + return "1b"; + } + return "0"; + } + + private int getNextMulliganNum(String mulligan) { + switch (mulligan) { + case "7": + return 6; + case "6a": + return 6; + case "6b": + return 5; + case "5a": + return 5; + case "5b": + return 4; + case "4a": + return 4; + case "4b": + return 3; + case "3a": + return 3; + case "3b": + return 2; + case "2a": + return 2; + case "2b": + return 1; + case "1a": + return 1; + } + return 0; + } + + @Override + public int mulliganDownTo(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int deduction = 1; + int numToMulliganTo = -1; + if (usedMulligans != null) { + String mulliganCode = "7"; + if (usedMulligans.containsKey(player.getId())) { + mulliganCode = usedMulligans.get(player.getId()); + } + numToMulliganTo = getNextMulliganNum(mulliganCode); + } + if (numToMulliganTo == -1) { + return player.getHand().size() - deduction; + } + return numToMulliganTo; + } + + @Override + public void mulligan(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int numCards = player.getHand().size(); + int numToMulliganTo = numCards; + player.getLibrary().addAll(player.getHand().getCards(game), game); + player.getHand().clear(); + player.shuffleLibrary(null, game); + if (usedMulligans != null) { + String mulliganCode = "7"; + if (usedMulligans.containsKey(player.getId())) { + mulliganCode = usedMulligans.get(player.getId()); + } + numToMulliganTo = getNextMulliganNum(mulliganCode); + usedMulligans.put(player.getId(), getNextMulligan(mulliganCode)); + } + game.fireInformEvent(new StringBuilder(player.getLogName()) + .append(" mulligans to ") + .append(Integer.toString(numToMulliganTo)) + .append(numToMulliganTo == 1 ? " card" : " cards").toString()); + player.drawCards(numToMulliganTo, game); + } + +} diff --git a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java new file mode 100644 index 00000000000..b63304b4ab2 --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java @@ -0,0 +1,110 @@ +package mage.game.mulligan; + +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.TargetCard; + +import java.util.*; + +public class LondonMulligan extends Mulligan { + + protected Map startingHandSizes = new HashMap<>(); + + public LondonMulligan(int freeMulligans) { + super(freeMulligans); + } + + @Override + public void executeMulliganPhase(Game game, int startingHandSize) { + for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { + startingHandSizes.put(playerId, startingHandSize); + } + + super.executeMulliganPhase(game, startingHandSize); + + for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { + int handSize = startingHandSizes.get(playerId); + Player player = game.getPlayer(playerId); + if (player != null && player.getHand().size() > handSize) { + int cardsToDiscard = player.getHand().size() - handSize; + Cards cards = new CardsImpl(); + cards.addAll(player.getHand()); + TargetCard target = new TargetCard(cardsToDiscard, cardsToDiscard, Zone.HAND, + new FilterCard("cards to PUT on the BOTTOM of your library (Discard for Mulligan)")); + player.chooseTarget(Outcome.Neutral, cards, target, null, game); + player.putCardsOnBottomOfLibrary(new CardsImpl(target.getTargets()), game, null, true); + cards.removeAll(target.getTargets()); + System.out.println(cardsToDiscard); + } + } + } + + @Override + public int mulliganDownTo(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int deduction = 1; + if (freeMulligans > 0) { + if (usedFreeMulligans != null && usedFreeMulligans.containsKey(player.getId())) { + int used = usedFreeMulligans.get(player.getId()); + if (used < freeMulligans) { + deduction = 0; + } + } else { + deduction = 0; + } + } + return startingHandSizes.get(playerId) - deduction; + } + + @Override + public void mulligan(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int numCards = player.getHand().size(); + player.getLibrary().addAll(player.getHand().getCards(game), game); + player.getHand().clear(); + player.shuffleLibrary(null, game); + int deduction = 1; + if (freeMulligans > 0) { + if (usedFreeMulligans.containsKey(player.getId())) { + int used = usedFreeMulligans.get(player.getId()); + if (used < freeMulligans) { + deduction = 0; + usedFreeMulligans.put(player.getId(), used + 1); + } + } else { + deduction = 0; + usedFreeMulligans.put(player.getId(), 1); + } + } + startingHandSizes.put(playerId, startingHandSizes.get(playerId) - deduction); + if (deduction == 0) { + game.fireInformEvent(new StringBuilder(player.getLogName()) + .append(" mulligans for free.") + .toString()); + } else { + game.fireInformEvent(new StringBuilder(player.getLogName()) + .append(" mulligans") + .append(" down to ") + .append((numCards - deduction)) + .append(numCards - deduction == 1 ? " card" : " cards").toString()); + } + player.drawCards(numCards, game); + } + + @Override + public void endMulligan(Game game, UUID playerId) {} + + @Override + public LondonMulligan copy() { + LondonMulligan mulligan = new LondonMulligan(getFreeMulligans()); + mulligan.startingHandSizes.putAll(startingHandSizes); + return mulligan; + } + +} diff --git a/Mage/src/main/java/mage/game/mulligan/Mulligan.java b/Mage/src/main/java/mage/game/mulligan/Mulligan.java new file mode 100644 index 00000000000..c12b6b0e7a9 --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/Mulligan.java @@ -0,0 +1,70 @@ +package mage.game.mulligan; + +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +import java.util.*; + +public abstract class Mulligan { + + protected final int freeMulligans; + protected final Map usedFreeMulligans = new HashMap<>(); + + public Mulligan(int freeMulligans) { + this.freeMulligans = freeMulligans; + } + + public void executeMulliganPhase(Game game, int startingHandSize) { + List keepPlayers = new ArrayList<>(); + List mulliganPlayers = new ArrayList<>(); + do { + mulliganPlayers.clear(); + for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { + if (!keepPlayers.contains(playerId)) { + Player player = game.getPlayer(playerId); + boolean keep = true; + while (true) { + if (player.getHand().isEmpty()) { + break; + } + GameEvent event = new GameEvent(GameEvent.EventType.CAN_TAKE_MULLIGAN, null, null, playerId); + if (!game.replaceEvent(event)) { + game.fireEvent(event); + game.getState().setChoosingPlayerId(playerId); + if (player.chooseMulligan(game)) { + keep = false; + } + break; + } + } + if (keep) { + game.endMulligan(player.getId()); + keepPlayers.add(playerId); + game.fireInformEvent(player.getLogName() + " keeps hand"); + } else { + mulliganPlayers.add(playerId); + game.fireInformEvent(player.getLogName() + " decides to take mulligan"); + } + } + } + for (UUID mulliganPlayerId : mulliganPlayers) { + mulligan(game, mulliganPlayerId); + } + game.saveState(false); + } while (!mulliganPlayers.isEmpty()); + } + + public abstract int mulliganDownTo(Game game, UUID playerId); + + public abstract void mulligan(Game game, UUID playerId); + + public abstract void endMulligan(Game game, UUID playerId); + + public abstract Mulligan copy(); + + public int getFreeMulligans() { + return freeMulligans; + } + +} diff --git a/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java b/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java new file mode 100644 index 00000000000..89ed63c087d --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java @@ -0,0 +1,67 @@ +package mage.game.mulligan; + +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +public class ParisMulligan extends Mulligan { + + public ParisMulligan(int freeMulligans) { + super(freeMulligans); + } + + @Override + public int mulliganDownTo(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int deduction = 1; + if (freeMulligans > 0) { + if (usedFreeMulligans != null && usedFreeMulligans.containsKey(player.getId())) { + int used = usedFreeMulligans.get(player.getId()); + if (used < freeMulligans) { + deduction = 0; + } + } else { + deduction = 0; + } + } + return player.getHand().size() - deduction; + } + + @Override + public void mulligan(Game game, UUID playerId) { + Player player = game.getPlayer(playerId); + int numCards = player.getHand().size(); + player.getLibrary().addAll(player.getHand().getCards(game), game); + player.getHand().clear(); + player.shuffleLibrary(null, game); + int deduction = 1; + if (freeMulligans > 0) { + if (usedFreeMulligans.containsKey(player.getId())) { + int used = usedFreeMulligans.get(player.getId()); + if (used < freeMulligans) { + deduction = 0; + usedFreeMulligans.put(player.getId(), used + 1); + } + } else { + deduction = 0; + usedFreeMulligans.put(player.getId(), 1); + } + } + game.fireInformEvent(new StringBuilder(player.getLogName()) + .append(" mulligans") + .append(deduction == 0 ? " for free and draws " : " down to ") + .append((numCards - deduction)) + .append(numCards - deduction == 1 ? " card" : " cards").toString()); + player.drawCards(numCards - deduction, game); + } + + @Override + public void endMulligan(Game game, UUID playerId) {} + + @Override + public ParisMulligan copy() { + return new ParisMulligan(getFreeMulligans()); + } + +} diff --git a/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java b/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java new file mode 100644 index 00000000000..0474a4df5fe --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java @@ -0,0 +1,31 @@ +package mage.game.mulligan; + +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +public class VancouverMulligan extends ParisMulligan { + + public VancouverMulligan(int freeMulligans) { + super(freeMulligans); + } + + @Override + public void executeMulliganPhase(Game game, int startingHandSize) { + super.executeMulliganPhase(game, startingHandSize); + // new scry rule + for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { + Player player = game.getPlayer(playerId); + if (player != null && player.getHand().size() < startingHandSize) { + player.scry(1, null, game); + } + } + } + + @Override + public VancouverMulligan copy() { + return new VancouverMulligan(getFreeMulligans()); + } + +} From 4a232b148c625317ec08b8003cf53969ebe407f4 Mon Sep 17 00:00:00 2001 From: John Hitchings Date: Tue, 19 Mar 2019 06:00:22 -0700 Subject: [PATCH 2/5] Add mulligan options to NewTableDialog, passing mulligan information to games. --- .../mage/client/dialog/NewTableDialog.form | 205 +++++---- .../mage/client/dialog/NewTableDialog.java | 422 +++++++++--------- .../src/mage/game/BrawlDuel.java | 5 +- .../src/mage/game/BrawlDuelMatch.java | 5 +- .../src/mage/game/BrawlFreeForAll.java | 5 +- .../src/mage/game/BrawlFreeForAllMatch.java | 7 +- .../src/mage/game/CanadianHighlanderDuel.java | 5 +- .../game/CanadianHighlanderDuelMatch.java | 6 +- .../src/mage/game/CommanderDuel.java | 5 +- .../src/mage/game/CommanderDuelMatch.java | 4 +- .../src/mage/game/CommanderFreeForAll.java | 5 +- .../mage/game/CommanderFreeForAllMatch.java | 4 +- .../src/mage/game/FreeForAll.java | 6 +- .../src/mage/game/FreeForAllMatch.java | 4 +- .../game/FreeformCommanderFreeForAll.java | 5 +- .../FreeformCommanderFreeForAllMatch.java | 4 +- .../src/mage/game/MomirDuel.java | 6 +- .../src/mage/game/MomirDuelMatch.java | 4 +- .../src/mage/game/MomirFreeForAllMatch.java | 4 +- .../src/mage/game/MomirGame.java | 6 +- .../PennyDreadfulCommanderFreeForAll.java | 5 +- ...PennyDreadfulCommanderFreeForAllMatch.java | 4 +- .../src/mage/game/TinyLeadersDuel.java | 5 +- .../src/mage/game/TinyLeadersDuelMatch.java | 6 +- .../src/mage/game/TwoPlayerDuel.java | 8 +- .../src/mage/game/TwoPlayerMatch.java | 4 +- .../abilities/other/NaturesWillTest.java | 3 +- .../abilities/other/StormTheVaultTest.java | 3 +- .../test/cards/single/akh/RagsRichesTest.java | 3 +- .../test/multiplayer/BlatantThieveryTest.java | 3 +- .../test/multiplayer/CreepingDreadTest.java | 3 +- .../multiplayer/MultiplayerTriggerTest.java | 3 +- .../org/mage/test/multiplayer/MyriadTest.java | 3 +- .../PlayerDiedStackTargetHandlingTest.java | 3 +- .../multiplayer/PlayerLeftGameRange1Test.java | 3 +- .../PlayerLeftGameRangeAllTest.java | 3 +- .../multiplayer/PrivilegedPositionTest.java | 3 +- .../test/multiplayer/VindictiveLichTest.java | 3 +- .../mage/test/serverside/PlayGameTest.java | 3 +- .../test/serverside/TestPlayRandomGame.java | 3 +- .../base/CardTestCommander3PlayersFFA.java | 3 +- .../base/CardTestCommanderDuelBase.java | 3 +- .../base/CardTestMultiPlayerBase.java | 3 +- .../serverside/base/CardTestPlayerBase.java | 3 +- .../serverside/base/CardTestPlayerBaseAI.java | 3 +- .../java/org/mage/test/utils/RandomTest.java | 7 +- .../mage/game/GameCanadianHighlanderImpl.java | 9 +- .../java/mage/game/GameCommanderImpl.java | 11 +- .../java/mage/game/GameTinyLeadersImpl.java | 6 +- .../java/mage/game/match/MatchOptions.java | 14 + .../java/mage/game/mulligan/MulliganType.java | 43 ++ 51 files changed, 532 insertions(+), 364 deletions(-) create mode 100644 Mage/src/main/java/mage/game/mulligan/MulliganType.java diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form index b712fbe518e..5738ce662c8 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form @@ -25,97 +25,106 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -174,7 +183,10 @@ - + + + + @@ -187,6 +199,7 @@ + @@ -293,7 +306,6 @@ - @@ -432,5 +444,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index ce326d77404..d4f2e2a6b3f 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -1,13 +1,6 @@ package mage.client.dialog; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.swing.*; - import mage.cards.decks.importer.DeckImporter; import mage.client.MageFrame; import mage.client.SessionHandler; @@ -21,11 +14,19 @@ import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.constants.SkillLevel; import mage.game.match.MatchOptions; +import mage.game.mulligan.MulliganType; import mage.players.PlayerType; import mage.view.GameTypeView; import mage.view.TableView; import org.apache.log4j.Logger; +import javax.swing.*; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ @@ -77,6 +78,7 @@ public class NewTableDialog extends MageDialog { lblGameType = new javax.swing.JLabel(); cbGameType = new javax.swing.JComboBox(); chkRollbackTurnsAllowed = new javax.swing.JCheckBox(); + lblFreeMulligans = new javax.swing.JLabel(); chkSpectatorsAllowed = new javax.swing.JCheckBox(); chkPlaneChase = new javax.swing.JCheckBox(); chkRated = new javax.swing.JCheckBox(); @@ -109,6 +111,8 @@ public class NewTableDialog extends MageDialog { spnQuitRatio = new javax.swing.JSpinner(); spnMinimumRating = new javax.swing.JSpinner(); spnEdhPowerLevel = new javax.swing.JSpinner(); + lblMullgian = new javax.swing.JLabel(); + cbMulligan = new javax.swing.JComboBox<>(); setTitle("New Table"); @@ -121,31 +125,36 @@ public class NewTableDialog extends MageDialog { lbDeckType.setText("Deck Type:"); lbTimeLimit.setText("Time Limit:"); - lbTimeLimit.setToolTipText("The active time a player may use to finish the match. If their time runs out, the player looses the current game."); + lbTimeLimit.setToolTipText("The active time a player may use to finish the match. If his or her time runs out, the player looses the current game."); lblGameType.setText("Game Type:"); - cbGameType.addActionListener(evt -> cbGameTypeActionPerformed(evt)); + cbGameType.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbGameTypeActionPerformed(evt); + } + }); chkRollbackTurnsAllowed.setText("Allow rollbacks"); chkRollbackTurnsAllowed.setToolTipText("Allow to rollback to the start of previous turns
\nif all players agree.\n"); - chkSpectatorsAllowed.setText("Allow Spectators"); - chkSpectatorsAllowed.setToolTipText("Allow spectators to watch.\n"); - - chkPlaneChase.setText("Use PlaneChase"); - chkPlaneChase.setToolTipText("Use planechase variant (suitable for all game types).\n"); - - chkRated.setText("Rated"); - chkRated.setToolTipText("Indicates if matches will be rated."); - lblFreeMulligans.setText("Free Mulligans:"); lblFreeMulligans.setToolTipText("The number of mulligans a player can use without decreasing the number of drawn cards."); + chkSpectatorsAllowed.setText("Spectators allowed"); + chkSpectatorsAllowed.setToolTipText("Allow spectators to view your game."); + + chkPlaneChase.setText("Use PlaneChase"); + chkPlaneChase.setToolTipText("Use the PlaneChase variant for your game."); + lblNumPlayers.setLabelFor(spnNumPlayers); lblNumPlayers.setText("Players"); - spnNumPlayers.addChangeListener(evt -> numPlayersChanged(evt)); + spnNumPlayers.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + numPlayersChanged(evt); + } + }); lblRange.setLabelFor(cbRange); lblRange.setText("Range of Influence"); @@ -166,7 +175,11 @@ public class NewTableDialog extends MageDialog { lblNumWins.setText("Wins"); lblNumWins.setToolTipText("How many games has a player to win to win the match."); - spnNumWins.addChangeListener(evt -> spnNumWinsnumPlayersChanged(evt)); + spnNumWins.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spnNumWinsnumPlayersChanged(evt); + } + }); jLabel1.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N jLabel1.setText("Player 1 (You)"); @@ -177,200 +190,207 @@ public class NewTableDialog extends MageDialog { pnlOtherPlayers.setLayout(new java.awt.GridLayout(0, 1)); btnOK.setText("OK"); - btnOK.addActionListener(evt -> btnOKActionPerformed(evt)); - - btnPreviousConfiguration1.setText("M1"); - btnPreviousConfiguration1.setToolTipText("Load saved Match configuration #1"); - btnPreviousConfiguration1.addActionListener(evt -> btnPreviousConfigurationActionPerformed(evt, 1)); - btnPreviousConfiguration2.setText("M2"); - btnPreviousConfiguration2.setToolTipText("Load saved Match configuration #2"); - btnPreviousConfiguration2.addActionListener(evt -> btnPreviousConfigurationActionPerformed(evt, 2)); + btnOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnOKActionPerformed(evt); + } + }); btnCancel.setText("Cancel"); - btnCancel.addActionListener(evt -> btnCancelActionPerformed(evt)); + btnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnCancelActionPerformed(evt); + } + }); lblQuitRatio.setText("Allowed quit %"); - lblMinimumRating.setText("Minimum rating"); + lblEdhPowerLevel.setText("EDH power level"); - spnQuitRatio.setToolTipText("Players with quit % more than this value can't join this table"); - spnMinimumRating.setToolTipText("Players with rating less than this value can't join this table"); - spnEdhPowerLevel.setToolTipText("Players with decks with a higher power level can't join this table"); + cbMulligan.setToolTipText("Selections the type of mulligan for games."); + + lblMullgian.setLabelFor(spnNumWins); + lblMullgian.setText("Mulligan"); + lblMullgian.setToolTipText("What style of mulligan?"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblName) - .addComponent(lbDeckType) - .addComponent(lblGameType)) - .addGap(6, 6, 6) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 270, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(chkRollbackTurnsAllowed) - .addGap(13, 13, 13) - .addComponent(lblFreeMulligans) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(13, 13, 13) - .addComponent(chkSpectatorsAllowed) - .addGap(13, 13, 13) - .addComponent(chkPlaneChase)) - .addGroup(layout.createSequentialGroup() - .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lbTimeLimit) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lblPassword) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 125, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnPreviousConfiguration1, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnPreviousConfiguration2, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, 332, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(chkRated) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(lblQuitRatio) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(lblMinimumRating) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnMinimumRating, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(btnOK) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnCancel)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblNumPlayers) - .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 57, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblRange) - .addComponent(cbRange, javax.swing.GroupLayout.PREFERRED_SIZE, 117, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(lblAttack) - .addGap(116, 116, 116) - .addComponent(lblSkillLevel)) - .addGroup(layout.createSequentialGroup() - .addComponent(cbAttackOption, javax.swing.GroupLayout.PREFERRED_SIZE, 177, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 148, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblNumWins) - .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblEdhPowerLevel) - .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addComponent(jSeparator2) - .addComponent(player1Panel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING)) - .addContainerGap()) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(jSeparator3, javax.swing.GroupLayout.DEFAULT_SIZE, 660, Short.MAX_VALUE) - .addContainerGap())) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(btnOK) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnCancel)) + .addComponent(jSeparator2) + .addComponent(player1Panel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblName) + .addComponent(lbDeckType) + .addComponent(lblGameType)) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 270, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(chkRollbackTurnsAllowed) + .addGap(13, 13, 13) + .addComponent(lblFreeMulligans) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkSpectatorsAllowed) + .addGap(13, 13, 13) + .addComponent(chkPlaneChase)) + .addGroup(layout.createSequentialGroup() + .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lbTimeLimit) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblPassword) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 125, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnPreviousConfiguration1, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnPreviousConfiguration2, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, 332, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblQuitRatio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblEdhPowerLevel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblNumPlayers) + .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, 57, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblRange) + .addComponent(cbRange, javax.swing.GroupLayout.PREFERRED_SIZE, 117, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAttack) + .addGap(116, 116, 116) + .addComponent(lblSkillLevel)) + .addGroup(layout.createSequentialGroup() + .addComponent(cbAttackOption, javax.swing.GroupLayout.PREFERRED_SIZE, 177, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 148, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblNumWins)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblMullgian) + .addComponent(cbMulligan, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGap(13, 13, 13))) + .addContainerGap()) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jSeparator3, javax.swing.GroupLayout.DEFAULT_SIZE, 660, Short.MAX_VALUE) + .addContainerGap())) ); layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(4, 4, 4) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lblName) - .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(btnPreviousConfiguration1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(btnPreviousConfiguration2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lblPassword) - .addComponent(lbTimeLimit) - .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbDeckType) - .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(chkRated) - .addComponent(lblQuitRatio) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lblMinimumRating) - .addComponent(spnMinimumRating, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lblFreeMulligans) - .addComponent(chkRollbackTurnsAllowed) - .addComponent(chkSpectatorsAllowed) - .addComponent(chkPlaneChase)) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lblGameType))) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(lblNumPlayers) - .addGap(0, 0, 0) - .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblSkillLevel) - .addComponent(lblNumWins) - .addComponent(lblEdhPowerLevel) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lblRange) - .addComponent(lblAttack))) - .addGap(0, 0, 0) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cbRange, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(cbAttackOption, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel1) - .addGap(0, 0, 0) - .addComponent(player1Panel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(16, 16, 16) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 105, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 7, Short.MAX_VALUE) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(btnCancel) - .addComponent(btnOK)) - .addGap(0, 0, 0)) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(4, 4, 4) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblName) + .addComponent(btnPreviousConfiguration1) + .addComponent(btnPreviousConfiguration2) + .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblPassword) + .addComponent(lbTimeLimit) + .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDeckType) + .addComponent(lblQuitRatio) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblEdhPowerLevel) + .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblFreeMulligans) + .addComponent(chkRollbackTurnsAllowed) + .addComponent(chkSpectatorsAllowed) + .addComponent(chkPlaneChase)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblGameType))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(lblNumPlayers) + .addGap(0, 0, 0) + .addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(201, 201, 201) - .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(167, Short.MAX_VALUE))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblSkillLevel) + .addComponent(lblMullgian)) + .addComponent(lblNumWins) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblRange) + .addComponent(lblAttack))) + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cbRange, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cbAttackOption, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cbMulligan, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addGap(0, 0, 0) + .addComponent(player1Panel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(16, 16, 16) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 105, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 7, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnCancel) + .addComponent(btnOK)) + .addGap(0, 0, 0)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(201, 201, 201) + .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(167, Short.MAX_VALUE))) ); + lblMullgian.getAccessibleContext().setAccessibleName("Mullgian"); + lblMullgian.getAccessibleContext().setAccessibleDescription("What style of mulligan?"); + pack(); }// //GEN-END:initComponents @@ -408,6 +428,7 @@ public class NewTableDialog extends MageDialog { options.setQuitRatio((Integer) this.spnQuitRatio.getValue()); options.setMinimumRating((Integer) this.spnMinimumRating.getValue()); options.setEdhPowerLevel((Integer) this.spnEdhPowerLevel.getValue()); + options.setMullgianType((MulliganType) this.cbMulligan.getSelectedItem()); String serverAddress = SessionHandler.getSession().getServerHostname().orElseGet(() -> ""); options.setBannedUsers(IgnoreList.ignoreList(serverAddress)); if (!checkMatchOptions(options)) { @@ -596,6 +617,7 @@ public class NewTableDialog extends MageDialog { cbRange.setModel(new DefaultComboBoxModel(RangeOfInfluence.values())); cbAttackOption.setModel(new DefaultComboBoxModel(MultiplayerAttackOption.values())); cbSkillLevel.setModel(new DefaultComboBoxModel(SkillLevel.values())); + cbMulligan.setModel(new DefaultComboBoxModel(MulliganType.values())); // Update the existing player panels (neccessary if server was changes = new session) int i = 2; for (TablePlayerPanel tablePlayerPanel : players) { @@ -763,6 +785,7 @@ public class NewTableDialog extends MageDialog { private javax.swing.JComboBox cbAttackOption; private javax.swing.JComboBox cbDeckType; private javax.swing.JComboBox cbGameType; + private javax.swing.JComboBox cbMulligan; private javax.swing.JComboBox cbRange; private javax.swing.JComboBox cbSkillLevel; private javax.swing.JComboBox cbTimeLimit; @@ -780,6 +803,7 @@ public class NewTableDialog extends MageDialog { private javax.swing.JLabel lblAttack; private javax.swing.JLabel lblFreeMulligans; private javax.swing.JLabel lblGameType; + private javax.swing.JLabel lblMullgian; private javax.swing.JLabel lblName; private javax.swing.JLabel lblNumPlayers; private javax.swing.JLabel lblNumWins; diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuel.java b/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuel.java index 9684dc0d1d3..8aa8ba425e7 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuel.java +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuel.java @@ -5,11 +5,12 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; public class BrawlDuel extends GameCommanderImpl { - public BrawlDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public BrawlDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public BrawlDuel(final BrawlDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuelMatch.java b/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuelMatch.java index e0a56eb0e23..4e3437879b5 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/src/mage/game/BrawlDuelMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -17,8 +18,8 @@ public class BrawlDuelMatch extends MatchImpl { @Override public void startGame() throws GameException { int startLife = 25; - boolean alsoHand = true; - BrawlDuel game = new BrawlDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + BrawlDuel game = new BrawlDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setCheckCommanderDamage(false); game.setStartMessage(this.createGameStartMessage()); game.setAlsoHand(true); diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAll.java b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAll.java index b697be6a01e..29cc7f22e07 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAll.java @@ -6,6 +6,7 @@ import java.util.UUID; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; /** * @@ -15,8 +16,8 @@ public class BrawlFreeForAll extends GameCommanderImpl { private int numPlayers; - public BrawlFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public BrawlFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public BrawlFreeForAll(final BrawlFreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAllMatch.java index 857323e0c65..0e00a6ab9d9 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/src/mage/game/BrawlFreeForAllMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -17,10 +18,10 @@ public class BrawlFreeForAllMatch extends MatchImpl { @Override public void startGame() throws GameException { int startLife = 30; - boolean alsoHand = true; - BrawlFreeForAll game = new BrawlFreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + BrawlFreeForAll game = new BrawlFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); - game.setAlsoHand(alsoHand); + game.setAlsoHand(true); game.setCheckCommanderDamage(false); game.setAlsoLibrary(true); initGame(game); diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuel.java b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuel.java index 6abcd7a9dfa..79fe97333b1 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuel.java +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuel.java @@ -5,11 +5,12 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; public class CanadianHighlanderDuel extends GameCanadianHighlanderImpl { - public CanadianHighlanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public CanadianHighlanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public CanadianHighlanderDuel(final CanadianHighlanderDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuelMatch.java b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuelMatch.java index 1d55a415077..42d53366607 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/src/mage/game/CanadianHighlanderDuelMatch.java @@ -3,6 +3,9 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; + +import static mage.game.mulligan.MulliganType.CANADIAN_HIGHLANDER; /** * @@ -17,7 +20,8 @@ public class CanadianHighlanderDuelMatch extends MatchImpl { @Override public void startGame() throws GameException { int startLife = 20; - CanadianHighlanderDuel game = new CanadianHighlanderDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().orDefault(CANADIAN_HIGHLANDER).getMulligan(options.getFreeMulligans()); + CanadianHighlanderDuel game = new CanadianHighlanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); initGame(game); games.add(game); diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java index 9859fde690d..6e5df00b828 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java @@ -5,11 +5,12 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; public class CommanderDuel extends GameCommanderImpl { - public CommanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public CommanderDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public CommanderDuel(final CommanderDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java index 89d589224b0..654930db5b5 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -29,7 +30,8 @@ public class CommanderDuelMatch extends MatchImpl { startLife = 30; alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015 } - CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setCheckCommanderDamage(checkCommanderDamage); game.setStartMessage(this.createGameStartMessage()); game.setAlsoHand(alsoHand); diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java index 8bfc3ef63af..2c1a9e7d188 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java @@ -6,6 +6,7 @@ import java.util.UUID; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; /** * @@ -15,8 +16,8 @@ public class CommanderFreeForAll extends GameCommanderImpl { private int numPlayers; - public CommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public CommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public CommanderFreeForAll(final CommanderFreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAllMatch.java index a1a86514377..efdc7aa305a 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAllMatch.java @@ -4,6 +4,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -23,7 +24,8 @@ public class CommanderFreeForAllMatch extends MatchImpl { startLife = 30; alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015 } - CommanderFreeForAll game = new CommanderFreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + CommanderFreeForAll game = new CommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); game.setAlsoHand(alsoHand); game.setAlsoLibrary(true); diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java index 7ca787a1241..44209f3388e 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAll.java @@ -4,7 +4,7 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; -import mage.game.mulligan.VancouverMulligan; +import mage.game.mulligan.Mulligan; /** * @@ -14,8 +14,8 @@ public class FreeForAll extends GameImpl { private int numPlayers; - public FreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + public FreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public FreeForAll(final FreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAllMatch.java index da37d693b67..d0bb1b34e88 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/src/mage/game/FreeForAllMatch.java @@ -4,6 +4,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -17,7 +18,8 @@ public class FreeForAllMatch extends MatchImpl { @Override public void startGame() throws GameException { - FreeForAll game = new FreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), 20); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + FreeForAll game = new FreeForAll(options.getAttackOption(), options.getRange(), mulligan, 20); game.setStartMessage(this.createGameStartMessage()); initGame(game); games.add(game); diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java index 37918129fa3..2f9aa0e29f6 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAll.java @@ -6,6 +6,7 @@ import java.util.UUID; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; /** * @@ -15,8 +16,8 @@ public class FreeformCommanderFreeForAll extends GameCommanderImpl { private int numPlayers; - public FreeformCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public FreeformCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public FreeformCommanderFreeForAll(final FreeformCommanderFreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java index 518c9f263e9..5c623740426 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/src/mage/game/FreeformCommanderFreeForAllMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -18,7 +19,8 @@ public class FreeformCommanderFreeForAllMatch extends MatchImpl { public void startGame() throws GameException { int startLife = 40; boolean alsoHand = true; - FreeformCommanderFreeForAll game = new FreeformCommanderFreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + FreeformCommanderFreeForAll game = new FreeformCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); game.setAlsoHand(alsoHand); game.setAlsoLibrary(true); diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java index 61705e32feb..ac51137df93 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java @@ -13,7 +13,7 @@ import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; -import mage.game.mulligan.VancouverMulligan; +import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; @@ -23,8 +23,8 @@ import mage.players.Player; */ public class MomirDuel extends GameImpl { - public MomirDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + public MomirDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public MomirDuel(final MomirDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuelMatch.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuelMatch.java index 80dca4df70f..6ae458432ea 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuelMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -19,7 +20,8 @@ public class MomirDuelMatch extends MatchImpl { // Momir Vig, Simic Visionary gives +4 starting life int startLife = 24; - MomirDuel game = new MomirDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + MomirDuel game = new MomirDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); this.initGame(game); diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllMatch.java index 0aa7adb2aa3..9b8eddf38be 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -19,7 +20,8 @@ public class MomirFreeForAllMatch extends MatchImpl { // Momir Vig, Simic Visionary gives +4 starting life int startLife = 24; - MomirGame game = new MomirGame(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + MomirGame game = new MomirGame(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); this.initGame(game); diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java index b6efd57c2a3..0750d4d7cb1 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirGame.java @@ -13,7 +13,7 @@ import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; -import mage.game.mulligan.VancouverMulligan; +import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; @@ -25,8 +25,8 @@ public class MomirGame extends GameImpl { private int numPlayers; - public MomirGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + public MomirGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public MomirGame(final MomirGame game) { diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAll.java b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAll.java index 1cd80dc09ed..add9c578a9f 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAll.java @@ -6,6 +6,7 @@ import java.util.UUID; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; /** * @@ -15,8 +16,8 @@ public class PennyDreadfulCommanderFreeForAll extends GameCommanderImpl { private int numPlayers; - public PennyDreadfulCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public PennyDreadfulCommanderFreeForAll(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public PennyDreadfulCommanderFreeForAll(final PennyDreadfulCommanderFreeForAll game) { diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAllMatch.java b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAllMatch.java index 9ee1d3f2e81..e9deed1700c 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAllMatch.java +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/src/mage/game/PennyDreadfulCommanderFreeForAllMatch.java @@ -4,6 +4,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -23,7 +24,8 @@ public class PennyDreadfulCommanderFreeForAllMatch extends MatchImpl { startLife = 30; alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015 } - PennyDreadfulCommanderFreeForAll game = new PennyDreadfulCommanderFreeForAll(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + PennyDreadfulCommanderFreeForAll game = new PennyDreadfulCommanderFreeForAll(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); game.setAlsoHand(alsoHand); game.setAlsoLibrary(true); diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuel.java b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuel.java index 675768e2156..83044e4361e 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuel.java +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuel.java @@ -5,6 +5,7 @@ package mage.game; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; +import mage.game.mulligan.Mulligan; /** * @@ -12,8 +13,8 @@ import mage.game.match.MatchType; */ public class TinyLeadersDuel extends GameTinyLeadersImpl { - public TinyLeadersDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, freeMulligans, startLife); + public TinyLeadersDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public TinyLeadersDuel(final TinyLeadersDuel game) { diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuelMatch.java b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuelMatch.java index 8a6847d314e..426f8fc1c71 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/src/mage/game/TinyLeadersDuelMatch.java @@ -4,6 +4,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -19,8 +20,9 @@ public class TinyLeadersDuelMatch extends MatchImpl { public void startGame() throws GameException { //Tiny Leaders Play Rule 13: Players begin the game with 25 life. int startLife = 25; - - TinyLeadersDuel game = new TinyLeadersDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); + + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + TinyLeadersDuel game = new TinyLeadersDuel(options.getAttackOption(), options.getRange(), mulligan, startLife); game.setStartMessage(this.createGameStartMessage()); //Tucking a Tiny Leader is legal diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java index 2443e9b4d45..b5e7f2b3346 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java @@ -1,20 +1,16 @@ package mage.game; -import java.util.UUID; import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.game.match.MatchType; import mage.game.mulligan.Mulligan; -import mage.game.mulligan.VancouverMulligan; import mage.game.turn.TurnMod; -public class TwoPlayerDuel extends GameImpl { +import java.util.UUID; - public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - this(attackOption, range, new VancouverMulligan(freeMulligans), startLife); - } +public class TwoPlayerDuel extends GameImpl { public TwoPlayerDuel(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { super(attackOption, range, mulligan, startLife); diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerMatch.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerMatch.java index ea10bf4d0e3..9705f03e5dd 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerMatch.java +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerMatch.java @@ -3,6 +3,7 @@ package mage.game; import mage.game.match.MatchImpl; import mage.game.match.MatchOptions; +import mage.game.mulligan.Mulligan; /** * @@ -16,7 +17,8 @@ public class TwoPlayerMatch extends MatchImpl { @Override public void startGame() throws GameException { - TwoPlayerDuel game = new TwoPlayerDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), 20); + Mulligan mulligan = options.getMulliganType().getMulligan(options.getFreeMulligans()); + TwoPlayerDuel game = new TwoPlayerDuel(options.getAttackOption(), options.getRange(), mulligan, 20); // Sets a start message about the match score game.setStartMessage(this.createGameStartMessage()); initGame(game); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NaturesWillTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NaturesWillTest.java index 2c8d49e50ae..3b4b2b2f357 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NaturesWillTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/NaturesWillTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -15,7 +16,7 @@ import java.io.FileNotFoundException; public class NaturesWillTest extends CardTestPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 20); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); playerC = createPlayer(game, playerC, "PlayerC"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/StormTheVaultTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/StormTheVaultTest.java index 75f279af428..287f6828c78 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/StormTheVaultTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/StormTheVaultTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -15,7 +16,7 @@ import java.io.FileNotFoundException; public class StormTheVaultTest extends CardTestPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 20); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); playerC = createPlayer(game, playerC, "PlayerC"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/RagsRichesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/RagsRichesTest.java index 9df146aca78..f7acc869dea 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/RagsRichesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/RagsRichesTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -18,7 +19,7 @@ import java.io.FileNotFoundException; public class RagsRichesTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, 0, 20); + Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/BlatantThieveryTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/BlatantThieveryTest.java index 049e958347b..7bf3411dde4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/BlatantThieveryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/BlatantThieveryTest.java @@ -13,6 +13,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -24,7 +25,7 @@ public class BlatantThieveryTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 20); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java index 6c508213d9d..6be02becc88 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java @@ -8,6 +8,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -23,7 +24,7 @@ public class CreepingDreadTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MultiplayerTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MultiplayerTriggerTest.java index ed53f823941..fa693669c2a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MultiplayerTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MultiplayerTriggerTest.java @@ -8,6 +8,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -15,7 +16,7 @@ public class MultiplayerTriggerTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java index 0538e0c3d67..e4e420112a3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -19,7 +20,7 @@ public class MyriadTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java index 808ce5c696a..505351f83b6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java @@ -13,6 +13,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -26,7 +27,7 @@ public class PlayerDiedStackTargetHandlingTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { // Start Life = 2 - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 3); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 3); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java index 4c37d738d9c..df74f388754 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java @@ -10,6 +10,7 @@ import mage.counters.CounterType; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import mage.game.permanent.Permanent; import org.junit.Assert; import org.junit.Test; @@ -24,7 +25,7 @@ public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { // Start Life = 2 - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 2); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java index f2fbfe06a75..c1c1e1d11f1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java @@ -9,6 +9,7 @@ import mage.counters.CounterType; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -22,7 +23,7 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { // Start Life = 2 - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 2); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 2); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrivilegedPositionTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrivilegedPositionTest.java index b935b12442d..c404a37a384 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrivilegedPositionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrivilegedPositionTest.java @@ -9,6 +9,7 @@ import mage.counters.CounterType; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -21,7 +22,7 @@ public class PrivilegedPositionTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/VindictiveLichTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/VindictiveLichTest.java index fc58244a4bd..ae115daaa27 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/VindictiveLichTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/VindictiveLichTest.java @@ -9,6 +9,7 @@ import mage.constants.Zone; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.junit.Test; import org.mage.test.serverside.base.CardTestMultiPlayerBase; @@ -20,7 +21,7 @@ public class VindictiveLichTest extends CardTestMultiPlayerBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 40); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java index a2c8e5f1b23..5f498e437da 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java @@ -10,6 +10,7 @@ import mage.game.Game; import mage.game.GameException; import mage.game.GameOptions; import mage.game.TwoPlayerDuel; +import mage.game.mulligan.VancouverMulligan; import mage.player.ai.ComputerPlayer; import mage.players.Player; import mage.players.PlayerType; @@ -34,7 +35,7 @@ public class PlayGameTest extends MageTestBase { @Ignore @Test public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { - Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, 0, 20); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Player computerA = createPlayer("ComputerA", PlayerType.COMPUTER_MINIMAX_HYBRID); // Player playerA = createPlayer("ComputerA", "Computer - mad"); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java b/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java index 644c9fed18e..5d30022f1e7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java @@ -10,6 +10,7 @@ import mage.game.Game; import mage.game.GameException; import mage.game.GameOptions; import mage.game.TwoPlayerDuel; +import mage.game.mulligan.VancouverMulligan; import mage.player.ai.ComputerPlayer; import mage.players.Player; import mage.util.RandomUtil; @@ -40,7 +41,7 @@ public class TestPlayRandomGame extends MageTestBase { } private void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException { - Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, 0, 20); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, new VancouverMulligan(0), 20); Player computerA = createRandomPlayer("ComputerA"); Deck deck = generateRandomDeck(); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java index c094cf874c8..d259dd05ac9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java @@ -6,6 +6,7 @@ import mage.constants.RangeOfInfluence; import mage.game.CommanderFreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; /** @@ -23,7 +24,7 @@ public abstract class CardTestCommander3PlayersFFA extends CardTestPlayerAPIImpl @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 40); + Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, new VancouverMulligan(0), 40); playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerB = createPlayer(game, playerB, "PlayerB", deckNameB); playerC = createPlayer(game, playerC, "PlayerC", deckNameC); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommanderDuelBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommanderDuelBase.java index d3972b55308..3a9847db682 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommanderDuelBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommanderDuelBase.java @@ -7,6 +7,7 @@ import mage.constants.RangeOfInfluence; import mage.game.CommanderDuel; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; /** @@ -23,7 +24,7 @@ public abstract class CardTestCommanderDuelBase extends CardTestPlayerAPIImpl { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new CommanderDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, 0, 40); + Game game = new CommanderDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 40); playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerB = createPlayer(game, playerB, "PlayerB", deckNameB); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java index 863ac2b2565..804e28fef92 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java @@ -6,6 +6,7 @@ import mage.constants.RangeOfInfluence; import mage.game.FreeForAll; import mage.game.Game; import mage.game.GameException; +import mage.game.mulligan.VancouverMulligan; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; /** @@ -20,7 +21,7 @@ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, 0, 20); + Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java index 5c92e590068..6abbbf6dc76 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBase.java @@ -6,6 +6,7 @@ import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.GameException; import mage.game.TwoPlayerDuel; +import mage.game.mulligan.VancouverMulligan; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; /** @@ -22,7 +23,7 @@ public abstract class CardTestPlayerBase extends CardTestPlayerAPIImpl { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, 0, 20); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); playerA = createPlayer(game, playerA, "PlayerA", deckNameA); playerB = createPlayer(game, playerB, "PlayerB", deckNameB); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java index 89e522fd5af..d3da9e21c80 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java @@ -5,6 +5,7 @@ import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.GameException; import mage.game.TwoPlayerDuel; +import mage.game.mulligan.VancouverMulligan; import org.mage.test.player.TestComputerPlayer7; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; @@ -20,7 +21,7 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, 0, 20); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, new VancouverMulligan(0), 20); playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/RandomTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/RandomTest.java index 5735e12d601..6667339b6a1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/RandomTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/RandomTest.java @@ -7,6 +7,7 @@ import mage.constants.PlanarDieRoll; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.TwoPlayerDuel; +import mage.game.mulligan.VancouverMulligan; import mage.player.human.HumanPlayer; import mage.players.Player; import mage.util.RandomUtil; @@ -92,7 +93,7 @@ public class RandomTest { String dest = "f:/test/xmage/"; //RandomUtil.setSeed(123); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); - Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 50); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); int height = 512; int weight = 512; @@ -115,7 +116,7 @@ public class RandomTest { String dest = "f:/test/xmage/"; //RandomUtil.setSeed(123); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); - Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 50); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); int height = 512; int weight = 512; @@ -140,7 +141,7 @@ public class RandomTest { String dest = "f:/test/xmage/"; //RandomUtil.setSeed(123); Player player = new HumanPlayer("random", RangeOfInfluence.ALL, 1); - Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 50); + Game game = new TwoPlayerDuel(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, new VancouverMulligan(0), 50); Deck deck = DeckTestUtils.buildRandomDeck("WGUBR", false, "GRN"); player.getLibrary().addAll(deck.getCards(), game); diff --git a/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java b/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java index b9719e1a6b1..ab7408114aa 100644 --- a/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCanadianHighlanderImpl.java @@ -1,19 +1,18 @@ package mage.game; -import java.util.*; import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; -import mage.game.mulligan.CanadianHighlanderMulligan; import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; -import mage.players.Player; + +import java.util.UUID; public abstract class GameCanadianHighlanderImpl extends GameImpl { - public GameCanadianHighlanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new CanadianHighlanderMulligan(freeMulligans), startLife); + public GameCanadianHighlanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public GameCanadianHighlanderImpl(final GameCanadianHighlanderImpl game) { diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index f3034597ccd..96de6b83069 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -1,7 +1,5 @@ package mage.game; -import java.util.Map; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.InfoEffect; @@ -12,11 +10,14 @@ import mage.constants.MultiplayerAttackOption; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.constants.Zone; -import mage.game.mulligan.VancouverMulligan; +import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; +import java.util.Map; +import java.util.UUID; + public abstract class GameCommanderImpl extends GameImpl { // private final Map mulliganedCards = new HashMap<>(); @@ -25,8 +26,8 @@ public abstract class GameCommanderImpl extends GameImpl { protected boolean alsoLibrary; // replace commander going to library protected boolean startingPlayerSkipsDraw = true; - public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + public GameCommanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public GameCommanderImpl(final GameCommanderImpl game) { diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index c1dd6232f6a..78c7fdb0ec7 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -16,7 +16,7 @@ import mage.cards.CardSetInfo; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.constants.*; -import mage.game.mulligan.VancouverMulligan; +import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; import mage.watchers.common.CommanderInfoWatcher; @@ -31,8 +31,8 @@ public abstract class GameTinyLeadersImpl extends GameImpl { protected boolean alsoLibrary; // replace also commander going to library protected boolean startingPlayerSkipsDraw = true; - public GameTinyLeadersImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { - super(attackOption, range, new VancouverMulligan(freeMulligans), startLife); + public GameTinyLeadersImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife) { + super(attackOption, range, mulligan, startLife); } public GameTinyLeadersImpl(final GameTinyLeadersImpl game) { diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java index 47ad873e631..0fd193193e3 100644 --- a/Mage/src/main/java/mage/game/match/MatchOptions.java +++ b/Mage/src/main/java/mage/game/match/MatchOptions.java @@ -5,6 +5,7 @@ import mage.constants.MatchTimeLimit; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.constants.SkillLevel; +import mage.game.mulligan.MulliganType; import mage.game.result.ResultProtos; import mage.players.PlayerType; @@ -47,6 +48,7 @@ public class MatchOptions implements Serializable { * Time each player has during the game to play using his\her priority. */ protected MatchTimeLimit matchTimeLimit; // 0 = no priorityTime handling + protected MulliganType mulliganType; /*public MatchOptions(String name, String gameType) { this.name = name; @@ -257,4 +259,16 @@ public class MatchOptions implements Serializable { return builder.build(); } + + public void setMullgianType(MulliganType mulliganType) { + this.mulliganType = mulliganType; + } + + public MulliganType getMulliganType() { + if (mulliganType == null) { + return MulliganType.GAME_DEFAULT; + } + return mulliganType; + } + } diff --git a/Mage/src/main/java/mage/game/mulligan/MulliganType.java b/Mage/src/main/java/mage/game/mulligan/MulliganType.java new file mode 100644 index 00000000000..46356fdf116 --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/MulliganType.java @@ -0,0 +1,43 @@ +package mage.game.mulligan; + +public enum MulliganType { + + GAME_DEFAULT("Game Default"), + VANCOUVER("Vancouver"), + PARIS("Paris"), + LONDON("London"), + CANADIAN_HIGHLANDER("Canadian Highlander"); + + private final String displayName; + + MulliganType(String displayName) { + this.displayName = displayName; + } + + public Mulligan getMulligan(int freeMulligans) { + switch(this) { + case PARIS: + return new ParisMulligan(freeMulligans); + case CANADIAN_HIGHLANDER: + return new CanadianHighlanderMulligan(freeMulligans); + case LONDON: + return new LondonMulligan(freeMulligans); + default: + case VANCOUVER: + return new VancouverMulligan(freeMulligans); + } + } + + @Override + public String toString() { + return displayName; + } + + public MulliganType orDefault(MulliganType defaultMulligan) { + if (this == GAME_DEFAULT) { + return defaultMulligan; + } + return this; + } + +} From d77ad0ce1f975287a2df8a524a13a02b4b618592 Mon Sep 17 00:00:00 2001 From: John Hitchings Date: Tue, 19 Mar 2019 23:31:02 -0700 Subject: [PATCH 3/5] Add mulligan tests. --- .../CanadianHighlanderMulliganTest.java | 249 ++++++++++ .../test/mulligan/LondonMulliganTest.java | 231 ++++++++++ .../mage/test/mulligan/MulliganTestBase.java | 433 ++++++++++++++++++ .../mage/test/mulligan/ParisMulliganTest.java | 181 ++++++++ .../test/mulligan/VancouverMulliganTest.java | 251 ++++++++++ 5 files changed, 1345 insertions(+) create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/CanadianHighlanderMulliganTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/ParisMulliganTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/VancouverMulliganTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/CanadianHighlanderMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/CanadianHighlanderMulliganTest.java new file mode 100644 index 00000000000..74e72cd76f1 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/CanadianHighlanderMulliganTest.java @@ -0,0 +1,249 @@ +package org.mage.test.mulligan; + +import mage.game.mulligan.MulliganType; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; + +public class CanadianHighlanderMulliganTest extends MulliganTestBase { + + @Test + public void testCanadianHighlanderMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testCanadianHighlanderMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNTopOfLibrary(1))); + }); + } + + @Test + public void testCanadianHighlanderMulligan_OneMulligan_Scry() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return true; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNBottomOfLibrary(1))); + }); + } + + @Test + public void testCanadianHighlanderMulligan_TwoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(21, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(28, 6))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(21, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(28, 6))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(21, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(28, 6))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNTopOfLibrary(1))); + }); + } + + @Test + public void testCanadianHighlanderMulligan_ThreeMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + Set hand4 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(21, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(28, 6))); + hand3.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(16, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(23, 6))); + assertEquals(hand3, new HashSet<>(scenario.getLibraryRangeSize(29, 6))); + hand4.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + scenario.assertSizes(5, 35); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(16, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(23, 6))); + assertEquals(hand3, new HashSet<>(scenario.getLibraryRangeSize(29, 6))); + assertEquals(hand4, scenario.getHand()); + scry.add(scenario.getLibraryTopCard()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(5, 35); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(16, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(23, 6))); + assertEquals(hand3, new HashSet<>(scenario.getLibraryRangeSize(29, 6))); + assertEquals(hand4, scenario.getHand()); + assertEquals(scry, new HashSet<>(scenario.getNTopOfLibrary(1))); + }); + } + + @Test + public void testCanadianHighlanderMulligan_AlwaysMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.CANADIAN_HIGHLANDER, 0); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(4, 36); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(4, 36); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(3, 37); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(3, 37); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(2, 38); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(2, 38); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(1, 39); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(1, 39); + return true; + }); + scenario.scry(() -> { + scenario.assertSizes(0, 40); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(0, 40); + }); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java new file mode 100644 index 00000000000..5bd8aac5c5b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java @@ -0,0 +1,231 @@ +package org.mage.test.mulligan; + +import com.google.common.collect.Sets; +import mage.game.mulligan.MulliganType; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.assertEquals; + +public class LondonMulliganTest extends MulliganTestBase { + + @Test + public void testLondonMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testLondonMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + List discarded = new ArrayList<>(); + Set remainingHand = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return false; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(1, count); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, scenario.getHand()); + scenario.getHand().stream().limit(count).forEach(discarded::add); + remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); + return discarded; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(remainingHand, new HashSet<>(scenario.getHand())); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(discarded, scenario.getNBottomOfLibrary(1)); + }); + } + + @Test + public void testLondonMulligan_TwoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + List discarded = new ArrayList<>(); + Set remainingHand = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(2, count); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand3, scenario.getHand()); + scenario.getHand().stream().limit(count).forEach(discarded::add); + remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); + return discarded; + }); + scenario.run(() -> { + scenario.assertSizes(5, 35); + assertEquals(remainingHand, new HashSet<>(scenario.getHand())); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(discarded, scenario.getNBottomOfLibrary(2)); + }); + } + + @Test + public void testLondonMulligan_FreeMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testLondonMulligan_FreeMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand2.addAll(scenario.getHand()); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testLondonMulligan_FreeMulligan_TwoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + List discarded = new ArrayList<>(); + Set remainingHand = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(1, count); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand3, scenario.getHand()); + scenario.getHand().stream().limit(count).forEach(discarded::add); + remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); + return discarded; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(remainingHand, new HashSet<>(scenario.getHand())); + assertEquals(discarded, scenario.getNBottomOfLibrary(1)); + }); + } + + @Test + public void testLondonMulligan_AlwaysMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.discardBottom(count -> { + assertEquals(7, count); + return new ArrayList<>(scenario.getHand()); + }); + scenario.run(() -> { + scenario.assertSizes(0, 40); + }); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java new file mode 100644 index 00000000000..711d9792b7b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java @@ -0,0 +1,433 @@ + +package org.mage.test.mulligan; + +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.Modes; +import mage.abilities.TriggeredAbility; +import mage.abilities.costs.VariableCost; +import mage.abilities.costs.mana.ManaCost; +import mage.cards.Card; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.basiclands.Forest; +import mage.cards.decks.Deck; +import mage.choices.Choice; +import mage.constants.Outcome; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.game.GameOptions; +import mage.game.TwoPlayerDuel; +import mage.game.combat.CombatGroup; +import mage.game.draft.Draft; +import mage.game.match.Match; +import mage.game.mulligan.Mulligan; +import mage.game.mulligan.MulliganType; +import mage.game.permanent.Permanent; +import mage.game.tournament.Tournament; +import mage.players.Player; +import mage.players.PlayerImpl; +import mage.target.Target; +import mage.target.TargetAmount; +import mage.target.TargetCard; +import mage.target.TargetPlayer; +import org.apache.log4j.Logger; + +import java.io.Serializable; +import java.util.*; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.toList; +import static mage.constants.MultiplayerAttackOption.LEFT; +import static mage.constants.RangeOfInfluence.ONE; +import static mage.constants.Rarity.LAND; +import static org.junit.Assert.*; + +public class MulliganTestBase { + + protected static Logger logger = Logger.getLogger(MulliganTestBase.class); + + static class MulliganScenarioTest { + + private final MulliganType mulliganType; + private final int freeMulligans; + private final List steps = new ArrayList<>(); + + private PlayerProxy player1; + + public MulliganScenarioTest(MulliganType mulliganType, int freeMulligans) { + this.mulliganType = mulliganType; + this.freeMulligans = freeMulligans; + } + + public void mulligan(MulliganStep step) { + steps.add(step); + } + + public void scry(ScryStep step) { + steps.add(step); + } + + public void discardBottom(DiscardBottomStep step) { + steps.add(step); + } + + public void run(Runnable callback) { + Mulligan mulligan = mulliganType.getMulligan(freeMulligans); + Game game = new TwoPlayerDuel(LEFT, ONE, mulligan, 20) { + @Override + public void fireStatusEvent(String message, boolean withTime) { + super.fireStatusEvent(message, withTime); + } + + @Override + protected void play(UUID nextPlayerId) { + } + }; + GameOptions options = new GameOptions(); + options.skipInitShuffling = true; + game.setGameOptions(options); + + this.player1 = new PlayerProxy("p1", ONE); + player1.setSteps(steps); + Deck deck1 = generateDeck(player1.getId(), 40); + game.loadCards(deck1.getCards(), player1.getId()); + game.addPlayer(player1, deck1); + + PlayerProxy player2 = new PlayerProxy("p2", ONE); + Deck deck2 = generateDeck(player2.getId(), 40); + game.loadCards(deck2.getCards(), player2.getId()); + game.addPlayer(player2, deck2); + + game.start(player1.getId()); + + player1.assertStepsComplete(); + callback.run(); + } + + public Set getHand() { + checkState(player1 != null); + return unmodifiableSet(player1.getHand()); + } + + public List getLibrary() { + checkState(player1 != null); + return unmodifiableList(player1.getLibrary().getCardList()); + } + + public List getNTopOfLibrary(int n) { + checkState(player1 != null); + List library = getLibrary(); + checkArgument(n <= library.size()); + return unmodifiableList(library.subList(0, n)); + } + + public List getNBottomOfLibrary(int n) { + checkState(player1 != null); + List library = getLibrary(); + checkArgument(n <= library.size()); + return unmodifiableList(library.subList(library.size() - n, library.size())); + } + + + public List getLibraryRangeSize(int start, int n) { + return getLibraryRangeIndex(start, start + n); + } + + public List getLibraryRangeIndex(int start, int end) { + checkArgument(end >= start); + checkState(player1 != null); + List library = getLibrary(); + checkArgument(end <= library.size()); + return unmodifiableList(library.subList(start, end)); + } + + public UUID getLibraryTopCard() { + return getOnlyElement(getNTopOfLibrary(1)); + } + + public void assertSizes(int handSize, int librarySize) { + assertEquals("hand size", handSize, getHand().size()); + assertEquals("library size", librarySize, getLibrary().size()); + } + + } + + public static Deck generateDeck(UUID playerId, int count) { + Deck deck = new Deck(); + Stream.generate(() -> new Forest(playerId, new CardSetInfo("Forest", "TEST", "1", LAND))) + .limit(count) + .forEach(deck.getCards()::add); + return deck; + } + + interface Step {} + + interface MulliganStep extends Step { + boolean mulligan(); + } + + interface ScryStep extends Step { + boolean scry(); + } + + interface DiscardBottomStep extends Step { + List discardBottom(int count); + } + + static class PlayerProxy extends StubPlayer { + + private List steps = null; + private int current = 0; + + public PlayerProxy(String name, RangeOfInfluence range) { + super(name, range); + } + + @Override + public boolean chooseMulligan(Game game) { + if (steps == null) { + return super.chooseMulligan(game); + } + if (current >= steps.size()) { + fail("Tried to mulligan without a test step."); + } + Step step = steps.get(current++); + assertTrue("Expected mulligan step.", + MulliganStep.class.isAssignableFrom(step.getClass())); + return ((MulliganStep) step).mulligan(); + } + + @Override + public boolean chooseScry(Game game, UUID cardId) { + if (steps == null) { + return super.chooseScry(game, cardId); + } + if (current >= steps.size()) { + fail("Tried to scry without a test step."); + } + Step step = steps.get(current++); + assertTrue("Expected scry step.", + ScryStep.class.isAssignableFrom(step.getClass())); + return ((ScryStep) step).scry(); + } + + @Override + public List chooseDiscardBottom(Game game, int count, List cardIds) { + if (steps == null) { + return super.chooseDiscardBottom(game, count, cardIds); + } + if (current >= steps.size()) { + fail("Tried to discard without a test step."); + } + Step step = steps.get(current++); + assertTrue("Expected discard bottom step.", + DiscardBottomStep.class.isAssignableFrom(step.getClass())); + return ((DiscardBottomStep) step).discardBottom(count); + } + + public void setSteps(List steps) { + this.steps = steps; + } + + public void assertStepsComplete() { + assertEquals(steps.size(), current); + } + + } + + static class StubPlayer extends PlayerImpl implements Player { + + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) { + if (target instanceof TargetPlayer) { + for (Player player : game.getPlayers().values()) { + if (player.getId().equals(getId()) && target.canTarget(getId(), game)) { + target.add(player.getId(), game); + return true; + } + } + } + return false; + } + + public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game) { + cards.getCards(game).stream().map(MageItem::getId).forEach(cardId -> target.add(cardId, game)); + return true; + } + + @Override + public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + if ("cards to PUT on the BOTTOM of your library (Discard for Mulligan)".equals(target.getFilter().getMessage())) { + chooseDiscardBottom(game, target.getMinNumberOfTargets(), cards.getCards(game) + .stream().map(MageItem::getId).collect(toList())).forEach(cardId -> target.add(cardId, game)); + } else { + UUID cardId = getOnlyElement(cards.getCards(game)).getId(); + if (chooseScry(game, cardId)) { + target.add(cardId, game); + return true; + } + } + return false; + } + + public List chooseDiscardBottom(Game game, int count, List cardIds) { + return cardIds.subList(0, count); + } + + public boolean chooseScry(Game game, UUID cardId) { + return false; + } + + @Override + public void shuffleLibrary(Ability source, Game game) { + + } + + public StubPlayer(String name, RangeOfInfluence range) { + super(name, range); + } + + @Override + public void abort() { + + } + + @Override + public void skip() { + + } + + @Override + public Player copy() { + return null; + } + + @Override + public boolean priority(Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) { + return false; + } + + @Override + public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseMulligan(Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + return false; + } + + @Override + public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { + return false; + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + return false; + } + + @Override + public boolean choosePile(Outcome outcome, String message, List pile1, List pile2, Game game) { + return false; + } + + @Override + public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) { + return false; + } + + @Override + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + return 0; + } + + @Override + public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { + return 0; + } + + @Override + public int chooseReplacementEffect(Map abilityMap, Game game) { + return 0; + } + + @Override + public TriggeredAbility chooseTriggeredAbility(List abilities, Game game) { + return null; + } + + @Override + public Mode chooseMode(Modes modes, Ability source, Game game) { + return null; + } + + @Override + public void selectAttackers(Game game, UUID attackingPlayerId) { + + } + + @Override + public void selectBlockers(Game game, UUID defendingPlayerId) { + + } + + @Override + public UUID chooseAttackerOrder(List attacker, Game game) { + return null; + } + + @Override + public UUID chooseBlockerOrder(List blockers, CombatGroup combatGroup, List blockerOrder, Game game) { + return null; + } + + @Override + public void assignDamage(int damage, List targets, String singleTargetName, UUID sourceId, Game game) { + + } + + @Override + public int getAmount(int min, int max, String message, Game game) { + return 0; + } + + @Override + public void sideboard(Match match, Deck deck) { + + } + + @Override + public void construct(Tournament tournament, Deck deck) { + + } + + @Override + public void pickCard(List cards, Deck deck, Draft draft) { + + } + + } + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/ParisMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/ParisMulliganTest.java new file mode 100644 index 00000000000..fc99d063ca2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/ParisMulliganTest.java @@ -0,0 +1,181 @@ +package org.mage.test.mulligan; + +import mage.game.mulligan.MulliganType; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; + +public class ParisMulliganTest extends MulliganTestBase { + + @Test + public void testParisMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 0); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testParisMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + hand2.addAll(scenario.getHand()); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testParisMulligan_OneMulligan_Scry() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + hand2.addAll(scenario.getHand()); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testParisMulligan_FreeMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 1); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testParisMulligan_FreeMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand2.addAll(scenario.getHand()); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testParisMulligan_FreeMulligan_TwoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testParisMulligan_AlwaysMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.PARIS, 0); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(4, 36); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(3, 37); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(2, 38); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(1, 39); + return true; + }); + scenario.run(() -> { + scenario.assertSizes(0, 40); + }); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/VancouverMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/VancouverMulliganTest.java new file mode 100644 index 00000000000..1a5e2a5b0eb --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/VancouverMulliganTest.java @@ -0,0 +1,251 @@ +package org.mage.test.mulligan; + +import mage.game.mulligan.MulliganType; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; + +public class VancouverMulliganTest extends MulliganTestBase { + + @Test + public void testVancouverMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 0); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testVancouverMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNTopOfLibrary(1))); + }); + } + + @Test + public void testVancouverMulligan_OneMulligan_Scry() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 0); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand2.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return true; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNBottomOfLibrary(1))); + }); + } + + @Test + public void testVancouverMulligan_FreeMulligan_NoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 1); + Set hand1 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, scenario.getHand()); + }); + } + + @Test + public void testVancouverMulligan_FreeMulligan_OneMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand2.addAll(scenario.getHand()); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getHand())); + }); + } + + @Test + public void testVancouverMulligan_FreeMulligan_TwoMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNTopOfLibrary(1))); + }); + } + + @Test + public void testVancouverMulligan_FreeMulligan_TwoMulligan_Scry() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 1); + Set hand1 = new HashSet<>(); + Set hand2 = new HashSet<>(); + Set hand3 = new HashSet<>(); + Set scry = new HashSet<>(); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + hand1.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + hand2.addAll(scenario.getHand()); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + hand3.addAll(scenario.getHand()); + return false; + }); + scenario.scry(() -> { + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(20, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 7))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + scry.add(scenario.getLibraryTopCard()); + return true; + }); + scenario.run(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand3, new HashSet<>(scenario.getHand())); + assertEquals(scry, new HashSet<>(scenario.getNBottomOfLibrary(1))); + }); + } + + @Test + public void testVancouverMulligan_AlwaysMulligan() { + MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.VANCOUVER, 0); + scenario.mulligan(() -> { + scenario.assertSizes(7, 33); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(4, 36); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(3, 37); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(2, 38); + return true; + }); + scenario.mulligan(() -> { + scenario.assertSizes(1, 39); + return true; + }); + scenario.scry(() -> { + scenario.assertSizes(0, 40); + return false; + }); + scenario.run(() -> { + scenario.assertSizes(0, 40); + }); + } + +} From f7d785e4068fe8d7f11160ad66fe3e73b0edb957 Mon Sep 17 00:00:00 2001 From: John Hitchings Date: Tue, 19 Mar 2019 23:32:18 -0700 Subject: [PATCH 4/5] Add rules comments and handle mulliganing to less than 0 cards for London. --- .../mage/game/mulligan/LondonMulligan.java | 22 ++++++++++++++++++- .../java/mage/game/mulligan/Mulligan.java | 19 +++++++++++++++- .../mage/game/mulligan/VancouverMulligan.java | 7 +++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java index b63304b4ab2..0ced5be7236 100644 --- a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java @@ -22,6 +22,22 @@ public class LondonMulligan extends Mulligan { @Override public void executeMulliganPhase(Game game, int startingHandSize) { + /* + * 103.4. Each player draws a number of cards equal to their starting hand size, which is normally + * seven. (Some effects can modify a player’s starting hand size.) A player who is dissatisfied with + * their initial hand may take a mulligan. First, the starting player declares whether they will + * take a mulligan. Then each other player in turn order does the same. Once each player has made a + * declaration, all players who decided to take mulligans do so at the same time. To take a mulligan, + * a player shuffles the cards in their hand back into their library, draws a new hand of cards equal + * to their starting hand size, then puts a number of those cards onto the bottom of their library in + * any order equal to the number of times that player has taken a mulligan. Once a player chooses not + * to take a mulligan, the remaining cards become the player’s opening hand, and that player may not + * take any further mulligans. This process is then repeated until no player takes a mulligan. A + * player can’t take a number of mulligans greater their starting hand size. + * + * https://magic.wizards.com/en/articles/archive/competitive-gaming/mythic-championship-ii-format-and-london-test-2019-02-21 + */ + for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { startingHandSizes.put(playerId, startingHandSize); } @@ -40,7 +56,6 @@ public class LondonMulligan extends Mulligan { player.chooseTarget(Outcome.Neutral, cards, target, null, game); player.putCardsOnBottomOfLibrary(new CardsImpl(target.getTargets()), game, null, true); cards.removeAll(target.getTargets()); - System.out.println(cardsToDiscard); } } } @@ -62,6 +77,11 @@ public class LondonMulligan extends Mulligan { return startingHandSizes.get(playerId) - deduction; } + @Override + public boolean canTakeMulligan(Game game, Player player) { + return super.canTakeMulligan(game, player) && startingHandSizes.get(player.getId()) > 0; + } + @Override public void mulligan(Game game, UUID playerId) { Player player = game.getPlayer(playerId); diff --git a/Mage/src/main/java/mage/game/mulligan/Mulligan.java b/Mage/src/main/java/mage/game/mulligan/Mulligan.java index c12b6b0e7a9..43abf6c2840 100644 --- a/Mage/src/main/java/mage/game/mulligan/Mulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/Mulligan.java @@ -16,6 +16,19 @@ public abstract class Mulligan { } public void executeMulliganPhase(Game game, int startingHandSize) { + /* + * 103.4. Each player draws a number of cards equal to their starting hand size, + * which is normally seven. (Some effects can modify a player’s starting hand size.) + * A player who is dissatisfied with their initial hand may take a mulligan. First + * the starting player declares whether they will take a mulligan. Then each other + * player in turn order does the same. Once each player has made a declaration, all + * players who decided to take mulligans do so at the same time. To take a mulligan, + * a player shuffles their hand back into their library, then draws a new hand of one + * fewer cards than they had before. If a player kept their hand of cards, those cards + * become the player’s opening hand, and that player may not take any further mulligans. + * This process is then repeated until no player takes a mulligan. (Note that if a + * player’s hand size reaches zero cards, that player must keep that hand.) + */ List keepPlayers = new ArrayList<>(); List mulliganPlayers = new ArrayList<>(); do { @@ -25,7 +38,7 @@ public abstract class Mulligan { Player player = game.getPlayer(playerId); boolean keep = true; while (true) { - if (player.getHand().isEmpty()) { + if (!canTakeMulligan(game, player)) { break; } GameEvent event = new GameEvent(GameEvent.EventType.CAN_TAKE_MULLIGAN, null, null, playerId); @@ -63,6 +76,10 @@ public abstract class Mulligan { public abstract Mulligan copy(); + public boolean canTakeMulligan(Game game, Player player) { + return !player.getHand().isEmpty(); + } + public int getFreeMulligans() { return freeMulligans; } diff --git a/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java b/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java index 0474a4df5fe..9a131224d9f 100644 --- a/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/VancouverMulligan.java @@ -14,7 +14,12 @@ public class VancouverMulligan extends ParisMulligan { @Override public void executeMulliganPhase(Game game, int startingHandSize) { super.executeMulliganPhase(game, startingHandSize); - // new scry rule + /* + * 103.4 (scry rule) - After all players have kept an opening hand, each player in + * turn order whose hand contains fewer cards than that player’s starting hand size + * may look at the top card of their library. If a player does, that player may put + * that card on the bottom of their library. + */ for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { Player player = game.getPlayer(playerId); if (player != null && player.getHand().size() < startingHandSize) { From 1ca876c2ed6a9a5087099c3b6da2864b2f3f0163 Mon Sep 17 00:00:00 2001 From: John Hitchings Date: Wed, 20 Mar 2019 23:23:28 -0700 Subject: [PATCH 5/5] Fixed LondonMulligan to give mulligan choice after discarding to bottom, which fixes the interaction with Serum Powder. --- .../test/mulligan/LondonMulliganTest.java | 138 ++++++++++++------ .../mage/game/mulligan/LondonMulligan.java | 43 +++--- 2 files changed, 114 insertions(+), 67 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java index 5bd8aac5c5b..ed9a7c8abb8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/LondonMulliganTest.java @@ -5,6 +5,7 @@ import mage.game.mulligan.MulliganType; import org.junit.Test; import java.util.*; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -37,25 +38,26 @@ public class LondonMulliganTest extends MulliganTestBase { hand1.addAll(scenario.getHand()); return true; }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - hand2.addAll(scenario.getHand()); - return false; - }); scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(1, count); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - assertEquals(hand2, scenario.getHand()); scenario.getHand().stream().limit(count).forEach(discarded::add); remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); return discarded; }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(remainingHand, scenario.getHand()); + hand2.addAll(scenario.getHand()); + return false; + }); scenario.run(() -> { scenario.assertSizes(6, 34); assertEquals(remainingHand, new HashSet<>(scenario.getHand())); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, scenario.getHand()); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); }); } @@ -73,34 +75,46 @@ public class LondonMulliganTest extends MulliganTestBase { hand1.addAll(scenario.getHand()); return true; }); - scenario.mulligan(() -> { + scenario.discardBottom(count -> { scenario.assertSizes(7, 33); + assertEquals(1, count); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + scenario.getHand().stream().limit(count).forEach(discarded::add); + remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); + return discarded; + }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); hand2.addAll(scenario.getHand()); return true; }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - hand3.addAll(scenario.getHand()); - return false; - }); scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(2, count); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - assertEquals(hand3, scenario.getHand()); + assertEquals(discarded, scenario.getLibraryRangeSize(26, 1)); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + discarded.clear(); + remainingHand.clear(); scenario.getHand().stream().limit(count).forEach(discarded::add); remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); return discarded; }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + assertEquals(discarded, scenario.getNBottomOfLibrary(2)); + hand3.addAll(scenario.getHand()); + return false; + }); scenario.run(() -> { scenario.assertSizes(5, 35); assertEquals(remainingHand, new HashSet<>(scenario.getHand())); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + assertEquals(hand3, scenario.getHand()); assertEquals(discarded, scenario.getNBottomOfLibrary(2)); }); } @@ -162,27 +176,28 @@ public class LondonMulliganTest extends MulliganTestBase { hand2.addAll(scenario.getHand()); return true; }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - hand3.addAll(scenario.getHand()); - return false; - }); scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(1, count); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); - assertEquals(hand3, scenario.getHand()); scenario.getHand().stream().limit(count).forEach(discarded::add); remainingHand.addAll(Sets.difference(scenario.getHand(), new HashSet<>(discarded))); return discarded; }); + scenario.mulligan(() -> { + scenario.assertSizes(6, 34); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(discarded, scenario.getNBottomOfLibrary(1)); + hand3.addAll(scenario.getHand()); + return false; + }); scenario.run(() -> { scenario.assertSizes(6, 34); assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand3, scenario.getHand()); assertEquals(remainingHand, new HashSet<>(scenario.getHand())); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); }); @@ -195,33 +210,64 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.assertSizes(7, 33); return true; }); - scenario.mulligan(() -> { + scenario.discardBottom(count -> { scenario.assertSizes(7, 33); - return true; + assertEquals(1, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); }); scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - return true; - }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - return true; - }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - return true; - }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); - return true; - }); - scenario.mulligan(() -> { - scenario.assertSizes(7, 33); + scenario.assertSizes(6, 34); return true; }); scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(2, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); + }); + scenario.mulligan(() -> { + scenario.assertSizes(5, 35); + return true; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(3, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); + }); + scenario.mulligan(() -> { + scenario.assertSizes(4, 36); + return true; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(4, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); + }); + scenario.mulligan(() -> { + scenario.assertSizes(3, 37); + return true; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(5, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); + }); + scenario.mulligan(() -> { + scenario.assertSizes(2, 38); + return true; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); + assertEquals(6, count); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); + }); + scenario.mulligan(() -> { + scenario.assertSizes(1, 39); + return true; + }); + scenario.discardBottom(count -> { + scenario.assertSizes(7, 33); assertEquals(7, count); - return new ArrayList<>(scenario.getHand()); + return scenario.getHand().stream().limit(count).collect(Collectors.toList()); }); scenario.run(() -> { scenario.assertSizes(0, 40); diff --git a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java index 0ced5be7236..5c9e85a4d69 100644 --- a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java @@ -6,15 +6,17 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.target.TargetCard; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; public class LondonMulligan extends Mulligan { protected Map startingHandSizes = new HashMap<>(); + protected Map openingHandSizes = new HashMap<>(); public LondonMulligan(int freeMulligans) { super(freeMulligans); @@ -39,25 +41,11 @@ public class LondonMulligan extends Mulligan { */ for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { + openingHandSizes.put(playerId, startingHandSize); startingHandSizes.put(playerId, startingHandSize); } super.executeMulliganPhase(game, startingHandSize); - - for (UUID playerId : game.getState().getPlayerList(game.getStartingPlayerId())) { - int handSize = startingHandSizes.get(playerId); - Player player = game.getPlayer(playerId); - if (player != null && player.getHand().size() > handSize) { - int cardsToDiscard = player.getHand().size() - handSize; - Cards cards = new CardsImpl(); - cards.addAll(player.getHand()); - TargetCard target = new TargetCard(cardsToDiscard, cardsToDiscard, Zone.HAND, - new FilterCard("cards to PUT on the BOTTOM of your library (Discard for Mulligan)")); - player.chooseTarget(Outcome.Neutral, cards, target, null, game); - player.putCardsOnBottomOfLibrary(new CardsImpl(target.getTargets()), game, null, true); - cards.removeAll(target.getTargets()); - } - } } @Override @@ -74,18 +62,18 @@ public class LondonMulligan extends Mulligan { deduction = 0; } } - return startingHandSizes.get(playerId) - deduction; + return openingHandSizes.get(playerId) - deduction; } @Override public boolean canTakeMulligan(Game game, Player player) { - return super.canTakeMulligan(game, player) && startingHandSizes.get(player.getId()) > 0; + return super.canTakeMulligan(game, player) && openingHandSizes.get(player.getId()) > 0; } @Override public void mulligan(Game game, UUID playerId) { Player player = game.getPlayer(playerId); - int numCards = player.getHand().size(); + int numCards = startingHandSizes.get(player.getId()); player.getLibrary().addAll(player.getHand().getCards(game), game); player.getHand().clear(); player.shuffleLibrary(null, game); @@ -102,7 +90,7 @@ public class LondonMulligan extends Mulligan { usedFreeMulligans.put(player.getId(), 1); } } - startingHandSizes.put(playerId, startingHandSizes.get(playerId) - deduction); + openingHandSizes.put(playerId, openingHandSizes.get(playerId) - deduction); if (deduction == 0) { game.fireInformEvent(new StringBuilder(player.getLogName()) .append(" mulligans for free.") @@ -115,6 +103,18 @@ public class LondonMulligan extends Mulligan { .append(numCards - deduction == 1 ? " card" : " cards").toString()); } player.drawCards(numCards, game); + + int handSize = openingHandSizes.get(player.getId()); + if (player.getHand().size() > handSize) { + int cardsToDiscard = player.getHand().size() - handSize; + Cards cards = new CardsImpl(); + cards.addAll(player.getHand()); + TargetCard target = new TargetCard(cardsToDiscard, cardsToDiscard, Zone.HAND, + new FilterCard("cards to PUT on the BOTTOM of your library (Discard for Mulligan)")); + player.chooseTarget(Outcome.Neutral, cards, target, null, game); + player.putCardsOnBottomOfLibrary(new CardsImpl(target.getTargets()), game, null, true); + cards.removeAll(target.getTargets()); + } } @Override @@ -123,6 +123,7 @@ public class LondonMulligan extends Mulligan { @Override public LondonMulligan copy() { LondonMulligan mulligan = new LondonMulligan(getFreeMulligans()); + mulligan.openingHandSizes.putAll(openingHandSizes); mulligan.startingHandSizes.putAll(startingHandSizes); return mulligan; }