mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[OTJ] Implement custom play booster generation
Much can be improved from there, but it is a rough first implementation of a slot-based play booster.
This commit is contained in:
parent
71b267a3ca
commit
3e75f93c20
3 changed files with 228 additions and 12 deletions
|
|
@ -97,13 +97,14 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
"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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Integer> 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<Card> 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<Card> booster = new ArrayList<>();
|
||||
|
||||
List<CardInfo> list_OTJ_C_And_SL =
|
||||
getCardsByRarity(Rarity.COMMON).stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> 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<CardInfo> list_OTJ_SL =
|
||||
list_OTJ_C_And_SL.stream()
|
||||
.filter(info -> specialLands.contains(info.getCardNumberAsInt()))
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTJ_U =
|
||||
getCardsByRarity(Rarity.UNCOMMON)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTJ_R =
|
||||
getCardsByRarity(Rarity.RARE)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTJ_M =
|
||||
getCardsByRarity(Rarity.MYTHIC)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTJ_Basic =
|
||||
getCardsByRarity(Rarity.LAND)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTP_U =
|
||||
BreakingNews.getInstance().getCardsByRarity(Rarity.UNCOMMON)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= 65)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTP_R =
|
||||
BreakingNews.getInstance().getCardsByRarity(Rarity.RARE)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= 65)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_OTP_M =
|
||||
BreakingNews.getInstance().getCardsByRarity(Rarity.MYTHIC)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= 65)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> list_BIG =
|
||||
TheBigScore.getInstance().getCardsByRarity(Rarity.MYTHIC)
|
||||
.stream()
|
||||
.filter(info -> info.getCardNumberAsInt() <= 30)
|
||||
.collect(Collectors.toList());
|
||||
List<CardInfo> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Integer> resRatio = new HashMap<>();
|
||||
|
|
@ -560,7 +559,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase {
|
|||
List<Card> 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);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue