diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index 065723c0f5f..e5418cbfead 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -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 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())); } } diff --git a/Mage.Client/src/main/java/mage/client/dialog/RandomPacksSelectorDialog.java b/Mage.Client/src/main/java/mage/client/dialog/RandomPacksSelectorDialog.java index be57cebc553..03c71a4bb9e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/RandomPacksSelectorDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/RandomPacksSelectorDialog.java @@ -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); } diff --git a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java index 71d7aba6bc0..33a5b979617 100644 --- a/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java +++ b/Mage.Client/src/main/java/mage/client/draft/DraftPanel.java @@ -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 " "; } } diff --git a/Mage.Common/src/main/java/mage/view/TournamentTypeView.java b/Mage.Common/src/main/java/mage/view/TournamentTypeView.java index 22d7ecf9bca..8430259db2d 100644 --- a/Mage.Common/src/main/java/mage/view/TournamentTypeView.java +++ b/Mage.Common/src/main/java/mage/view/TournamentTypeView.java @@ -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; } diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RandomBoosterDraftEliminationTournamentType.java b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RandomBoosterDraftEliminationTournamentType.java index e6c3c68c957..aaaa8db01f7 100644 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RandomBoosterDraftEliminationTournamentType.java +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RandomBoosterDraftEliminationTournamentType.java @@ -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; } diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournament.java b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournament.java new file mode 100644 index 00000000000..21f7351e940 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournament.java @@ -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); + } +} diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournamentType.java b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournamentType.java new file mode 100644 index 00000000000..4c285875818 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftEliminationTournamentType.java @@ -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; + } + +} diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournament.java b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournament.java new file mode 100644 index 00000000000..9249ed2775a --- /dev/null +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournament.java @@ -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); + } +} diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournamentType.java b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournamentType.java new file mode 100644 index 00000000000..eaf4aa936da --- /dev/null +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/src/mage/tournament/RemixedBoosterDraftSwissTournamentType.java @@ -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; + } + +} diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index 13840ba7b4f..df767ece5ac 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -95,13 +95,13 @@ - + - + diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index 8b7709a0c7f..4915604a767 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -89,11 +89,13 @@ + + diff --git a/Mage.Server/src/main/java/mage/server/tournament/TournamentFactory.java b/Mage.Server/src/main/java/mage/server/tournament/TournamentFactory.java index 12bf551a7b2..1e0d67ecd83 100644 --- a/Mage.Server/src/main/java/mage/server/tournament/TournamentFactory.java +++ b/Mage.Server/src/main/java/mage/server/tournament/TournamentFactory.java @@ -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 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 entry: setInfo.entrySet()){ rv.append(entry.getKey()); rv.append(';'); @@ -94,4 +101,4 @@ public enum TournamentFactory { } } -} \ No newline at end of file +} diff --git a/Mage.Server/src/test/data/config_error.xml b/Mage.Server/src/test/data/config_error.xml index c92fb13314d..134de3a1fec 100644 --- a/Mage.Server/src/test/data/config_error.xml +++ b/Mage.Server/src/test/data/config_error.xml @@ -60,11 +60,13 @@ + + 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 db71ade65df..2a67eca9e8b 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 @@ -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 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 info = resRatio.entrySet().stream() - .sorted(new Comparator>() { - @Override - public int compare(Map.Entry o1, Map.Entry 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 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 resRatio = new HashMap<>(); + int totalCards = 0; + for (int i = 1; i <= openBoosters; i++) { + List 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 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)); + } + } diff --git a/Mage/src/main/java/mage/ObjectColor.java b/Mage/src/main/java/mage/ObjectColor.java index 4d9292feb0d..4bc3f7b587f 100644 --- a/Mage/src/main/java/mage/ObjectColor.java +++ b/Mage/src/main/java/mage/ObjectColor.java @@ -18,7 +18,9 @@ public class ObjectColor implements Serializable, Copyable, 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; diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java index 58091373de1..e3d4d538641 100644 --- a/Mage/src/main/java/mage/cards/ExpansionSet.java +++ b/Mage/src/main/java/mage/cards/ExpansionSet.java @@ -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 booster) { List 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); } diff --git a/Mage/src/main/java/mage/game/draft/RemixedBoosterDraft.java b/Mage/src/main/java/mage/game/draft/RemixedBoosterDraft.java new file mode 100644 index 00000000000..b7b2250eada --- /dev/null +++ b/Mage/src/main/java/mage/game/draft/RemixedBoosterDraft.java @@ -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 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()); + } + } + } + +} diff --git a/Mage/src/main/java/mage/game/draft/RemixedSet.java b/Mage/src/main/java/mage/game/draft/RemixedSet.java new file mode 100644 index 00000000000..1d00693264c --- /dev/null +++ b/Mage/src/main/java/mage/game/draft/RemixedSet.java @@ -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 commons; + protected final List uncommons; + protected final List rares; + protected final List mythics; + protected final List specials; + protected final double chanceMythic; + + public RemixedSet (List sets, int c, int u, int r) { + this(sets, c, u, r, 0); + } + + public RemixedSet(List 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 findCardsBySetAndRarity(ExpansionSet set, Rarity rarity) { + List 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 createBooster() { + List booster = new ArrayList<>(); + booster.addAll(generateCommons()); + booster.addAll(generateUncommons()); + booster.addAll(generateRares()); + // TODO: Generate special cards + return booster; + } + + protected void addToBooster(List booster, List 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 generateCommons() { + List 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 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 generateUncommons() { + List 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 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 generateRares() { + List boosterRares = new ArrayList<>(); + List raresForGenerate = new ArrayList<>(rares); + List 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 booster) { + List 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 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 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 booster) { + List 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); + } + +} diff --git a/Mage/src/main/java/mage/game/tournament/LimitedOptions.java b/Mage/src/main/java/mage/game/tournament/LimitedOptions.java index 473dce849ef..476eacb0ae1 100644 --- a/Mage/src/main/java/mage/game/tournament/LimitedOptions.java +++ b/Mage/src/main/java/mage/game/tournament/LimitedOptions.java @@ -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; } diff --git a/Mage/src/main/java/mage/game/tournament/TournamentType.java b/Mage/src/main/java/mage/game/tournament/TournamentType.java index f11bd7e38ec..4a61e9b5173 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentType.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentType.java @@ -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; }