New feature: "Chaos Remixed" booster draft (#10328)

* Fix error in draft pick logger that was failing on chaos drafts with fewer than 3 sets

* Implement Remixed Booster Draft

* Add debug test

* minor cleanup

* Cleanup unnecessary checks

* Fix elimination tournament type

* Add note for future improvement
This commit is contained in:
xenohedron 2023-05-12 10:12:23 -04:00 committed by GitHub
parent 6d4e353867
commit 4cc9329b15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 437 additions and 47 deletions

View file

@ -55,6 +55,7 @@ public class NewTournamentDialog extends MageDialog {
private static final int CONSTRUCTION_TIME_MAX = 30;
private boolean isRandom = false;
private boolean isRichMan = false;
private boolean isRemixed = false;
private String cubeFromDeckFilename = "";
private String jumpstartPacksFilename = "";
private boolean automaticChange = false;
@ -679,11 +680,11 @@ public class NewTournamentDialog extends MageDialog {
// CHECKS
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
if (tournamentType.isRandom() || tournamentType.isRichMan()) {
if (tOptions.getLimitedOptions().getSetCodes().size() < tournamentType.getNumBoosters()) {
if (tournamentType.isRandom() || tournamentType.isRichMan() || tournamentType.isRemixed()) {
if (tOptions.getLimitedOptions().getSetCodes().size() < 1) {
JOptionPane.showMessageDialog(
MageFrame.getDesktop(),
String.format("Warning, you must select %d packs for the pool", tournamentType.getNumBoosters()),
"Warning, you must select at least one set for the pool",
"Warning",
JOptionPane.WARNING_MESSAGE
);
@ -916,7 +917,8 @@ public class NewTournamentDialog extends MageDialog {
if (tournamentType.isLimited()) {
this.isRandom = tournamentType.isRandom();
this.isRichMan = tournamentType.isRichMan();
if (this.isRandom || this.isRichMan) {
this.isRemixed = tournamentType.isRemixed();
if (this.isRandom || this.isRichMan || this.isRemixed) {
createRandomPacks();
} else {
createPacks(tournamentType.getNumBoosters());
@ -960,7 +962,7 @@ public class NewTournamentDialog extends MageDialog {
this.lblPacks.setVisible(false);
this.pnlPacks.setVisible(false);
this.pnlRandomPacks.setVisible(false);
} else if (tournamentType.isRandom() || tournamentType.isRichMan()) {
} else if (tournamentType.isRandom() || tournamentType.isRichMan() || tournamentType.isRemixed()) {
this.lblDraftCube.setVisible(false);
this.cbDraftCube.setVisible(false);
this.lblPacks.setVisible(true);
@ -1031,7 +1033,7 @@ public class NewTournamentDialog extends MageDialog {
pnlRandomPacks.add(txtRandomPacks);
JButton btnSelectRandomPacks = new JButton();
btnSelectRandomPacks.setAlignmentX(Component.LEFT_ALIGNMENT);
btnSelectRandomPacks.setText("Select packs to be included in the pool");
btnSelectRandomPacks.setText("Select sets to be included in the pool");
btnSelectRandomPacks.setToolTipText(RandomPacksSelectorDialog.randomDraftDescription);
btnSelectRandomPacks.addActionListener(evt -> showRandomPackSelectorDialog());
pnlRandomPacks.add(btnSelectRandomPacks);
@ -1044,8 +1046,7 @@ public class NewTournamentDialog extends MageDialog {
}
private void showRandomPackSelectorDialog() {
TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem();
randomPackSelector.showDialog(isRandom, isRichMan, tournamentType.getNumBoosters());
randomPackSelector.showDialog(isRandom, isRichMan, isRemixed);
this.txtRandomPacks.setText(String.join(";", randomPackSelector.getSelectedPacks()));
this.pack();
this.revalidate();
@ -1248,6 +1249,7 @@ public class NewTournamentDialog extends MageDialog {
if (tournamentType.isLimited()) {
tOptions.getLimitedOptions().setConstructionTime((Integer) this.spnConstructTime.getValue() * 60);
tOptions.getLimitedOptions().setIsRandom(tournamentType.isRandom());
tOptions.getLimitedOptions().setIsRemixed(tournamentType.isRemixed());
tOptions.getLimitedOptions().setIsRichMan(tournamentType.isRichMan());
tOptions.getLimitedOptions().setIsJumpstart(tournamentType.isJumpstart());
@ -1283,6 +1285,7 @@ public class NewTournamentDialog extends MageDialog {
} else if (tournamentType.isRandom() || tournamentType.isRichMan()) {
this.isRandom = tournamentType.isRandom();
this.isRichMan = tournamentType.isRichMan();
this.isRemixed = tournamentType.isRemixed();
tOptions.getLimitedOptions().getSetCodes().clear();
java.util.List<String> selected = randomPackSelector.getSelectedPacks();
Collections.shuffle(selected);
@ -1299,6 +1302,12 @@ public class NewTournamentDialog extends MageDialog {
} else {
tOptions.getLimitedOptions().getSetCodes().addAll(selected);
}
} else if (tournamentType.isRemixed()) {
this.isRandom = tournamentType.isRandom();
this.isRichMan = tournamentType.isRichMan();
this.isRemixed = tournamentType.isRemixed();
tOptions.getLimitedOptions().getSetCodes().clear();
tOptions.getLimitedOptions().getSetCodes().addAll(randomPackSelector.getSelectedPacks());
} else {
for (JPanel panel : packPanels) {
JComboBox combo = findComboInComponent(panel);
@ -1383,7 +1392,7 @@ public class NewTournamentDialog extends MageDialog {
numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_DRAFT + versionStr, "4"));
prepareTourneyView(numPlayers);
if (tournamentType.isRandom() || tournamentType.isRichMan()) {
if (tournamentType.isRandom() || tournamentType.isRichMan() || tournamentType.isRemixed()) {
loadRandomPacks(version);
} else {
loadBoosterPacks(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_DRAFT + versionStr, ""));
@ -1444,7 +1453,7 @@ public class NewTournamentDialog extends MageDialog {
if (deckFile != null && !deckFile.isEmpty()) {
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE + versionStr, deckFile);
}
if (tOptions.getLimitedOptions().getIsRandom() || tOptions.getLimitedOptions().getIsRichMan()) {
if (tOptions.getLimitedOptions().getIsRandom() || tOptions.getLimitedOptions().getIsRichMan() || tOptions.getLimitedOptions().getIsRemixed()) {
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT + versionStr, String.join(";", this.randomPackSelector.getSelectedPacks()));
}
}

View file

@ -18,7 +18,6 @@ public class RandomPacksSelectorDialog extends javax.swing.JDialog {
* Creates new form RandomPacksSelectorDialog
*/
private boolean boxesCreated;
private int needSetsAmount;
public static final String randomDraftDescription = ("The selected packs will be randomly distributed to players. Each player may open different packs. Duplicates will be avoided.");
public RandomPacksSelectorDialog() {
@ -28,21 +27,22 @@ public class RandomPacksSelectorDialog extends javax.swing.JDialog {
boxesCreated = false;
}
private void setType(boolean isRandomDraft, boolean isRichManDraft, int needSetsAmount) {
this.needSetsAmount = needSetsAmount;
String title = "";
private void setType(boolean isRandomDraft, boolean isRichManDraft, boolean isRemixedDraft) {
String title;
if (isRandomDraft) {
title = "Random Booster Draft Packs Selector";
} else if (isRichManDraft) {
title = "Rich Man Booster Draft Packs Selector";
} else if (isRemixedDraft) {
title = "Chaos Remixed Draft Set Selector";
} else {
title = "Booster Draft Packs Selector";
}
setTitle(title);
}
public void showDialog(boolean isRandomDraft, boolean isRichManDraft, int needSetsAmount) {
setType(isRandomDraft, isRichManDraft, needSetsAmount);
public void showDialog(boolean isRandomDraft, boolean isRichManDraft, boolean isRemixedDraft) {
setType(isRandomDraft, isRichManDraft, isRemixedDraft);
createCheckboxes();
pnlPacks.setVisible(true);
pnlPacks.revalidate();
@ -204,8 +204,8 @@ public class RandomPacksSelectorDialog extends javax.swing.JDialog {
}//GEN-LAST:event_formWindowClosing
public void doApply() {
if (getSelectedPacks().size() < needSetsAmount) {
JOptionPane.showMessageDialog(this, String.format("At least %d sets must be selected", needSetsAmount), "Error", JOptionPane.ERROR_MESSAGE);
if (getSelectedPacks().size() < 1) {
JOptionPane.showMessageDialog(this, "At least one set must be selected", "Error", JOptionPane.ERROR_MESSAGE);
} else {
this.setVisible(false);
}

View file

@ -148,7 +148,7 @@
public void updateDraft(DraftView draftView) {
if (draftView.getSets().size() != 3) {
// Random draft
// Random draft - TODO: can we access the type of draft here?
this.txtPack1.setText("Random Boosters");
this.txtPack2.setText("Random Boosters");
this.txtPack3.setText("Random Boosters");
@ -171,6 +171,7 @@
int left = draftView.getPlayers().size() - right;
int height = left * 18;
lblTableImage.setSize(new Dimension(lblTableImage.getWidth(), height));
// TODO: Can we fix this for Rich Draft where there is no direction?
Image tableImage = ImageHelper.getImageFromResources(draftView.getBoosterNum() % 2 == 1 ? "/draft/table_left.png" : "/draft/table_right.png");
BufferedImage resizedTable = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(tableImage, BufferedImage.TYPE_INT_ARGB), lblTableImage.getWidth(), lblTableImage.getHeight());
lblTableImage.setIcon(new ImageIcon(resizedTable));
@ -431,10 +432,11 @@
}
private String getCurrentSetCode() {
if (!setCodes.isEmpty()) {
// TODO: Record set codes for random drafts correctly
if (setCodes.size() >= packNo) {
return setCodes.get(packNo - 1);
} else {
return "";
return " ";
}
}

View file

@ -21,6 +21,7 @@ public class TournamentTypeView implements Serializable {
private final boolean cubeBooster;
private final boolean elimination;
private final boolean random;
private final boolean remixed;
private final boolean richMan;
private final boolean jumpstart;
@ -34,6 +35,7 @@ public class TournamentTypeView implements Serializable {
this.cubeBooster = tournamentType.isCubeBooster();
this.elimination = tournamentType.isElimination();
this.random = tournamentType.isRandom();
this.remixed = tournamentType.isRemixed();
this.richMan = tournamentType.isRichMan();
this.jumpstart = tournamentType.isJumpstart();
}
@ -79,6 +81,10 @@ public class TournamentTypeView implements Serializable {
return random;
}
public boolean isRemixed() {
return remixed;
}
public boolean isRichMan() {
return richMan;
}

View file

@ -18,7 +18,7 @@ public class RandomBoosterDraftEliminationTournamentType extends TournamentType
this.draft = true;
this.limited = true;
this.cubeBooster = false;
this.elimination = false;
this.elimination = true;
this.isRandom = true;
}

View file

@ -0,0 +1,26 @@
package mage.tournament;
import mage.constants.TournamentPlayerState;
import mage.game.draft.DraftOptions;
import mage.game.draft.RemixedBoosterDraft;
import mage.game.events.TableEvent;
import mage.game.tournament.TournamentOptions;
import mage.game.tournament.TournamentPlayer;
public class RemixedBoosterDraftEliminationTournament extends BoosterDraftEliminationTournament {
public RemixedBoosterDraftEliminationTournament(TournamentOptions options) {
super(options);
currentStep = TournamentStep.START;
}
@Override
protected void draft() {
draft = new RemixedBoosterDraft((DraftOptions) options.getLimitedOptions(), getSets());
for (TournamentPlayer player: players.values()) {
draft.addPlayer(player.getPlayer());
player.setState(TournamentPlayerState.DRAFTING);
}
tableEventSource.fireTableEvent(TableEvent.EventType.START_DRAFT, null, draft);
}
}

View file

@ -0,0 +1,25 @@
package mage.tournament;
import mage.game.tournament.TournamentType;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class RemixedBoosterDraftEliminationTournamentType extends TournamentType {
public RemixedBoosterDraftEliminationTournamentType() {
this.name = "Booster Draft Elimination (Remixed)";
this.maxPlayers = 16;
this.minPlayers = 4;
this.numBoosters = 3;
this.draft = true;
this.limited = true;
this.cubeBooster = false;
this.elimination = true;
this.isRemixed = true;
}
}

View file

@ -0,0 +1,26 @@
package mage.tournament;
import mage.constants.TournamentPlayerState;
import mage.game.draft.DraftOptions;
import mage.game.draft.RemixedBoosterDraft;
import mage.game.events.TableEvent;
import mage.game.tournament.TournamentOptions;
import mage.game.tournament.TournamentPlayer;
public class RemixedBoosterDraftSwissTournament extends BoosterDraftSwissTournament {
public RemixedBoosterDraftSwissTournament(TournamentOptions options) {
super(options);
currentStep = BoosterDraftSwissTournament.TournamentStep.START;
}
@Override
protected void draft() {
draft = new RemixedBoosterDraft((DraftOptions) options.getLimitedOptions(), getSets());
for (TournamentPlayer player: players.values()) {
draft.addPlayer(player.getPlayer());
player.setState(TournamentPlayerState.DRAFTING);
}
tableEventSource.fireTableEvent(TableEvent.EventType.START_DRAFT, null, draft);
}
}

View file

@ -0,0 +1,25 @@
package mage.tournament;
import mage.game.tournament.TournamentType;
/**
*
* @author LevelX2
*/
public class RemixedBoosterDraftSwissTournamentType extends TournamentType {
public RemixedBoosterDraftSwissTournamentType() {
this.name = "Booster Draft Swiss (Remixed)";
this.maxPlayers = 16;
this.minPlayers = 4;
this.numBoosters = 3;
this.draft = true;
this.limited = true;
this.cubeBooster = false;
this.elimination = false;
this.isRemixed = true;
}
}

View file

@ -95,13 +95,13 @@
<tournamentType name="Booster Draft Elimination" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationCubeTournamentType"/>
<tournamentType name="Booster Draft Elimination (Random)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RandomBoosterDraftEliminationTournament" typeName="mage.tournament.RandomBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Remixed)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RemixedBoosterDraftEliminationTournament" typeName="mage.tournament.RemixedBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RichManDraftEliminationTournament" typeName="mage.tournament.RichManDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RichManCubeDraftEliminationTournament" typeName="mage.tournament.RichManCubeDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Swiss" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissCubeTournamentType"/>
<tournamentType name="Booster Draft Swiss (Random)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RandomBoosterDraftSwissTournament" typeName="mage.tournament.RandomBoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Remixed)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RemixedBoosterDraftSwissTournament" typeName="mage.tournament.RemixedBoosterDraftSwissTournamentType"/>
<tournamentType name="Sealed Elimination" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationTournamentType"/>
<tournamentType name="Sealed Elimination (Cube)" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationCubeTournamentType"/>
<tournamentType name="Sealed Swiss" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedSwissTournament" typeName="mage.tournament.SealedSwissTournamentType"/>

View file

@ -89,11 +89,13 @@
<tournamentType name="Booster Draft Elimination" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Cube)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationCubeTournamentType"/>
<tournamentType name="Booster Draft Elimination (Random)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RandomBoosterDraftEliminationTournament" typeName="mage.tournament.RandomBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Remixed)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RemixedBoosterDraftEliminationTournament" typeName="mage.tournament.RemixedBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RichManDraftEliminationTournament" typeName="mage.tournament.RichManDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man Cube)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RichManCubeDraftEliminationTournament" typeName="mage.tournament.RichManCubeDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Swiss" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Cube)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissCubeTournamentType"/>
<tournamentType name="Booster Draft Swiss (Random)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RandomBoosterDraftSwissTournament" typeName="mage.tournament.RandomBoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Remixed)" jar="mage-tournament-boosterdraft-${project.version}.jar" className="mage.tournament.RemixedBoosterDraftSwissTournament" typeName="mage.tournament.RemixedBoosterDraftSwissTournamentType"/>
<tournamentType name="Sealed Elimination" jar="mage-tournament-sealed-${project.version}.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationTournamentType"/>
<tournamentType name="Sealed Elimination (Cube)" jar="mage-tournament-sealed-${project.version}.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationCubeTournamentType"/>
<tournamentType name="Sealed Swiss" jar="mage-tournament-sealed-${project.version}.jar" className="mage.tournament.SealedSwissTournament" typeName="mage.tournament.SealedSwissTournamentType"/>

View file

@ -55,7 +55,14 @@ public enum TournamentFactory {
tournament.getOptions().getLimitedOptions().setDraftCube(draftCube);
tournament.setBoosterInfo(tournament.getOptions().getLimitedOptions().getDraftCubeName());
} else if (tournament.getTournamentType().isRandom()) {
StringBuilder rv = new StringBuilder( "Random Draft using sets: ");
StringBuilder rv = new StringBuilder( "Chaos Draft using sets: ");
for (Map.Entry<String, Integer> entry: setInfo.entrySet()){
rv.append(entry.getKey());
rv.append(';');
}
tournament.setBoosterInfo(rv.toString());
} else if (tournament.getTournamentType().isRemixed()) {
StringBuilder rv = new StringBuilder( "Chaos Remixed Draft using sets: ");
for (Map.Entry<String, Integer> entry: setInfo.entrySet()){
rv.append(entry.getKey());
rv.append(';');
@ -94,4 +101,4 @@ public enum TournamentFactory {
}
}
}
}

View file

@ -60,11 +60,13 @@
<tournamentType name="Booster Draft Elimination" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftEliminationTournament" typeName="mage.tournament.BoosterDraftEliminationCubeTournamentType"/>
<tournamentType name="Booster Draft Elimination (Random)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RandomBoosterDraftEliminationTournament" typeName="mage.tournament.RandomBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Remixed)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RemixedBoosterDraftEliminationTournament" typeName="mage.tournament.RemixedBoosterDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RichManDraftEliminationTournament" typeName="mage.tournament.RichManDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Elimination (Rich Man Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RichManCubeDraftEliminationTournament" typeName="mage.tournament.RichManCubeDraftEliminationTournamentType"/>
<tournamentType name="Booster Draft Swiss" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Cube)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.BoosterDraftSwissTournament" typeName="mage.tournament.BoosterDraftSwissCubeTournamentType"/>
<tournamentType name="Booster Draft Swiss (Random)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RandomBoosterDraftSwissTournament" typeName="mage.tournament.RandomBoosterDraftSwissTournamentType"/>
<tournamentType name="Booster Draft Swiss (Remixed)" jar="mage-tournament-booster-draft.jar" className="mage.tournament.RemixedBoosterDraftSwissTournament" typeName="mage.tournament.RemixedBoosterDraftSwissTournamentType"/>
<tournamentType name="Sealed Elimination" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationTournamentType"/>
<tournamentType name="Sealed Elimination (Cube)" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedEliminationTournament" typeName="mage.tournament.SealedEliminationCubeTournamentType"/>
<tournamentType name="Sealed Swiss" jar="mage-tournament-sealed.jar" className="mage.tournament.SealedSwissTournament" typeName="mage.tournament.SealedSwissTournamentType"/>

View file

@ -11,6 +11,7 @@ import mage.cards.repository.CardScanner;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.SubType;
import mage.game.draft.RemixedSet;
import mage.sets.*;
import mage.util.CardUtil;
import org.junit.Assert;
@ -552,7 +553,7 @@ public class BoosterGenerationTest extends MageTestBase {
@Test
public void test_CollectBoosterStats() {
ExpansionSet setToAnalyse = FallenEmpires.getInstance();
int openBoosters = 1000;
int openBoosters = 10000;
Map<String, Integer> resRatio = new HashMap<>();
int totalCards = 0;
@ -565,21 +566,47 @@ public class BoosterGenerationTest extends MageTestBase {
resRatio.computeIfPresent(code, (u, count) -> count + 1);
});
}
final Integer totalCardsFinal = totalCards;
List<String> info = resRatio.entrySet().stream()
.sorted(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return Integer.compare(o2.getValue(), o1.getValue());
}
})
.sorted((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue()))
.map(e -> String.format("%s: %d",
e.getKey(),
e.getValue()
//(double) e.getValue() / totalCardsFinal * 100.0
))
.collect(Collectors.toList());
System.out.println(setToAnalyse.getName() + " - boosters opened: " + openBoosters + ". Found cards: " + totalCardsFinal + "\n"
+ info.stream().collect(Collectors.joining("\n")));
System.out.println(setToAnalyse.getName() + " - boosters opened: " + openBoosters + ". Found cards: " + totalCards + "\n"
+ String.join("\n", info));
}
@Ignore // debug only
@Test
public void test_RemixedBoosterStats() {
List<ExpansionSet> sets = new ArrayList<>();
sets.add(ScarsOfMirrodin.getInstance());
sets.add(MirrodinBesieged.getInstance());
sets.add(NewPhyrexia.getInstance());
RemixedSet setToAnalyse = new RemixedSet(sets, 10, 3, 1);
int openBoosters = 10000;
Map<String, Integer> resRatio = new HashMap<>();
int totalCards = 0;
for (int i = 1; i <= openBoosters; i++) {
List<Card> booster = setToAnalyse.createBooster();
totalCards += booster.size();
booster.forEach(card -> {
String code = String.format("%s %s", card.getRarity().getCode(), card.getName());
resRatio.putIfAbsent(code, 0);
resRatio.computeIfPresent(code, (u, count) -> count + 1);
});
}
List<String> info = resRatio.entrySet().stream()
.sorted((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue()))
.map(e -> String.format("%s: %d",
e.getKey(),
e.getValue()
))
.collect(Collectors.toList());
System.out.println("Boosters opened: " + openBoosters + ". Found cards: " + totalCards + "\n"
+ String.join("\n", info));
}
}

View file

@ -18,7 +18,9 @@ public class ObjectColor implements Serializable, Copyable<ObjectColor>, Compara
public static final ObjectColor RED = new ObjectColor("R");
public static final ObjectColor GREEN = new ObjectColor("G");
public static final ObjectColor GOLD = new ObjectColor("O");
public static final ObjectColor COLORLESS = new ObjectColor();
public static final ObjectColor GOLD = new ObjectColor("O"); // Not multicolored - Sword of Dungeons & Dragons
private boolean white;
private boolean blue;

View file

@ -305,7 +305,7 @@ public abstract class ExpansionSet implements Serializable {
return true;
}
private static ObjectColor getColorForValidate(Card card) {
public static ObjectColor getColorForValidate(Card card) {
ObjectColor color = card.getColor();
// treat colorless nonland cards with exactly one ID color as cards of that color
// (e.g. devoid, emerge, spellbombs... but not mana fixing artifacts)
@ -364,8 +364,6 @@ public abstract class ExpansionSet implements Serializable {
return (RandomUtil.nextDouble() > Math.pow(0.8, colorlessCountPlusOne));
}
private static final ObjectColor COLORLESS = new ObjectColor();
protected boolean validateUncommonColors(List<Card> booster) {
List<ObjectColor> uncommonColors = booster.stream()
.filter(card -> card.getRarity() == Rarity.UNCOMMON)
@ -375,7 +373,7 @@ public abstract class ExpansionSet implements Serializable {
// if there are only two uncommons, they can be the same color
if (uncommonColors.size() < 3) return true;
// boosters of artifact sets can have all colorless uncommons
if (uncommonColors.contains(COLORLESS)) return true;
if (uncommonColors.contains(ObjectColor.COLORLESS)) return true;
// otherwise, reject if all uncommons are the same color combination
return (new HashSet<>(uncommonColors).size() > 1);
}

View file

@ -0,0 +1,28 @@
package mage.game.draft;
import mage.cards.ExpansionSet;
import java.util.List;
public class RemixedBoosterDraft extends BoosterDraft {
final RemixedSet remixedSet;
public RemixedBoosterDraft(DraftOptions options, List<ExpansionSet> sets) {
super(options, sets);
if (sets.isEmpty()){
throw new RuntimeException("At least one set must be selected for remixed booster draft");
}
remixedSet = new RemixedSet(sets, 10, 3, 1);
}
@Override
protected void openBooster() {
if (boosterNum <= numberBoosters) {
for (DraftPlayer player: players.values()) {
player.setBooster(remixedSet.createBooster());
}
}
}
}

View file

@ -0,0 +1,191 @@
package mage.game.draft;
import mage.ObjectColor;
import mage.cards.Card;
import mage.cards.ExpansionSet;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.constants.Rarity;
import mage.util.RandomUtil;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RemixedSet implements Serializable {
protected final int numBoosterCommons;
protected final int numBoosterUncommons;
protected final int numBoosterRares;
protected final int numBoosterSpecials;
protected final List<CardInfo> commons;
protected final List<CardInfo> uncommons;
protected final List<CardInfo> rares;
protected final List<CardInfo> mythics;
protected final List<CardInfo> specials;
protected final double chanceMythic;
public RemixedSet (List<ExpansionSet> sets, int c, int u, int r) {
this(sets, c, u, r, 0);
}
public RemixedSet(List<ExpansionSet> sets, int c, int u, int r, int special) {
this.numBoosterCommons = c;
this.numBoosterUncommons = u;
this.numBoosterRares = r;
this.numBoosterSpecials = special; // TODO: add support for uploading a custom list of special cards
this.commons = new ArrayList<>();
this.uncommons = new ArrayList<>();
this.rares = new ArrayList<>();
this.mythics = new ArrayList<>();
this.specials = new ArrayList<>();
for (ExpansionSet set : sets) {
commons.addAll(findCardsBySetAndRarity(set, Rarity.COMMON));
uncommons.addAll(findCardsBySetAndRarity(set, Rarity.UNCOMMON));
rares.addAll(findCardsBySetAndRarity(set, Rarity.RARE));
mythics.addAll(findCardsBySetAndRarity(set, Rarity.MYTHIC));
}
float nMythics = mythics.size();
float nRares = rares.size();
this.chanceMythic = (nMythics / (nMythics + nRares + nRares));
}
protected List<CardInfo> findCardsBySetAndRarity(ExpansionSet set, Rarity rarity) {
List<CardInfo> cardInfos = CardRepository.instance.findCards(new CardCriteria()
.setCodes(set.getCode())
.rarities(rarity)
.maxCardNumber(set.getMaxCardNumberInBooster())); // TODO: Make sure this parameter is set appropriately where needed
cardInfos.removeIf(next -> (
next.getCardNumber().contains("*")
|| next.getCardNumber().contains("+")));
return cardInfos;
}
public List<Card> createBooster() {
List<Card> booster = new ArrayList<>();
booster.addAll(generateCommons());
booster.addAll(generateUncommons());
booster.addAll(generateRares());
// TODO: Generate special cards
return booster;
}
protected void addToBooster(List<Card> booster, List<CardInfo> cards) {
if (cards.isEmpty()) {
return;
}
CardInfo cardInfo = cards.remove(RandomUtil.nextInt(cards.size())); // so no duplicates in a booster
Card card = cardInfo.getCard();
if (card == null) {
return;
}
booster.add(card);
}
protected List<Card> generateCommons() {
List<Card> boosterCommons = new ArrayList<>(); // will be returned once valid or max attempts reached
for (int i = 0; i < 100; i++) { // don't want to somehow loop forever
boosterCommons.clear();
List<CardInfo> commonsForGenerate = new ArrayList<>(commons); // to not modify base list
for (int j = 0; j < numBoosterCommons; j++) {
addToBooster(boosterCommons, commonsForGenerate);
}
if (validateCommonColors(boosterCommons)) {
return boosterCommons;
}
}
return boosterCommons;
}
protected List<Card> generateUncommons() {
List<Card> boosterUncommons = new ArrayList<>(); // will be returned once valid or max attempts reached
for (int i = 0; i < 100; i++) { // don't want to somehow loop forever
boosterUncommons.clear();
List<CardInfo> uncommonsForGenerate = new ArrayList<>(uncommons); // to not modify base list
for (int j = 0; j < numBoosterUncommons; j++) {
addToBooster(boosterUncommons, uncommonsForGenerate);
}
if (validateUncommonColors(boosterUncommons)) {
return boosterUncommons;
}
}
return boosterUncommons;
}
protected List<Card> generateRares() {
List<Card> boosterRares = new ArrayList<>();
List<CardInfo> raresForGenerate = new ArrayList<>(rares);
List<CardInfo> mythicsForGenerate = new ArrayList<>(mythics);
for (int j = 0; j < numBoosterRares; j++) {
if (RandomUtil.nextDouble() < chanceMythic) {
addToBooster(boosterRares, mythicsForGenerate);
} else {
addToBooster(boosterRares, raresForGenerate);
}
}
return boosterRares;
}
// See ExpansionSet for original validation logic by awjackson
protected boolean validateCommonColors(List<Card> booster) {
List<ObjectColor> commonColors = booster.stream()
.filter(card -> card.getRarity() == Rarity.COMMON)
.map(ExpansionSet::getColorForValidate)
.collect(Collectors.toList());
// for multicolor sets, count not just the colors present at common,
// but also the number of color combinations (guilds/shards/wedges)
// e.g. a booster with three UB commons, three RW commons and four G commons
// has all five colors but isn't "balanced"
ObjectColor colorsRepresented = new ObjectColor();
Set<ObjectColor> colorCombinations = new HashSet<>();
int colorlessCountPlusOne = 1;
for (ObjectColor color : commonColors) {
colorCombinations.add(color);
int colorCount = color.getColorCount();
if (colorCount == 0) {
++colorlessCountPlusOne;
} else if (colorCount > 1) {
// to prevent biasing toward multicolor over monocolor cards,
// count them as one of their colors chosen at random
List<ObjectColor> multiColor = color.getColors();
colorsRepresented.addColor(multiColor.get(RandomUtil.nextInt(multiColor.size())));
} else {
colorsRepresented.addColor(color);
}
}
int colors = Math.min(colorsRepresented.getColorCount(), colorCombinations.size());
// if booster has all five colors in five unique combinations, or if it has
// one card per color and all but one of the rest are colorless, accept it
// ("all but one" adds some leeway for sets with small boosters)
if (colors >= Math.min(5, commonColors.size() - colorlessCountPlusOne)) return true;
// otherwise, if booster is missing more than one color, reject it
if (colors < 4) return false;
// otherwise, stochastically treat each colorless card as 1/5 of a card of the missing color
return (RandomUtil.nextDouble() > Math.pow(0.8, colorlessCountPlusOne));
}
protected boolean validateUncommonColors(List<Card> booster) {
List<ObjectColor> uncommonColors = booster.stream()
.filter(card -> card.getRarity() == Rarity.UNCOMMON)
.map(ExpansionSet::getColorForValidate)
.collect(Collectors.toList());
// if there are only two uncommons, they can be the same color
if (uncommonColors.size() < 3) return true;
// boosters of artifact sets can have all colorless uncommons
if (uncommonColors.contains(ObjectColor.COLORLESS)) return true;
// otherwise, reject if all uncommons are the same color combination
return (new HashSet<>(uncommonColors).size() > 1);
}
}

View file

@ -17,6 +17,7 @@ public class LimitedOptions implements Serializable {
protected DraftCube draftCube;
protected int numberBoosters;
protected boolean isRandom;
protected boolean isRemixed;
protected boolean isRichMan;
protected Deck cubeFromDeck;
@ -95,6 +96,14 @@ public class LimitedOptions implements Serializable {
this.isRandom = isRandom;
}
public boolean getIsRemixed() {
return isRemixed;
}
public void setIsRemixed(boolean isRemixed) {
this.isRemixed = isRemixed;
}
public boolean getIsRichMan() {
return isRichMan;
}

View file

@ -14,11 +14,12 @@ public class TournamentType implements Serializable {
protected int maxPlayers;
protected int numBoosters;
protected boolean cubeBooster; // boosters are generated from a defined cube
protected boolean draft; // or sealed
protected boolean limited; // or construced
protected boolean elimination; // or Swiss
protected boolean isRandom;
protected boolean isRichMan; // or Rich Man Draft
protected boolean draft; // else Sealed
protected boolean limited; // else Constructed
protected boolean elimination; // else Swiss
protected boolean isRandom; // chaos draft
protected boolean isRemixed; // boosters generated containing cards from multiple sets
protected boolean isRichMan; // new boosters generated for each pick
protected boolean isJumpstart;
protected TournamentType() {
@ -65,6 +66,10 @@ public class TournamentType implements Serializable {
return this.isRandom;
}
public boolean isRemixed() {
return this.isRemixed;
}
public boolean isRichMan() {
return this.isRichMan;
}