diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index bb59cc2bdfa..957aaf4e0b5 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -97,13 +97,14 @@ public class GathererSets implements Iterable { "GNT", "UMA", "GRN", "RNA", "WAR", "MH1", "M20", - "C19","ELD","MB1","GN2","J20","THB","UND","C20","IKO","M21", - "JMP","2XM","ZNR","KLR","CMR","KHC","KHM","TSR","STX","STA", - "C21","MH2","AFR","AFC","J21","MID","MIC","VOW","VOC","YMID", - "NEC","NEO","SNC","NCC","CLB","2X2","DMU","DMC","40K","GN3", - "UNF","BRO","BRC","BOT","30A","J22","SCD","DMR","ONE","ONC", - "MOM","MOC","MUL","MAT","LTR","CMM","WOE","WHO","RVR","WOT", - "WOC","SPG","LCI","LCC","REX" + "C19", "ELD", "MB1", "GN2", "J20", "THB", "UND", "C20", "IKO", "M21", + "JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA", + "C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID", + "NEC", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3", + "UNF", "BRO", "BRC", "BOT", "30A", "J22", "SCD", "DMR", "ONE", "ONC", + "MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT", + "WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ", + "OTC", "OTP", "BIG", "MH3", "ACR", "BLB" // "HHO", "ANA" -- do not exist on gatherer }; diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java index 3a807721fac..af3aa085f73 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java @@ -1,8 +1,14 @@ package mage.sets; +import mage.cards.Card; import mage.cards.ExpansionSet; +import mage.cards.repository.CardInfo; import mage.constants.Rarity; import mage.constants.SetType; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; /** * @author TheElk801 @@ -19,7 +25,8 @@ public final class OutlawsOfThunderJunction extends ExpansionSet { super("Outlaws of Thunder Junction", "OTJ", ExpansionSet.buildDate(2024, 4, 19), SetType.EXPANSION); this.blockName = "Outlaws of Thunder Junction"; // for sorting in GUI this.hasBasicLands = true; - this.hasBoosters = false; // temporary + this.hasBoosters = true; + this.maxCardNumberInBooster = 276; cards.add(new SetCardInfo("Abraded Bluffs", 251, Rarity.COMMON, mage.cards.a.AbradedBluffs.class)); cards.add(new SetCardInfo("Akul the Unrepentant", 189, Rarity.RARE, mage.cards.a.AkulTheUnrepentant.class)); @@ -297,4 +304,213 @@ public final class OutlawsOfThunderJunction extends ExpansionSet { cards.add(new SetCardInfo("Wrangler of the Damned", 238, Rarity.UNCOMMON, mage.cards.w.WranglerOfTheDamned.class)); cards.add(new SetCardInfo("Wylie Duke, Atiin Hero", 239, Rarity.RARE, mage.cards.w.WylieDukeAtiinHero.class)); } + + private Set specialLands = new HashSet<>(Arrays.asList(251, 253, 255, 256, 257, 258, 259, 260, 261, 264)); + + // otp: 30 rare, 15 mythic, otj: 60, 20 + private static double ratioRareMythicOfOtpInFoilSlot = (30.0 * 2.0 + 15.0) / ((30.0 + 60.0) * 2.0 + (15.0 + 20.0)); + private static double ratioMythic = 20.0 / (20.0 + 60.0 * 2.0); + private static double ratioOTPMythic = 15.0 / (15.0 + 30.0 * 2.0); + // otp: 20, otj: 100 + private static double ratioUncommonOPTInFoilSlot = 20.0 / (20.0 + 100.0); + + @Override + public List tryBooster() { + // TODO: make part of this more generic, this is the first try at a play booster generation. + // We start by deciding the various slots. + // Land Slot: 1/2 chance for a basic, 1/2 chance for a nonbasic in the special list + int basicLand = 0; + int nonbasicLand = 0; + { + if (RandomUtil.nextDouble() <= 0.5) { + basicLand++; + } else { + nonbasicLand++; + } + } + + // 1 slot is guarantee opt. + int otpUncommon = 0; + int otpRareOrMythic = 0; + { + double rollOtp = RandomUtil.nextDouble(); + if (rollOtp >= 1.0 / 3.0) { // know probability of 2/3 to have an uncommon. + otpUncommon++; + } else { + otpRareOrMythic++; + } + } + + // 8 slots have guarantee rarity + int rareOrMythic = 1; + int uncommon = 3; + int common = 5; + + // 1 slot is 1/64 chance to be spg, and 1/5 - 1/64 to be otp, 4/5 to be common + int spg = 0; + int big = 0; + { + double rollBig = RandomUtil.nextDouble(); + if (rollBig <= 1.0 / 64.0) { // know probability of 1/64 to be spg + spg++; + } else if (rollBig <= 1.0 / 5.0) { + big++; + } else { + common++; + } + } + + // 1 slot is a wildcard, with 1/12 to be r/m as a known info. + // MISSING INFO: relative chance of C/U in that slot. Let's assume 3/12 uncommon and 8/12 common. + // MISSING INFO: what about the special common lands? do they count here? + { + double rollWildcard = RandomUtil.nextDouble(); + if (rollWildcard <= 1.0 / 12.0) { + rareOrMythic++; + } else if (rollWildcard <= 4.0 / 12.0) { + uncommon++; + } else { + if (rollWildcard >= 1.0 - (8.0 / 12.0) * 10.0 / (10.0 + 81.0)) { + nonbasicLand++; + } else { + common++; + } + } + } + + // 1 slot is a (foil) wildcard that can be otp, we know nothing more here. + // MISSING INFO: all the following chances are made up + { + double rollFoilWildcard = RandomUtil.nextDouble(); + if (rollFoilWildcard <= 1.0 / 12.0) { + // Let's assume any of the rare among set + otp have same chance, that is twice the chance of mythic + if (rollFoilWildcard <= (1.0 / 12.0) * ratioRareMythicOfOtpInFoilSlot) { + otpRareOrMythic++; + } else { + rareOrMythic++; + } + } else if (rollFoilWildcard <= 4.0 / 12.0) { + if (rollFoilWildcard <= (1.0 / 12.0) + (3.0 / 12.0) * ratioUncommonOPTInFoilSlot) { + otpUncommon++; + } else { + uncommon++; + } + } else { + common++; + } + } + + /* + int total = rareOrMythic + uncommon + common + nonbasicLand + basicLand + otpRareOrMythic + otpUncommon + big + spg; + System.out.println( + "Total" + total + + "R" + rareOrMythic + " U" + uncommon + " C" + common + + " SL" + nonbasicLand + " B" + basicLand + + " OTP-R" + otpRareOrMythic + " OPT-U" + otpUncommon + + " BIG" + big + " SPG" + spg + ); + */ + + // The booster we are building + List booster = new ArrayList<>(); + + List list_OTJ_C_And_SL = + getCardsByRarity(Rarity.COMMON).stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_C = // All commons, minus the special lands + list_OTJ_C_And_SL.stream() + .filter(info -> !(specialLands.contains(info.getCardNumberAsInt()))) + .collect(Collectors.toList()); + List list_OTJ_SL = + list_OTJ_C_And_SL.stream() + .filter(info -> specialLands.contains(info.getCardNumberAsInt())) + .collect(Collectors.toList()); + List list_OTJ_U = + getCardsByRarity(Rarity.UNCOMMON) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_R = + getCardsByRarity(Rarity.RARE) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_M = + getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_Basic = + getCardsByRarity(Rarity.LAND) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTP_U = + BreakingNews.getInstance().getCardsByRarity(Rarity.UNCOMMON) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_OTP_R = + BreakingNews.getInstance().getCardsByRarity(Rarity.RARE) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_OTP_M = + BreakingNews.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_BIG = + TheBigScore.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 30) + .collect(Collectors.toList()); + List list_SPG = + SpecialGuests.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> { + int cn = info.getCardNumberAsInt(); + return cn >= 29 && cn <= 38; + }) + .collect(Collectors.toList()); + + for (int i = 0; i < spg; i++) { + addToBooster(booster, list_SPG); + } + for (int i = 0; i < big; i++) { + addToBooster(booster, list_BIG); + } + for (int i = 0; i < rareOrMythic; i++) { + if (RandomUtil.nextDouble() <= ratioMythic) { + addToBooster(booster, list_OTJ_M); + } else { + addToBooster(booster, list_OTJ_R); + } + } + for (int i = 0; i < otpRareOrMythic; i++) { + if (RandomUtil.nextDouble() <= ratioOTPMythic) { + addToBooster(booster, list_OTP_M); + } else { + addToBooster(booster, list_OTP_R); + } + } + for (int i = 0; i < otpUncommon; i++) { + addToBooster(booster, list_OTP_U); + } + for (int i = 0; i < uncommon; i++) { + addToBooster(booster, list_OTJ_U); + } + for (int i = 0; i < common; i++) { + addToBooster(booster, list_OTJ_C); + } + for (int i = 0; i < nonbasicLand; i++) { + addToBooster(booster, list_OTJ_SL); + } + for (int i = 0; i < basicLand; i++) { + addToBooster(booster, list_OTJ_Basic); + } + + return booster; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java index b932679cb4e..65bec496a91 100644 --- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java @@ -14,6 +14,7 @@ import mage.game.draft.RemixedSet; import mage.sets.*; import mage.util.CardUtil; import org.junit.Assert; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -22,8 +23,6 @@ import org.mage.test.serverside.base.MageTestPlayerBase; import java.util.*; import java.util.stream.Collectors; -import static org.junit.Assert.*; - /** * @author nigelzor, JayDi85 */ @@ -551,7 +550,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase { @Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081 @Test public void test_CollectBoosterStats() { - ExpansionSet setToAnalyse = FallenEmpires.getInstance(); + ExpansionSet setToAnalyse = OutlawsOfThunderJunction.getInstance(); int openBoosters = 10000; Map resRatio = new HashMap<>(); @@ -560,7 +559,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase { List booster = setToAnalyse.createBooster(); totalCards += booster.size(); booster.forEach(card -> { - String code = String.format("%s %s", card.getRarity().getCode(), card.getName()); + String code = String.format("%s %s %s", card.getExpansionSetCode(), card.getRarity().getCode(), card.getName()); resRatio.putIfAbsent(code, 0); resRatio.computeIfPresent(code, (u, count) -> count + 1); });