Skip to content

Commit edbd847

Browse files
authored
Merge branch 'master' into refactor/clean-filter-logic
2 parents d1f190a + 47f2a4f commit edbd847

File tree

499 files changed

+8257
-3608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

499 files changed

+8257
-3608
lines changed

Mage.Client/src/main/java/mage/client/components/BracketLegalityLabel.java

Lines changed: 196 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import mage.cards.Card;
55
import mage.cards.decks.Deck;
66
import mage.client.util.GUISizeHelper;
7+
import org.apache.log4j.Logger;
78

89
import java.awt.*;
10+
import java.io.BufferedReader;
11+
import java.io.InputStream;
12+
import java.io.InputStreamReader;
913
import java.util.List;
1014
import java.util.*;
1115
import java.util.stream.Stream;
@@ -16,22 +20,73 @@
1620
* <p>
1721
* Support:
1822
* - [x] game changers
19-
* - [ ] infinite combos
23+
* - [x] infinite combos
2024
* - [x] mass land destruction
2125
* - [x] extra turns
2226
* - [x] tutors
27+
* Features:
28+
* - [x] find possible bracket level of the deck
29+
* - [x] find affected cards by checking group
30+
* - [x] can auto-generate infinite combos list, see verify test downloadAndPrepareCommanderBracketsData
31+
* - [ ] TODO: tests
32+
* - [ ] TODO: table - players brackets level disclose settings
33+
* - [ ] TODO: generate - convert card name to xmage format and assert on bad names (ascii only)
2334
*
2435
* @author JayDi85
2536
*/
2637
public class BracketLegalityLabel extends LegalityLabel {
2738

39+
private static final Logger logger = Logger.getLogger(BracketLegalityLabel.class);
40+
2841
private static final String GROUP_GAME_CHANGES = "Game Changers";
29-
private static final String GROUP_INFINITE_COMBOS = "Infinite Combos (unsupported)";
42+
private static final String GROUP_INFINITE_COMBOS = "Infinite Combos";
3043
private static final String GROUP_MASS_LAND_DESTRUCTION = "Mass Land Destruction";
3144
private static final String GROUP_EXTRA_TURN = "Extra Turns";
3245
private static final String GROUP_TUTORS = "Tutors";
3346

34-
private final BracketLevel level;
47+
private static final Map<String, List<Integer>> MAX_GROUP_LIMITS = new LinkedHashMap<>();
48+
49+
static {
50+
// 1
51+
// No cards from the Game Changer list.
52+
// No intentional two-card infinite combos.
53+
// No mass land destruction.
54+
// No extra turn cards.
55+
// Tutors should be sparse.
56+
// 2
57+
// No cards from the Game Changer list.
58+
// No intentional two-card infinite combos.
59+
// No mass land destruction.
60+
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
61+
// Tutors should be sparse.
62+
// 3
63+
// Up to three (3) cards from the Game Changer list.
64+
// No intentional early game two-card infinite combos.
65+
// No mass land destruction.
66+
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
67+
// 4
68+
// 5
69+
// allow any cards
70+
71+
// cards limits per brackets level, it's ok to use 99 as max
72+
// group - levels 0, 1, 2, 3, 4, 5
73+
MAX_GROUP_LIMITS.put(GROUP_GAME_CHANGES,
74+
Arrays.asList(0, 0, 0, 3, 99, 99));
75+
MAX_GROUP_LIMITS.put(GROUP_INFINITE_COMBOS,
76+
Arrays.asList(0, 0, 0, 0, 99, 99));
77+
MAX_GROUP_LIMITS.put(GROUP_MASS_LAND_DESTRUCTION,
78+
Arrays.asList(0, 0, 0, 0, 99, 99));
79+
MAX_GROUP_LIMITS.put(GROUP_EXTRA_TURN,
80+
Arrays.asList(0, 0, 0, 3, 99, 99));
81+
MAX_GROUP_LIMITS.put(GROUP_TUTORS,
82+
Arrays.asList(0, 3, 3, 99, 99, 99));
83+
}
84+
85+
private static final String RESOURCE_INFINITE_COMBOS = "brackets/infinite-combos.txt";
86+
87+
private final String fullName;
88+
private final String shortName;
89+
private final int maxLevel;
3590

3691
private final List<String> foundGameChangers = new ArrayList<>();
3792
private final List<String> foundInfiniteCombos = new ArrayList<>();
@@ -41,28 +96,14 @@ public class BracketLegalityLabel extends LegalityLabel {
4196

4297
private final List<String> badCards = new ArrayList<>();
4398
private final List<String> fullGameChanges = new ArrayList<>();
99+
private final Set<String> fullInfiniteCombos = new HashSet<>(); // card1@card2, sorted by names, name must be xmage compatible
44100

45-
public enum BracketLevel {
46-
BRACKET_1("Bracket 1"),
47-
BRACKET_2_3("Bracket 2-3"),
48-
BRACKET_4_5("Bracket 4-5");
49-
50-
private final String name;
51-
52-
BracketLevel(String name) {
53-
this.name = name;
54-
}
55-
56-
@Override
57-
public String toString() {
58-
return this.name;
59-
}
60-
}
61-
62-
public BracketLegalityLabel(BracketLevel level) {
63-
super(level.toString(), null);
64-
this.level = level;
65-
setPreferredSize(DIM_PREFERRED);
101+
public BracketLegalityLabel(String fullName, String shortName, int maxLevel) {
102+
super(shortName, null);
103+
this.fullName = fullName;
104+
this.shortName = shortName;
105+
this.maxLevel = maxLevel;
106+
setPreferredSize(DIM_PREFERRED_1_OF_5);
66107
}
67108

68109
@Override
@@ -72,81 +113,68 @@ public List<String> selectCards() {
72113

73114
private void validateBracketLevel() {
74115
this.badCards.clear();
75-
switch (this.level) {
76-
case BRACKET_1:
77-
// No cards from the Game Changer list.
78-
// No intentional two-card infinite combos.
79-
// No mass land destruction.
80-
// No extra turn cards.
81-
// Tutors should be sparse.
82-
this.badCards.addAll(this.foundGameChangers);
83-
this.badCards.addAll(this.foundInfiniteCombos);
84-
this.badCards.addAll(this.foundMassLandDestruction);
85-
this.badCards.addAll(this.foundExtraTurn);
86-
if (this.foundTutors.size() > 3) {
87-
this.badCards.addAll(this.foundTutors);
88-
}
89-
break;
90-
case BRACKET_2_3:
91-
// 2
92-
// No cards from the Game Changer list.
93-
// No intentional two-card infinite combos.
94-
// No mass land destruction.
95-
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
96-
// Tutors should be sparse.
97-
// 3
98-
// Up to three (3) cards from the Game Changer list.
99-
// No intentional early game two-card infinite combos.
100-
// No mass land destruction.
101-
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
102-
if (this.foundGameChangers.size() > 3) {
103-
this.badCards.addAll(this.foundGameChangers);
104-
}
105-
this.badCards.addAll(this.foundInfiniteCombos);
106-
this.badCards.addAll(this.foundMassLandDestruction);
107-
if (this.foundExtraTurn.size() > 3) {
108-
this.badCards.addAll(this.foundExtraTurn);
109-
}
110-
// this.badCards.addAll(this.foundTutors); // allow any amount
111-
break;
112-
case BRACKET_4_5:
113-
// allow any cards
114-
break;
115-
default:
116-
throw new IllegalArgumentException("Unsupported level: " + this.level);
116+
117+
if (this.foundGameChangers.size() > getMaxCardsLimit(GROUP_GAME_CHANGES)) {
118+
this.badCards.addAll(this.foundGameChangers);
119+
}
120+
if (this.foundInfiniteCombos.size() > getMaxCardsLimit(GROUP_INFINITE_COMBOS)) {
121+
this.badCards.addAll(this.foundInfiniteCombos);
122+
}
123+
if (this.foundMassLandDestruction.size() > getMaxCardsLimit(GROUP_MASS_LAND_DESTRUCTION)) {
124+
this.badCards.addAll(this.foundMassLandDestruction);
125+
}
126+
if (this.foundExtraTurn.size() > getMaxCardsLimit(GROUP_EXTRA_TURN)) {
127+
this.badCards.addAll(this.foundExtraTurn);
128+
}
129+
if (this.foundTutors.size() > getMaxCardsLimit(GROUP_TUTORS)) {
130+
this.badCards.addAll(this.foundTutors);
117131
}
118132
}
119133

134+
private Integer getMaxCardsLimit(String groupName) {
135+
return MAX_GROUP_LIMITS.get(groupName).get(this.maxLevel);
136+
}
137+
120138
@Override
121139
public void validateDeck(Deck deck) {
122140
collectAll(deck);
123141
validateBracketLevel();
124142

125-
int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f);
143+
int infoFontHeaderSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 1.0f);
144+
int infoFontTextSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f);
126145

127146
// show all found cards in any use cases
128147
Color showColor = this.badCards.isEmpty() ? COLOR_LEGAL : COLOR_NOT_LEGAL;
129148

130149
List<String> showInfo = new ArrayList<>();
131150
if (this.badCards.isEmpty()) {
132-
showInfo.add("<p>Deck is <span style='color:green;font-weight:bold;'>GOOD</span> for " + this.level + "</p>");
151+
showInfo.add(String.format("<span style='font-weight:bold;font-size:%dpx;'><p>Deck is <span style='color:green;'>GOOD</span> for %s</p></span>",
152+
infoFontHeaderSize,
153+
this.fullName
154+
));
133155
} else {
134-
showInfo.add("<p>Deck is <span style='color:#BF544A;font-weight:bold;'>BAD</span> for " + this.level + "</p>");
156+
showInfo.add(String.format("<span style='font-weight:bold;font-size:%dpx;'><p>Deck is <span style='color:#BF544A;'>BAD</span> for %s</p></span>",
157+
infoFontHeaderSize,
158+
this.fullName
159+
));
135160
showInfo.add("<p>(click here to select all bad cards)</p>");
136161
}
137162

138163
Map<String, List<String>> groups = new LinkedHashMap<>();
139-
groups.put(GROUP_GAME_CHANGES, this.foundGameChangers);
140-
groups.put(GROUP_INFINITE_COMBOS, this.foundInfiniteCombos);
141-
groups.put(GROUP_MASS_LAND_DESTRUCTION, this.foundMassLandDestruction);
142-
groups.put(GROUP_EXTRA_TURN, this.foundExtraTurn);
143-
groups.put(GROUP_TUTORS, this.foundTutors);
164+
groups.put(GROUP_GAME_CHANGES + getStats(GROUP_GAME_CHANGES), this.foundGameChangers);
165+
groups.put(GROUP_INFINITE_COMBOS + getStats(GROUP_INFINITE_COMBOS), this.foundInfiniteCombos);
166+
groups.put(GROUP_MASS_LAND_DESTRUCTION + getStats(GROUP_MASS_LAND_DESTRUCTION), this.foundMassLandDestruction);
167+
groups.put(GROUP_EXTRA_TURN + getStats(GROUP_EXTRA_TURN), this.foundExtraTurn);
168+
groups.put(GROUP_TUTORS + getStats(GROUP_TUTORS), this.foundTutors);
144169
groups.forEach((group, cards) -> {
145170
showInfo.add("<br>");
146-
showInfo.add("<br>");
147-
showInfo.add("<span style='font-weight:bold;'>" + group + ": " + cards.size() + "</span>");
148-
if (!cards.isEmpty()) {
149-
showInfo.add("<ul style=\"font-size: " + infoFontSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">");
171+
showInfo.add("<span style='font-weight:bold;font-size: " + infoFontTextSize + "px;'>" + group + "</span>");
172+
if (cards.isEmpty()) {
173+
showInfo.add("<ul style=\"font-size: " + infoFontTextSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">");
174+
showInfo.add("<li style=\"margin-bottom: 2px;\">no cards</li>");
175+
showInfo.add("</ul>");
176+
} else {
177+
showInfo.add("<ul style=\"font-size: " + infoFontTextSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">");
150178
cards.forEach(s -> showInfo.add(String.format("<li style=\"margin-bottom: 2px;\">%s</li>", s)));
151179
showInfo.add("</ul>");
152180
}
@@ -156,6 +184,39 @@ public void validateDeck(Deck deck) {
156184
showState(showColor, showText, false);
157185
}
158186

187+
private String getStats(String groupName) {
188+
int currentAmount = 0;
189+
switch (groupName) {
190+
case GROUP_GAME_CHANGES:
191+
currentAmount = this.foundGameChangers.size();
192+
break;
193+
case GROUP_INFINITE_COMBOS:
194+
currentAmount = this.foundInfiniteCombos.size();
195+
break;
196+
case GROUP_MASS_LAND_DESTRUCTION:
197+
currentAmount = this.foundMassLandDestruction.size();
198+
break;
199+
case GROUP_EXTRA_TURN:
200+
currentAmount = this.foundExtraTurn.size();
201+
break;
202+
case GROUP_TUTORS:
203+
currentAmount = this.foundTutors.size();
204+
break;
205+
default:
206+
throw new IllegalArgumentException("Unknown group " + groupName);
207+
}
208+
int maxAmount = MAX_GROUP_LIMITS.get(groupName).get(this.maxLevel);
209+
210+
String info;
211+
if (currentAmount > maxAmount) {
212+
info = " (<span style='color:#BF544A;'>%s of %s</span>)";
213+
} else {
214+
info = " (<span>%s of %s</span>)";
215+
}
216+
217+
return String.format(info, currentAmount, maxAmount == 99 ? "any" : maxAmount);
218+
}
219+
159220
private void collectAll(Deck deck) {
160221
collectGameChangers(deck);
161222
collectInfiniteCombos(deck);
@@ -243,8 +304,64 @@ private void collectGameChangers(Deck deck) {
243304
}
244305

245306
private void collectInfiniteCombos(Deck deck) {
246-
// TODO: implement
247307
this.foundInfiniteCombos.clear();
308+
309+
if (this.fullInfiniteCombos.isEmpty()) {
310+
InputStream in = BracketLegalityLabel.class.getClassLoader().getResourceAsStream(RESOURCE_INFINITE_COMBOS);
311+
if (in == null) {
312+
throw new RuntimeException("Commander brackets: can't load infinite combos list");
313+
}
314+
try (InputStreamReader input = new InputStreamReader(in);
315+
BufferedReader reader = new BufferedReader(input)) {
316+
String line = reader.readLine();
317+
while (line != null) {
318+
try {
319+
line = line.trim();
320+
if (line.startsWith("#")) {
321+
continue;
322+
}
323+
List<String> cards = Arrays.asList(line.split("@"));
324+
if (cards.size() != 2) {
325+
logger.warn("wrong line format in commander brackets file: " + line);
326+
continue;
327+
}
328+
329+
Collections.sort(cards);
330+
this.fullInfiniteCombos.add(String.join("@", cards));
331+
} finally {
332+
line = reader.readLine();
333+
}
334+
}
335+
} catch (Exception e) {
336+
throw new RuntimeException("Tokens brackets: can't load infinite combos list - " + e);
337+
}
338+
}
339+
340+
// search and check all x2 combinations
341+
List<Card> deckCards = new ArrayList<>();
342+
Set<Card> foundCards = new HashSet<>();
343+
deckCards.addAll(deck.getCards());
344+
deckCards.addAll(deck.getSideboard());
345+
for (Card card1 : deckCards) {
346+
for (Card card2 : deckCards) {
347+
if (card1 == card2) {
348+
continue;
349+
}
350+
List<String> names = Arrays.asList(card1.getName(), card2.getName());
351+
Collections.sort(names);
352+
String deckCombo = String.join("@", names);
353+
if (this.fullInfiniteCombos.contains(deckCombo)) {
354+
foundCards.add(card1);
355+
foundCards.add(card2);
356+
break;
357+
}
358+
}
359+
}
360+
361+
foundCards.stream()
362+
.map(MageObject::getName)
363+
.sorted()
364+
.forEach(this.foundInfiniteCombos::add);
248365
}
249366

250367
private void collectMassLandDestruction(Deck deck) {

Mage.Client/src/main/java/mage/client/components/EdhPowerLevelLegalityLabel.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import mage.cards.decks.Deck;
44
import mage.client.util.GUISizeHelper;
5-
import mage.client.util.gui.GuiDisplayUtil;
65
import mage.deck.Commander;
76

87
import java.util.ArrayList;
@@ -22,7 +21,7 @@ public class EdhPowerLevelLegalityLabel extends LegalityLabel {
2221

2322
public EdhPowerLevelLegalityLabel() {
2423
super("EDH Power Level: ?", null);
25-
setPreferredSize(DIM_PREFERRED_X3);
24+
setPreferredSize(DIM_PREFERRED_3_OF_3);
2625
}
2726

2827
@Override

0 commit comments

Comments
 (0)