diff --git a/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java b/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java new file mode 100644 index 00000000000..3f5676b66d7 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java @@ -0,0 +1,256 @@ +package mage.client.components; + +import mage.MageObject; +import mage.cards.decks.Deck; +import mage.client.util.GUISizeHelper; + +import java.awt.*; +import java.util.List; +import java.util.*; +import java.util.stream.Stream; + +/** + * Inject bracket level inside validation panel + * See more details at wiki + *

+ * Support: + * - [x] game changers + * - [ ] infinite combos + * - [ ] mass land destruction + * - [ ] extra turns + * - [ ] tutors + * + * @author JayDi85 + */ +public class BracketLegalityLabel extends LegalityLabel { + + private static final String GROUP_GAME_CHANGES = "Game Changers"; + private static final String GROUP_INFINITE_COMBOS = "Infinite Combos (unsupported)"; + private static final String GROUP_MASS_LAND_DESTRUCTION = "Mass Land Destruction (unsupported)"; + private static final String GROUP_EXTRA_TURN = "Extra Turns (unsupported)"; + private static final String GROUP_TUTORS = "Tutors (unsupported)"; + + private final BracketLevel level; + + private final List foundGameChangers = new ArrayList<>(); + private final List foundInfiniteCombos = new ArrayList<>(); + private final List foundMassLandDestruction = new ArrayList<>(); + private final List foundExtraTurn = new ArrayList<>(); + private final List foundTutors = new ArrayList<>(); + + private final List badCards = new ArrayList<>(); + private final List fullGameChanges = new ArrayList<>(); + + 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); + } + + @Override + public List selectCards() { + return new ArrayList<>(this.badCards); + } + + 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: + // any allow + break; + default: + throw new IllegalArgumentException("Unsupported level: " + this.level); + } + } + + @Override + public void validateDeck(Deck deck) { + collectAll(deck); + validateBracketLevel(); + + int infoFontSize = 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<>(); + + 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.forEach((group, cards) -> { + showInfo.add("
"); + showInfo.add("" + group + ": " + this.foundGameChangers.size() + ""); + if (!cards.isEmpty()) { + showInfo.add("

"); + } + }); + + String showText = "" + String.join("\n", showInfo) + ""; + showState(showColor, showText, false); + } + + private void collectAll(Deck deck) { + collectGameChangers(deck); + collectInfiniteCombos(deck); + collectMassLandDestruction(deck); + collectExtraTurn(deck); + collectTutors(deck); + } + + private void collectGameChangers(Deck deck) { + this.foundGameChangers.clear(); + + if (fullGameChanges.isEmpty()) { + // https://mtg.wiki/page/Game_Changers + // TODO: share list with AbstractCommander and edh power level + fullGameChanges.addAll(Arrays.asList( + "Ad Nauseam", + "Ancient Tomb", + "Aura Shards", + "Bolas's Citadel", + "Braids, Cabal Minion", + "Demonic Tutor", + "Drannith Magistrate", + "Chrome Mox", + "Coalition Victory", + "Consecrated Sphinx", + "Crop Rotation", + "Cyclonic Rift", + "Deflecting Swat", + "Enlightened Tutor", + "Expropriate", + "Field of the Dead", + "Fierce Guardianship", + "Food Chain", + "Force of Will", + "Gaea's Cradle", + "Gamble", + "Gifts Ungiven", + "Glacial Chasm", + "Grand Arbiter Augustin IV", + "Grim Monolith", + "Humility", + "Imperial Seal", + "Intuition", + "Jeska's Will", + "Jin-Gitaxias, Core Augur", + "Kinnan, Bonder Prodigy", + "Lion's Eye Diamond", + "Mana Vault", + "Mishra's Workshop", + "Mox Diamond", + "Mystical Tutor", + "Narset, Parter of Veils", + "Natural Order", + "Necropotence", + "Notion Thief", + "Rhystic Study", + "Opposition Agent", + "Orcish Bowmasters", + "Panoptic Mirror", + "Seedborn Muse", + "Serra's Sanctum", + "Smothering Tithe", + "Survival of the Fittest", + "Sway of the Stars", + "Teferi's Protection", + "Tergrid, God of Fright", + "Thassa's Oracle", + "The One Ring", + "The Tabernacle at Pendrell Vale", + "Underworld Breach", + "Urza, Lord High Artificer", + "Vampiric Tutor", + "Vorinclex, Voice of Hunger", + "Yuriko, the Tiger's Shadow", + "Winota, Joiner of Forces", + "Worldly Tutor" + )); + } + + Stream.concat(deck.getCards().stream(), deck.getSideboard().stream()) + .map(MageObject::getName) + .filter(fullGameChanges::contains) + .distinct() + .sorted() + .forEach(this.foundGameChangers::add); + } + + private void collectInfiniteCombos(Deck deck) { + // TODO: implement + this.foundInfiniteCombos.clear(); + } + + private void collectMassLandDestruction(Deck deck) { + // TODO: implement + this.foundMassLandDestruction.clear(); + } + + private void collectExtraTurn(Deck deck) { + // TODO: implement + this.foundExtraTurn.clear(); + } + + private void collectTutors(Deck deck) { + // TODO: implement + this.foundTutors.clear(); + } +} \ No newline at end of file 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 998c174c7f6..29f7fce598f 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckLegalityPanel.java @@ -5,6 +5,7 @@ import mage.cards.decks.Deck; import mage.cards.decks.DeckValidator; import mage.cards.mock.MockCard; import mage.cards.mock.MockSplitCard; +import mage.client.components.BracketLegalityLabel; import mage.client.components.EdhPowerLevelLegalityLabel; import mage.client.components.LegalityLabel; import mage.deck.*; @@ -105,6 +106,10 @@ 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)); addHidePanelButton();