From d5dc85a54ca9e9f19d127ca6775a95226f0c1a6e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 6 Jun 2025 23:06:49 +0400 Subject: [PATCH] GUI, deck: improved bracket level calculations (added all levels to validator panel, improved stats and visual, part of #13341) --- .../components/BracketLegalityLabel.java | 198 +++++++++++------- .../EdhPowerLevelLegalityLabel.java | 3 +- .../mage/client/components/LegalityLabel.java | 13 +- .../client/deckeditor/DeckLegalityPanel.java | 8 +- 4 files changed, 134 insertions(+), 88 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java index b3bef68f60e..c45fb575270 100644 --- a/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java +++ b/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java @@ -30,7 +30,6 @@ import java.util.stream.Stream; * - [x] can auto-generate infinite combos list, see verify test downloadAndPrepareCommanderBracketsData * - [ ] TODO: tests * - [ ] TODO: table - players brackets level disclose settings - * - [ ] TODO: deck - improve gui to show more levels * - [ ] TODO: generate - convert card name to xmage format and assert on bad names (ascii only) * * @author JayDi85 @@ -45,9 +44,49 @@ public class BracketLegalityLabel extends LegalityLabel { private static final String GROUP_EXTRA_TURN = "Extra Turns"; private static final String GROUP_TUTORS = "Tutors"; + private static final Map> MAX_GROUP_LIMITS = new LinkedHashMap<>(); + + static { + // 1 + // No cards from the Game Changer list. + // No intentional two-card infinite combos. + // No mass land destruction. + // No extra turn cards. + // Tutors should be sparse. + // 2 + // No cards from the Game Changer list. + // No intentional two-card infinite combos. + // No mass land destruction. + // Extra turn cards should only appear in low quantities and should not be chained in succession or looped. + // Tutors should be sparse. + // 3 + // Up to three (3) cards from the Game Changer list. + // No intentional early game two-card infinite combos. + // No mass land destruction. + // Extra turn cards should only appear in low quantities and should not be chained in succession or looped. + // 4 + // 5 + // allow any cards + + // cards limits per brackets level, it's ok to use 99 as max + // group - levels 0, 1, 2, 3, 4, 5 + MAX_GROUP_LIMITS.put(GROUP_GAME_CHANGES, + Arrays.asList(0, 0, 0, 3, 99, 99)); + MAX_GROUP_LIMITS.put(GROUP_INFINITE_COMBOS, + Arrays.asList(0, 0, 0, 0, 99, 99)); + MAX_GROUP_LIMITS.put(GROUP_MASS_LAND_DESTRUCTION, + Arrays.asList(0, 0, 0, 0, 99, 99)); + MAX_GROUP_LIMITS.put(GROUP_EXTRA_TURN, + Arrays.asList(0, 0, 0, 3, 99, 99)); + MAX_GROUP_LIMITS.put(GROUP_TUTORS, + Arrays.asList(0, 3, 3, 99, 99, 99)); + } + private static final String RESOURCE_INFINITE_COMBOS = "brackets/infinite-combos.txt"; - private final BracketLevel level; + private final String fullName; + private final String shortName; + private final int maxLevel; private final List foundGameChangers = new ArrayList<>(); private final List foundInfiniteCombos = new ArrayList<>(); @@ -59,27 +98,12 @@ public class BracketLegalityLabel extends LegalityLabel { private final List fullGameChanges = new ArrayList<>(); private final Set fullInfiniteCombos = new HashSet<>(); // card1@card2, sorted by names, name must be xmage compatible - public enum BracketLevel { - BRACKET_1("Bracket 1"), - BRACKET_2_3("Bracket 2-3"), - BRACKET_4_5("Bracket 4-5"); - - private final String name; - - BracketLevel(String name) { - this.name = name; - } - - @Override - public String toString() { - return this.name; - } - } - - public BracketLegalityLabel(BracketLevel level) { - super(level.toString(), null); - this.level = level; - setPreferredSize(DIM_PREFERRED); + public BracketLegalityLabel(String fullName, String shortName, int maxLevel) { + super(shortName, null); + this.fullName = fullName; + this.shortName = shortName; + this.maxLevel = maxLevel; + setPreferredSize(DIM_PREFERRED_1_OF_5); } @Override @@ -89,49 +113,26 @@ public class BracketLegalityLabel extends LegalityLabel { private void validateBracketLevel() { this.badCards.clear(); - switch (this.level) { - case BRACKET_1: - // No cards from the Game Changer list. - // No intentional two-card infinite combos. - // No mass land destruction. - // No extra turn cards. - // Tutors should be sparse. - this.badCards.addAll(this.foundGameChangers); - this.badCards.addAll(this.foundInfiniteCombos); - this.badCards.addAll(this.foundMassLandDestruction); - this.badCards.addAll(this.foundExtraTurn); - if (this.foundTutors.size() > 3) { - this.badCards.addAll(this.foundTutors); - } - break; - case BRACKET_2_3: - // 2 - // No cards from the Game Changer list. - // No intentional two-card infinite combos. - // No mass land destruction. - // Extra turn cards should only appear in low quantities and should not be chained in succession or looped. - // Tutors should be sparse. - // 3 - // Up to three (3) cards from the Game Changer list. - // No intentional early game two-card infinite combos. - // No mass land destruction. - // Extra turn cards should only appear in low quantities and should not be chained in succession or looped. - if (this.foundGameChangers.size() > 3) { - this.badCards.addAll(this.foundGameChangers); - } - this.badCards.addAll(this.foundInfiniteCombos); - this.badCards.addAll(this.foundMassLandDestruction); - if (this.foundExtraTurn.size() > 3) { - this.badCards.addAll(this.foundExtraTurn); - } - // this.badCards.addAll(this.foundTutors); // allow any amount - break; - case BRACKET_4_5: - // allow any cards - break; - default: - throw new IllegalArgumentException("Unsupported level: " + this.level); + + if (this.foundGameChangers.size() > getMaxCardsLimit(GROUP_GAME_CHANGES)) { + this.badCards.addAll(this.foundGameChangers); } + if (this.foundInfiniteCombos.size() > getMaxCardsLimit(GROUP_INFINITE_COMBOS)) { + this.badCards.addAll(this.foundInfiniteCombos); + } + if (this.foundMassLandDestruction.size() > getMaxCardsLimit(GROUP_MASS_LAND_DESTRUCTION)) { + this.badCards.addAll(this.foundMassLandDestruction); + } + if (this.foundExtraTurn.size() > getMaxCardsLimit(GROUP_EXTRA_TURN)) { + this.badCards.addAll(this.foundExtraTurn); + } + if (this.foundTutors.size() > getMaxCardsLimit(GROUP_TUTORS)) { + this.badCards.addAll(this.foundTutors); + } + } + + private Integer getMaxCardsLimit(String groupName) { + return MAX_GROUP_LIMITS.get(groupName).get(this.maxLevel); } @Override @@ -139,31 +140,41 @@ public class BracketLegalityLabel extends LegalityLabel { collectAll(deck); validateBracketLevel(); - int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f); + int infoFontHeaderSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 1.0f); + int infoFontTextSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f); // show all found cards in any use cases Color showColor = this.badCards.isEmpty() ? COLOR_LEGAL : COLOR_NOT_LEGAL; List showInfo = new ArrayList<>(); if (this.badCards.isEmpty()) { - showInfo.add("

Deck is GOOD for " + this.level + "

"); + showInfo.add(String.format("

Deck is GOOD for %s

", + infoFontHeaderSize, + this.fullName + )); } else { - showInfo.add("

Deck is BAD for " + this.level + "

"); + showInfo.add(String.format("

Deck is BAD for %s

", + infoFontHeaderSize, + this.fullName + )); showInfo.add("

(click here to select all bad cards)

"); } Map> groups = new LinkedHashMap<>(); - groups.put(GROUP_GAME_CHANGES, this.foundGameChangers); - groups.put(GROUP_INFINITE_COMBOS, this.foundInfiniteCombos); - groups.put(GROUP_MASS_LAND_DESTRUCTION, this.foundMassLandDestruction); - groups.put(GROUP_EXTRA_TURN, this.foundExtraTurn); - groups.put(GROUP_TUTORS, this.foundTutors); + groups.put(GROUP_GAME_CHANGES + getStats(GROUP_GAME_CHANGES), this.foundGameChangers); + groups.put(GROUP_INFINITE_COMBOS + getStats(GROUP_INFINITE_COMBOS), this.foundInfiniteCombos); + groups.put(GROUP_MASS_LAND_DESTRUCTION + getStats(GROUP_MASS_LAND_DESTRUCTION), this.foundMassLandDestruction); + groups.put(GROUP_EXTRA_TURN + getStats(GROUP_EXTRA_TURN), this.foundExtraTurn); + groups.put(GROUP_TUTORS + getStats(GROUP_TUTORS), this.foundTutors); groups.forEach((group, cards) -> { showInfo.add("
"); - showInfo.add("
"); - showInfo.add("" + group + ": " + cards.size() + ""); - if (!cards.isEmpty()) { - showInfo.add("
    "); + showInfo.add("" + group + ""); + if (cards.isEmpty()) { + showInfo.add("
      "); + showInfo.add("
    • no cards
    • "); + showInfo.add("
    "); + } else { + showInfo.add("
      "); cards.forEach(s -> showInfo.add(String.format("
    • %s
    • ", s))); showInfo.add("
    "); } @@ -173,6 +184,39 @@ public class BracketLegalityLabel extends LegalityLabel { showState(showColor, showText, false); } + private String getStats(String groupName) { + int currentAmount = 0; + switch (groupName) { + case GROUP_GAME_CHANGES: + currentAmount = this.foundGameChangers.size(); + break; + case GROUP_INFINITE_COMBOS: + currentAmount = this.foundInfiniteCombos.size(); + break; + case GROUP_MASS_LAND_DESTRUCTION: + currentAmount = this.foundMassLandDestruction.size(); + break; + case GROUP_EXTRA_TURN: + currentAmount = this.foundExtraTurn.size(); + break; + case GROUP_TUTORS: + currentAmount = this.foundTutors.size(); + break; + default: + throw new IllegalArgumentException("Unknown group " + groupName); + } + int maxAmount = MAX_GROUP_LIMITS.get(groupName).get(this.maxLevel); + + String info; + if (currentAmount > maxAmount) { + info = " (%s of %s)"; + } else { + info = " (%s of %s)"; + } + + return String.format(info, currentAmount, maxAmount == 99 ? "any" : maxAmount); + } + private void collectAll(Deck deck) { collectGameChangers(deck); collectInfiniteCombos(deck); diff --git a/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java index fd96815df30..0dac9d88697 100644 --- a/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java +++ b/Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java @@ -2,7 +2,6 @@ package mage.client.components; import mage.cards.decks.Deck; import mage.client.util.GUISizeHelper; -import mage.client.util.gui.GuiDisplayUtil; import mage.deck.Commander; import java.util.ArrayList; @@ -22,7 +21,7 @@ public class EdhPowerLevelLegalityLabel extends LegalityLabel { public EdhPowerLevelLegalityLabel() { super("EDH Power Level: ?", null); - setPreferredSize(DIM_PREFERRED_X3); + setPreferredSize(DIM_PREFERRED_3_OF_3); } @Override diff --git a/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java index 95d2a13c7bf..6f02be7bebe 100644 --- a/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java +++ b/Mage.Client/src/main/java/mage/client/components/LegalityLabel.java @@ -25,9 +25,10 @@ public class LegalityLabel extends JLabel { protected static final Color COLOR_TEXT = new Color(255, 255, 255); protected static final Dimension DIM_MINIMUM = new Dimension(75, 25); protected static final Dimension DIM_MAXIMUM = new Dimension(150, 75); - protected static final Dimension DIM_PREFERRED = new Dimension(75, 25); - protected static final Dimension DIM_PREFERRED_X2 = new Dimension(DIM_PREFERRED.width * 2 + 5, 25); - protected static final Dimension DIM_PREFERRED_X3 = new Dimension(DIM_PREFERRED.width * 3 + 5 + 5, 25); + protected static final Dimension DIM_PREFERRED_1_OF_3 = new Dimension(75, 25); + protected static final Dimension DIM_PREFERRED_2_OF_3 = new Dimension(DIM_PREFERRED_1_OF_3.width * 2 + 5, 25); + protected static final Dimension DIM_PREFERRED_3_OF_3 = new Dimension(DIM_PREFERRED_1_OF_3.width * 3 + 5 * 2, 25); + protected static final Dimension DIM_PREFERRED_1_OF_5 = new Dimension((DIM_PREFERRED_3_OF_3.width - 5 * 4) / 5, 25); protected static final int TOOLTIP_TABLE_WIDTH = 400; // size of the label's tooltip protected static final int TOOLTIP_MAX_ERRORS = 20; // max errors to show in tooltip @@ -54,7 +55,7 @@ public class LegalityLabel extends JLabel { setMaximumSize(DIM_MAXIMUM); setName(text); // NOI18N setOpaque(true); - setPreferredSize(DIM_PREFERRED); + setPreferredSize(DIM_PREFERRED_1_OF_3); } /** @@ -80,7 +81,7 @@ public class LegalityLabel extends JLabel { setMinimumSize(DIM_MINIMUM); setMaximumSize(DIM_MAXIMUM); setOpaque(true); - setPreferredSize(DIM_PREFERRED); + setPreferredSize(DIM_PREFERRED_1_OF_3); } /** @@ -91,7 +92,7 @@ public class LegalityLabel extends JLabel { button.setHorizontalAlignment(SwingConstants.CENTER); button.setMinimumSize(DIM_MINIMUM); button.setMaximumSize(DIM_MAXIMUM); - button.setPreferredSize(DIM_PREFERRED); + button.setPreferredSize(DIM_PREFERRED_1_OF_3); return button; } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java index 29f7fce598f..f9ecdb01acd 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java @@ -107,9 +107,11 @@ public class DeckLegalityPanel extends javax.swing.JPanel { // extra buttons like score this.add(new EdhPowerLevelLegalityLabel()); // only 3 buttons allowed for one line - this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_1)); - this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_2_3)); - this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_4_5)); + this.add(new BracketLegalityLabel("Bracket 1", "B1", 1)); + this.add(new BracketLegalityLabel("Bracket 2", "B2", 2)); + this.add(new BracketLegalityLabel("Bracket 3", "B3", 3)); + this.add(new BracketLegalityLabel("Bracket 4", "B4", 4)); + this.add(new BracketLegalityLabel("Bracket 5", "B5", 5)); addHidePanelButton();