mirror of
https://github.com/correl/mage.git
synced 2024-12-25 11:11:16 +00:00
* Game: fixed wrong booster's cards ratio in sets with various arts (#7333)
This commit is contained in:
parent
00ebef654f
commit
e48f024909
4 changed files with 158 additions and 23 deletions
|
@ -3515,6 +3515,7 @@ public class MysteryBooster extends ExpansionSet {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Card> create15CardBooster() {
|
public List<Card> create15CardBooster() {
|
||||||
|
// ignore special partner generation for 15 booster
|
||||||
return this.createBooster();
|
return this.createBooster();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import mage.constants.CardType;
|
||||||
import mage.constants.Rarity;
|
import mage.constants.Rarity;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.sets.*;
|
import mage.sets.*;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
@ -249,6 +250,42 @@ public class BoosterGenerationTest extends MageTestBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFallenEmpires_BoosterMustUseVariousArtsButUnique() {
|
||||||
|
// Related issue: https://github.com/magefree/mage/issues/7333
|
||||||
|
// Actual for default boosters without collation
|
||||||
|
Set<String> cardNumberPosfixes = new HashSet<>();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
List<Card> booster = FallenEmpires.getInstance().createBooster();
|
||||||
|
|
||||||
|
// must have single version of the card for booster generation
|
||||||
|
Map<String, Long> stats = FallenEmpires.getInstance().getCardsByRarity(Rarity.COMMON)
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.groupingBy(CardInfo::getName, Collectors.counting()));
|
||||||
|
String multipleCopies = stats.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(data -> data.getValue() > 1)
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
assertTrue("booster generation must use cards with various arts as one card: " + multipleCopies, multipleCopies.isEmpty());
|
||||||
|
|
||||||
|
// must have all reprints
|
||||||
|
booster.forEach(card -> {
|
||||||
|
// 123c -> c
|
||||||
|
String postfix = card.getCardNumber().replace(String.valueOf(CardUtil.parseCardNumberAsInt(card.getCardNumber())), "");
|
||||||
|
if (!postfix.isEmpty()) {
|
||||||
|
cardNumberPosfixes.add(postfix);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
assertTrue("booster must use cards with various arts",
|
||||||
|
cardNumberPosfixes.contains("a")
|
||||||
|
&& cardNumberPosfixes.contains("b")
|
||||||
|
&& cardNumberPosfixes.contains("c")
|
||||||
|
&& cardNumberPosfixes.contains("d")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testZendikarRising_MDFC() {
|
public void testZendikarRising_MDFC() {
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
|
@ -514,7 +551,7 @@ public class BoosterGenerationTest extends MageTestBase {
|
||||||
@Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081
|
@Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081
|
||||||
@Test
|
@Test
|
||||||
public void test_CollectBoosterStats() {
|
public void test_CollectBoosterStats() {
|
||||||
ExpansionSet setToAnalyse = Innistrad.getInstance();
|
ExpansionSet setToAnalyse = FallenEmpires.getInstance();
|
||||||
int openBoosters = 1000;
|
int openBoosters = 1000;
|
||||||
|
|
||||||
Map<String, Integer> resRatio = new HashMap<>();
|
Map<String, Integer> resRatio = new HashMap<>();
|
||||||
|
|
|
@ -144,6 +144,7 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
|
|
||||||
protected final EnumMap<Rarity, List<CardInfo>> savedCards = new EnumMap<>(Rarity.class);
|
protected final EnumMap<Rarity, List<CardInfo>> savedCards = new EnumMap<>(Rarity.class);
|
||||||
protected final EnumMap<Rarity, List<CardInfo>> savedSpecialCards = new EnumMap<>(Rarity.class);
|
protected final EnumMap<Rarity, List<CardInfo>> savedSpecialCards = new EnumMap<>(Rarity.class);
|
||||||
|
protected Map<String, List<CardInfo>> savedReprints = null;
|
||||||
protected final Map<String, CardInfo> inBoosterMap = new HashMap<>();
|
protected final Map<String, CardInfo> inBoosterMap = new HashMap<>();
|
||||||
|
|
||||||
public ExpansionSet(String name, String code, Date releaseDate, SetType setType) {
|
public ExpansionSet(String name, String code, Date releaseDate, SetType setType) {
|
||||||
|
@ -223,16 +224,19 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addToBooster(List<Card> booster, List<CardInfo> cards) {
|
protected void addToBooster(List<Card> booster, List<CardInfo> cards) {
|
||||||
if (!cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CardInfo cardInfo = cards.remove(RandomUtil.nextInt(cards.size()));
|
CardInfo cardInfo = cards.remove(RandomUtil.nextInt(cards.size()));
|
||||||
if (cardInfo != null) {
|
|
||||||
Card card = cardInfo.getCard();
|
Card card = cardInfo.getCard();
|
||||||
if (card != null) {
|
if (card == null) {
|
||||||
|
// card with error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
booster.add(card);
|
booster.add(card);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BoosterCollator createCollator() {
|
public BoosterCollator createCollator() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -247,13 +251,13 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
for (int i = 0; i < 100; i++) {//don't want to somehow loop forever
|
for (int i = 0; i < 100; i++) {//don't want to somehow loop forever
|
||||||
List<Card> booster = tryBooster();
|
List<Card> booster = tryBooster();
|
||||||
if (boosterIsValid(booster)) {
|
if (boosterIsValid(booster)) {
|
||||||
return booster;
|
return addReprints(booster);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return random booster if can't do valid
|
// return random booster if can't do valid
|
||||||
logger.error(String.format("Can't generate valid booster for set [%s - %s]", this.getCode(), this.getName()));
|
logger.error(String.format("Can't generate valid booster for set [%s - %s]", this.getCode(), this.getName()));
|
||||||
return tryBooster();
|
return addReprints(tryBooster());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Card> createBoosterUsingCollator(BoosterCollator collator) {
|
private List<Card> createBoosterUsingCollator(BoosterCollator collator) {
|
||||||
|
@ -384,7 +388,16 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
return ratioBoosterSpecialMythic > 0 && ratioBoosterSpecialMythic * RandomUtil.nextDouble() <= 1;
|
return ratioBoosterSpecialMythic > 0 && ratioBoosterSpecialMythic * RandomUtil.nextDouble() <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Card> tryBooster() {
|
/**
|
||||||
|
* Generates a single booster by rarity ratio in sets without custom collator
|
||||||
|
*/
|
||||||
|
protected List<Card> tryBooster() {
|
||||||
|
// Booster generating proccess must use:
|
||||||
|
// * unique cards list for ratio calculation (see removeReprints)
|
||||||
|
// * reprints for final result (see addReprints)
|
||||||
|
//
|
||||||
|
// BUT there is possible a card's duplication, see https://www.mtgsalvation.com/forums/magic-fundamentals/magic-general/554944-do-booster-packs-ever-contain-two-of-the-same-card
|
||||||
|
|
||||||
List<Card> booster = new ArrayList<>();
|
List<Card> booster = new ArrayList<>();
|
||||||
if (!hasBoosters) {
|
if (!hasBoosters) {
|
||||||
return booster;
|
return booster;
|
||||||
|
@ -491,10 +504,75 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
return hasBasicLands;
|
return hasBasicLands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep only unique cards for booster generation and card ratio calculation
|
||||||
|
*
|
||||||
|
* @param list all cards list
|
||||||
|
*/
|
||||||
|
private List<CardInfo> removeReprints(List<CardInfo> list) {
|
||||||
|
Map<String, CardInfo> usedNames = new HashMap<>();
|
||||||
|
List<CardInfo> filteredList = new ArrayList<>();
|
||||||
|
list.forEach(card -> {
|
||||||
|
CardInfo foundCard = usedNames.getOrDefault(card.getName(), null);
|
||||||
|
if (foundCard == null) {
|
||||||
|
usedNames.put(card.getName(), card);
|
||||||
|
filteredList.add(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filteredList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill booster with reprints, used for non collator generation
|
||||||
|
*
|
||||||
|
* @param booster booster's cards
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private List<Card> addReprints(List<Card> booster) {
|
||||||
|
if (booster.stream().noneMatch(Card::getUsesVariousArt)) {
|
||||||
|
return new ArrayList<>(booster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate possible reprints
|
||||||
|
if (this.savedReprints == null) {
|
||||||
|
this.savedReprints = new HashMap<>();
|
||||||
|
List<String> needSets = new ArrayList<>();
|
||||||
|
needSets.add(this.code);
|
||||||
|
if (this.parentSet != null) {
|
||||||
|
needSets.add(this.parentSet.code);
|
||||||
|
}
|
||||||
|
List<CardInfo> cardInfos = CardRepository.instance.findCards(new CardCriteria()
|
||||||
|
.setCodes(needSets)
|
||||||
|
.variousArt(true)
|
||||||
|
.maxCardNumber(maxCardNumberInBooster) // ignore bonus/extra reprints
|
||||||
|
);
|
||||||
|
cardInfos.forEach(card -> {
|
||||||
|
this.savedReprints.putIfAbsent(card.getName(), new ArrayList<>());
|
||||||
|
this.savedReprints.get(card.getName()).add(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace normal cards by random reprints
|
||||||
|
List<Card> finalBooster = new ArrayList<>();
|
||||||
|
booster.forEach(card -> {
|
||||||
|
List<CardInfo> reprints = this.savedReprints.getOrDefault(card.getName(), null);
|
||||||
|
if (reprints != null && reprints.size() > 1) {
|
||||||
|
Card newCard = reprints.get(RandomUtil.nextInt(reprints.size())).getCard();
|
||||||
|
if (newCard != null) {
|
||||||
|
finalBooster.add(newCard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalBooster.add(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
return finalBooster;
|
||||||
|
}
|
||||||
|
|
||||||
public final synchronized List<CardInfo> getCardsByRarity(Rarity rarity) {
|
public final synchronized List<CardInfo> getCardsByRarity(Rarity rarity) {
|
||||||
List<CardInfo> savedCardInfos = savedCards.get(rarity);
|
List<CardInfo> savedCardInfos = savedCards.get(rarity);
|
||||||
if (savedCardInfos == null) {
|
if (savedCardInfos == null) {
|
||||||
savedCardInfos = findCardsByRarity(rarity);
|
savedCardInfos = removeReprints(findCardsByRarity(rarity));
|
||||||
savedCards.put(rarity, savedCardInfos);
|
savedCards.put(rarity, savedCardInfos);
|
||||||
}
|
}
|
||||||
// Return a copy of the saved cards information, as not to let modify the original.
|
// Return a copy of the saved cards information, as not to let modify the original.
|
||||||
|
@ -504,7 +582,7 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
public final synchronized List<CardInfo> getSpecialCardsByRarity(Rarity rarity) {
|
public final synchronized List<CardInfo> getSpecialCardsByRarity(Rarity rarity) {
|
||||||
List<CardInfo> savedCardInfos = savedSpecialCards.get(rarity);
|
List<CardInfo> savedCardInfos = savedSpecialCards.get(rarity);
|
||||||
if (savedCardInfos == null) {
|
if (savedCardInfos == null) {
|
||||||
savedCardInfos = findSpecialCardsByRarity(rarity);
|
savedCardInfos = removeReprints(findSpecialCardsByRarity(rarity));
|
||||||
savedSpecialCards.put(rarity, savedCardInfos);
|
savedSpecialCards.put(rarity, savedCardInfos);
|
||||||
}
|
}
|
||||||
// Return a copy of the saved cards information, as not to let modify the original.
|
// Return a copy of the saved cards information, as not to let modify the original.
|
||||||
|
@ -540,11 +618,11 @@ public abstract class ExpansionSet implements Serializable {
|
||||||
* "Special cards" are cards that have common/uncommon/rare/mythic rarities
|
* "Special cards" are cards that have common/uncommon/rare/mythic rarities
|
||||||
* but can only appear in a specific slot in boosters. Examples are DFCs in
|
* but can only appear in a specific slot in boosters. Examples are DFCs in
|
||||||
* Innistrad sets and common nonbasic lands in many post-2018 sets.
|
* Innistrad sets and common nonbasic lands in many post-2018 sets.
|
||||||
*
|
* <p>
|
||||||
* Note that Rarity.SPECIAL and Rarity.BONUS cards are not normally treated
|
* Note that Rarity.SPECIAL and Rarity.BONUS cards are not normally treated
|
||||||
* as "special cards" because by default boosters don't even have slots for
|
* as "special cards" because by default boosters don't even have slots for
|
||||||
* those rarities.
|
* those rarities.
|
||||||
*
|
* <p>
|
||||||
* Also note that getCardsByRarity calls getSpecialCardsByRarity to exclude
|
* Also note that getCardsByRarity calls getSpecialCardsByRarity to exclude
|
||||||
* special cards from non-special booster slots, so sets that override this
|
* special cards from non-special booster slots, so sets that override this
|
||||||
* method must not call getCardsByRarity in it or infinite recursion will occur.
|
* method must not call getCardsByRarity in it or infinite recursion will occur.
|
||||||
|
|
|
@ -29,6 +29,7 @@ public class CardCriteria {
|
||||||
private final List<SuperType> notSupertypes;
|
private final List<SuperType> notSupertypes;
|
||||||
private final List<SubType> subtypes;
|
private final List<SubType> subtypes;
|
||||||
private final List<Rarity> rarities;
|
private final List<Rarity> rarities;
|
||||||
|
private Boolean variousArt;
|
||||||
private Boolean doubleFaced;
|
private Boolean doubleFaced;
|
||||||
private Boolean modalDoubleFaced;
|
private Boolean modalDoubleFaced;
|
||||||
private boolean nightCard;
|
private boolean nightCard;
|
||||||
|
@ -98,6 +99,11 @@ public class CardCriteria {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CardCriteria variousArt(boolean variousArt) {
|
||||||
|
this.variousArt = variousArt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CardCriteria doubleFaced(boolean doubleFaced) {
|
public CardCriteria doubleFaced(boolean doubleFaced) {
|
||||||
this.doubleFaced = doubleFaced;
|
this.doubleFaced = doubleFaced;
|
||||||
return this;
|
return this;
|
||||||
|
@ -144,7 +150,11 @@ public class CardCriteria {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardCriteria setCodes(String... setCodes) {
|
public CardCriteria setCodes(String... setCodes) {
|
||||||
this.setCodes.addAll(Arrays.asList(setCodes));
|
return setCodes(Arrays.asList(setCodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardCriteria setCodes(List<String> setCodes) {
|
||||||
|
this.setCodes.addAll(setCodes);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,6 +233,11 @@ public class CardCriteria {
|
||||||
clausesCount++;
|
clausesCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (variousArt != null) {
|
||||||
|
where.eq("variousArt", variousArt);
|
||||||
|
clausesCount++;
|
||||||
|
}
|
||||||
|
|
||||||
if (doubleFaced != null) {
|
if (doubleFaced != null) {
|
||||||
where.eq("doubleFaced", doubleFaced);
|
where.eq("doubleFaced", doubleFaced);
|
||||||
clausesCount++;
|
clausesCount++;
|
||||||
|
@ -426,6 +441,10 @@ public class CardCriteria {
|
||||||
return rarities;
|
return rarities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getVariousArt() {
|
||||||
|
return variousArt;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getDoubleFaced() {
|
public Boolean getDoubleFaced() {
|
||||||
return doubleFaced;
|
return doubleFaced;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue