From c50e9133985df11436c942163f87120f7b57661f Mon Sep 17 00:00:00 2001 From: ssk97 Date: Sun, 27 Aug 2023 12:08:27 -0700 Subject: [PATCH] Add Smoothed London Mulligan option (#10965) * Add Smoothed London Mulligan (similar to but weaker than MTGA's) * Make SmoothedLondonMulligan extend LondonMulligan instead of copying code * modified to be have no effect within +1/-1 of the expected lands fixed tests by always putting nonchosen hand on the bottom * Inherit the primary mulligan logic as well, add comments * Make drawHand public and part of Mulligan, use it on opening 7 use Card::isLand instead of reimplementing it, remove unused imports Use standard spacing * Better account for half-land MDFCs * Don't count TDFCs as half-lands * Remove "crossover_point" calculation to make algorithm clearer * Genericize the tests, undo changed access that's no longer needed, unbox bool * Use standard case in function naming * Add Override * Add mulligan type to TableView info, add tourneyMatchOptions local variable --- .../src/main/java/mage/view/TableView.java | 21 ++++-- .../test/mulligan/LondonMulliganTest.java | 67 +++++++++-------- .../mage/test/mulligan/MulliganTestBase.java | 6 +- .../mulligan/SmoothedLondonMulliganTest.java | 13 ++++ Mage/src/main/java/mage/game/GameImpl.java | 2 +- .../mage/game/mulligan/LondonMulligan.java | 2 +- .../java/mage/game/mulligan/Mulligan.java | 3 + .../java/mage/game/mulligan/MulliganType.java | 3 + .../mage/game/mulligan/ParisMulligan.java | 2 +- .../game/mulligan/SmoothedLondonMulligan.java | 75 +++++++++++++++++++ 10 files changed, 153 insertions(+), 41 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/mulligan/SmoothedLondonMulliganTest.java create mode 100644 Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java diff --git a/Mage.Common/src/main/java/mage/view/TableView.java b/Mage.Common/src/main/java/mage/view/TableView.java index 9bd2403f02a..0c016a1e84e 100644 --- a/Mage.Common/src/main/java/mage/view/TableView.java +++ b/Mage.Common/src/main/java/mage/view/TableView.java @@ -7,7 +7,9 @@ import mage.game.Seat; import mage.game.Table; import mage.game.draft.Draft; import mage.game.draft.DraftOptions; +import mage.game.match.MatchOptions; import mage.game.match.MatchPlayer; +import mage.game.mulligan.MulliganType; import mage.game.tournament.TournamentPlayer; import java.io.Serializable; @@ -99,6 +101,9 @@ public class TableView implements Serializable { addInfo.append("Wins:").append(table.getMatch().getWinsNeeded()); addInfo.append(" Time: ").append(table.getMatch().getOptions().getMatchTimeLimit().toString()); addInfo.append(" Buffer: ").append(table.getMatch().getOptions().getMatchBufferTime().toString()); + if (table.getMatch().getOptions().getMulliganType() != MulliganType.GAME_DEFAULT){ + addInfo.append(" Mulligan: \"").append(table.getMatch().getOptions().getMulliganType().toString()).append("\""); + } if (table.getMatch().getFreeMulligans() > 0) { addInfo.append(" FM: ").append(table.getMatch().getFreeMulligans()); } @@ -150,10 +155,14 @@ public class TableView implements Serializable { if (TableState.WAITING.equals(table.getState())) { stateText.append(" (").append(table.getTournament().getPlayers().size()).append('/').append(table.getNumberOfSeats()).append(')'); } - infoText.append(" Time: ").append(table.getTournament().getOptions().getMatchOptions().getMatchTimeLimit().toString()); - infoText.append(" Buffer: ").append(table.getTournament().getOptions().getMatchOptions().getMatchBufferTime().toString()); - if (table.getTournament().getOptions().getMatchOptions().getFreeMulligans() > 0) { - infoText.append(" FM: ").append(table.getTournament().getOptions().getMatchOptions().getFreeMulligans()); + MatchOptions tourneyMatchOptions = table.getTournament().getOptions().getMatchOptions(); + infoText.append(" Time: ").append(tourneyMatchOptions.getMatchTimeLimit().toString()); + infoText.append(" Buffer: ").append(tourneyMatchOptions.getMatchBufferTime().toString()); + if (tourneyMatchOptions.getMulliganType() != MulliganType.GAME_DEFAULT){ + infoText.append(" Mulligan: \"").append(tourneyMatchOptions.getMulliganType().toString()).append("\""); + } + if (tourneyMatchOptions.getFreeMulligans() > 0) { + infoText.append(" FM: ").append(tourneyMatchOptions.getFreeMulligans()); } if (table.getTournament().getTournamentType().isLimited()) { infoText.append(" Constr.: ").append(table.getTournament().getOptions().getLimitedOptions().getConstructionTime() / 60).append(" Min."); @@ -162,10 +171,10 @@ public class TableView implements Serializable { DraftOptions draftOptions = (DraftOptions) table.getTournament().getOptions().getLimitedOptions(); infoText.append(" Pick time: ").append(draftOptions.getTiming().getShortName()); } - if (table.getTournament().getOptions().getMatchOptions().isRollbackTurnsAllowed()) { + if (tourneyMatchOptions.isRollbackTurnsAllowed()) { infoText.append(" RB"); } - if (table.getTournament().getOptions().getMatchOptions().isPlaneChase()) { + if (tourneyMatchOptions.isPlaneChase()) { infoText.append(" PC"); } if (table.getTournament().getOptions().isWatchingAllowed()) { 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 99b329ee01b..0f0f242ef7f 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 @@ -10,10 +10,15 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; public class LondonMulliganTest extends MulliganTestBase { - + protected MulliganType getMullType() { + return MulliganType.LONDON; + } + protected int getCardsPerMull() { + return 7; + } @Test public void testLondonMulligan_NoMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 0); Set hand1 = new HashSet<>(); scenario.mulligan(() -> { scenario.assertSizes(7, 33); @@ -28,7 +33,7 @@ public class LondonMulliganTest extends MulliganTestBase { @Test public void testLondonMulligan_OneMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 0); Set hand1 = new HashSet<>(); Set hand2 = new HashSet<>(); List discarded = new ArrayList<>(); @@ -41,14 +46,14 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(1, count); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 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))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); assertEquals(remainingHand, scenario.getHand()); hand2.addAll(scenario.getHand()); return false; @@ -56,7 +61,7 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.run(() -> { scenario.assertSizes(6, 34); assertEquals(remainingHand, new HashSet<>(scenario.getHand())); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); assertEquals(hand2, scenario.getHand()); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); }); @@ -64,7 +69,7 @@ public class LondonMulliganTest extends MulliganTestBase { @Test public void testLondonMulligan_TwoMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 0); Set hand1 = new HashSet<>(); Set hand2 = new HashSet<>(); Set hand3 = new HashSet<>(); @@ -78,23 +83,23 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(1, count); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 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))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); hand2.addAll(scenario.getHand()); return true; }); scenario.discardBottom(count -> { scenario.assertSizes(7, 33); assertEquals(1, count); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(discarded, scenario.getLibraryRangeSize(26, 1)); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(discarded, scenario.getLibraryRangeSize(33-getCardsPerMull(), 1)); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(34-getCardsPerMull(), 6))); discarded.clear(); remainingHand.clear(); scenario.getHand().stream().limit(count).forEach(discarded::add); @@ -104,9 +109,9 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.discardBottom(count -> { scenario.assertSizes(6, 34); assertEquals(1, count); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(34-getCardsPerMull(), 6))); discarded.clear(); remainingHand.clear(); scenario.getHand().stream().limit(count).forEach(discarded::add); @@ -115,8 +120,8 @@ public class LondonMulliganTest extends MulliganTestBase { }); scenario.mulligan(() -> { scenario.assertSizes(5, 35); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(27, 6))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(34-getCardsPerMull(), 6))); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); hand3.addAll(scenario.getHand()); return false; @@ -124,8 +129,8 @@ public class LondonMulliganTest extends MulliganTestBase { 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(27, 6))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(34-getCardsPerMull(), 6))); assertEquals(hand3, scenario.getHand()); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); }); @@ -133,7 +138,7 @@ public class LondonMulliganTest extends MulliganTestBase { @Test public void testLondonMulligan_FreeMulligan_NoMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 1); Set hand1 = new HashSet<>(); scenario.mulligan(() -> { scenario.assertSizes(7, 33); @@ -148,7 +153,7 @@ public class LondonMulliganTest extends MulliganTestBase { @Test public void testLondonMulligan_FreeMulligan_OneMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 1); Set hand1 = new HashSet<>(); Set hand2 = new HashSet<>(); scenario.mulligan(() -> { @@ -159,19 +164,19 @@ public class LondonMulliganTest extends MulliganTestBase { scenario.mulligan(() -> { scenario.assertSizes(7, 33); hand2.addAll(scenario.getHand()); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); return false; }); scenario.run(() -> { scenario.assertSizes(7, 33); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); assertEquals(hand2, new HashSet<>(scenario.getHand())); }); } @Test public void testLondonMulligan_FreeMulligan_TwoMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 1); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 1); Set hand1 = new HashSet<>(); Set hand2 = new HashSet<>(); Set hand3 = new HashSet<>(); @@ -184,31 +189,31 @@ public class LondonMulliganTest extends MulliganTestBase { }); scenario.mulligan(() -> { scenario.assertSizes(7, 33); - assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); hand2.addAll(scenario.getHand()); return true; }); 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(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 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(19, 7))); - assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(26, 7))); + assertEquals(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 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(hand1, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull()*2, 7))); + assertEquals(hand2, new HashSet<>(scenario.getLibraryRangeSize(33-getCardsPerMull(), 7))); assertEquals(hand3, scenario.getHand()); assertEquals(remainingHand, new HashSet<>(scenario.getHand())); assertEquals(discarded, scenario.getNBottomOfLibrary(1)); @@ -217,7 +222,7 @@ public class LondonMulliganTest extends MulliganTestBase { @Test public void testLondonMulligan_AlwaysMulligan() { - MulliganScenarioTest scenario = new MulliganScenarioTest(MulliganType.LONDON, 0); + MulliganScenarioTest scenario = new MulliganScenarioTest(getMullType(), 0); scenario.mulligan(() -> { scenario.assertSizes(7, 33); return true; 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 index b830ef85bb4..56b9ba5edb5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/MulliganTestBase.java @@ -3,6 +3,7 @@ package org.mage.test.mulligan; import mage.cards.CardSetInfo; import mage.cards.basiclands.Forest; import mage.cards.decks.Deck; +import mage.cards.s.Squire; import mage.constants.RangeOfInfluence; import mage.game.Game; import mage.game.GameOptions; @@ -141,7 +142,10 @@ public class MulliganTestBase { 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) + .limit(count/2+(count & 1)) //If odd number of cards, add one extra forest + .forEach(deck.getCards()::add); + Stream.generate(() -> new Squire(playerId, new CardSetInfo("Squire", "TEST", "2", LAND))) + .limit(count/2) .forEach(deck.getCards()::add); return deck; } diff --git a/Mage.Tests/src/test/java/org/mage/test/mulligan/SmoothedLondonMulliganTest.java b/Mage.Tests/src/test/java/org/mage/test/mulligan/SmoothedLondonMulliganTest.java new file mode 100644 index 00000000000..cf53523dd83 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/mulligan/SmoothedLondonMulliganTest.java @@ -0,0 +1,13 @@ +package org.mage.test.mulligan; +import mage.game.mulligan.MulliganType; + +public class SmoothedLondonMulliganTest extends LondonMulliganTest { + @Override + protected MulliganType getMullType() { + return MulliganType.SMOOTHED_LONDON; + } + @Override + protected int getCardsPerMull() { + return 14; + } +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 721b5986ff5..df6f4748231 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1263,7 +1263,7 @@ public abstract class GameImpl implements Game { player.initLife(this.getStartingLife()); } if (!gameOptions.testMode) { - player.drawCards(startingHandSize, null, this); + mulligan.drawHand(startingHandSize, player, this); } } diff --git a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java index 99bc94d36dc..ebc6bef1d07 100644 --- a/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/LondonMulligan.java @@ -107,7 +107,7 @@ public class LondonMulligan extends Mulligan { newHandSize + (newHandSize == 1 ? " card" : " cards")); } - player.drawCards(numCards, null, game); + drawHand(numCards, player, game); while (player.canRespond() && player.getHand().size() > newHandSize) { Target target = new TargetCardInHand(new FilterCard("card (" + (player.getHand().size() - newHandSize) + " more) to put on the bottom of your library")); diff --git a/Mage/src/main/java/mage/game/mulligan/Mulligan.java b/Mage/src/main/java/mage/game/mulligan/Mulligan.java index 6f8a2863bfb..eb65030d5f7 100644 --- a/Mage/src/main/java/mage/game/mulligan/Mulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/Mulligan.java @@ -91,4 +91,7 @@ public abstract class Mulligan implements Serializable { return freeMulligans; } + public void drawHand(int numCards, Player player, Game game){ + player.drawCards(numCards, null, game); + } } diff --git a/Mage/src/main/java/mage/game/mulligan/MulliganType.java b/Mage/src/main/java/mage/game/mulligan/MulliganType.java index a22394d3944..7902047ae5f 100644 --- a/Mage/src/main/java/mage/game/mulligan/MulliganType.java +++ b/Mage/src/main/java/mage/game/mulligan/MulliganType.java @@ -8,6 +8,7 @@ public enum MulliganType { VANCOUVER("Vancouver"), PARIS("Paris"), LONDON("London"), + SMOOTHED_LONDON("Smoothed London"), CANADIAN_HIGHLANDER("Canadian Highlander"); private final String displayName; @@ -24,6 +25,8 @@ public enum MulliganType { return new CanadianHighlanderMulligan(freeMulligans); case VANCOUVER: return new VancouverMulligan(freeMulligans); + case SMOOTHED_LONDON: + return new SmoothedLondonMulligan(freeMulligans); default: case LONDON: return new LondonMulligan(freeMulligans); diff --git a/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java b/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java index 37f933a2d5e..735873b1d81 100644 --- a/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java +++ b/Mage/src/main/java/mage/game/mulligan/ParisMulligan.java @@ -57,7 +57,7 @@ public class ParisMulligan extends Mulligan { .append(deduction == 0 ? " for free and draws " : " down to ") .append((numCards - deduction)) .append(numCards - deduction == 1 ? " card" : " cards").toString()); - player.drawCards(numCards - deduction, null, game); + drawHand(numCards - deduction, player, game); } @Override diff --git a/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java b/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java new file mode 100644 index 00000000000..3fedd02d7ab --- /dev/null +++ b/Mage/src/main/java/mage/game/mulligan/SmoothedLondonMulligan.java @@ -0,0 +1,75 @@ +package mage.game.mulligan; + +import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.cards.ModalDoubleFacedCard; +import mage.game.Game; +import mage.players.Player; +import mage.util.RandomUtil; + +import java.util.*; + +public class SmoothedLondonMulligan extends LondonMulligan { + + public SmoothedLondonMulligan(int freeMulligans) { + super(freeMulligans); + } + + SmoothedLondonMulligan(final SmoothedLondonMulligan mulligan) { + super(mulligan); + } + + private static double countLands(Collection cards, boolean library){ + double land_count = 0; + for (Card card : cards){ + if (card.isLand()) { + land_count += 1; + } else if (card instanceof ModalDoubleFacedCard && ((ModalDoubleFacedCard)card).getRightHalfCard().isLand()){ + if (library) { //count MDFCs with a nonland front and a land back as: + land_count += 0.5;//half a land in a library + } else if (RandomUtil.nextBoolean()){ + land_count += 1; //randomly as a land or nonland in a hand + // This avoids the bias problem where adjusting the deck land ratio to be (integer vs X.5)/7 + // can greatly affect the chance of drawing an MDFC + } + } + } + return land_count; + } + @Override + public void drawHand(int numCards, Player player, Game game){ + List library = player.getLibrary().getCards(game); + if (library.size() >= numCards*2 && numCards > 1) { + double land_ratio = countLands(library, true) / (double) library.size(); + Set hand1 = player.getLibrary().getTopCards(game, numCards); + Set hand2 = player.getLibrary().getTopCards(game, numCards * 2); + hand2.removeAll(hand1); + double hand1_ratio = countLands(hand1, false) / (double) numCards; + double hand2_ratio = countLands(hand2, false) / (double) numCards; + //distance = max(0,abs(land_ratio-hand_ratio)-0.15)+random()*0.3 + //Where land_ratio is (deck lands/deck size) and hand_ratio is (hand lands/hand size) + //Keeps whichever hand's distance is smaller. Note that a 1-land difference is 1/7 = 0.143 + //So -0.15 means that there's no change in relative probabilities if within +1/-1 of the expected amount + double hand1_distance = Math.max(0,Math.abs(land_ratio - hand1_ratio)-0.15)+RandomUtil.nextDouble()*0.3; + double hand2_distance = Math.max(0,Math.abs(land_ratio - hand2_ratio)-0.15)+RandomUtil.nextDouble()*0.3; + //game.debugMessage("1: "+hand1_ratio+", 2 = "+hand2_ratio+", expected = "+land_ratio); + //game.debugMessage("hand1: "+hand1_distance+", hand2: "+hand2_distance); + if (hand1_distance < hand2_distance) { + player.drawCards(numCards, null, game); + player.putCardsOnBottomOfLibrary(new CardsImpl(hand2), game, null, false); + //These are immediately shuffled away, but needed for consistent testing + } else { + player.putCardsOnBottomOfLibrary(new CardsImpl(hand1), game, null, false); + player.drawCards(numCards, null, game); + } + player.shuffleLibrary(null, game); + } else { //not enough cards in library or hand, just do a normal draw instead + player.drawCards(numCards, null, game); + } + } + + @Override + public SmoothedLondonMulligan copy() { + return new SmoothedLondonMulligan(this); + } +}