diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index fe8aee6cb53..9b3104a83ab 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; -import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,14 +48,15 @@ public class DeckRecognizer { * The Enum TokenType. */ public enum TokenType { - KnownCard, - UnknownCard, - IllegalCard, - DeckName, - DeckSectionName, - Comment, - UnknownText, - CardType + LEGAL_CARD_REQUEST, + ILLEGAL_CARD_REQUEST, + INVALID_CARD_REQUEST, + UNKNOWN_CARD_REQUEST, + DECK_NAME, + DECK_SECTION_NAME, + COMMENT, + UNKNOWN_TEXT, + CARD_TYPE } /** @@ -68,35 +68,41 @@ public class DeckRecognizer { private final int number; private final String text; - public static Token knownCard(final PaperCard theCard, final int count) { - return new Token(theCard, TokenType.KnownCard, count, null); + public static Token KnownCard(final PaperCard theCard, final int count) { + return new Token(theCard, TokenType.LEGAL_CARD_REQUEST, count, null); } - public static Token illegalCard(final String cardName, final String setCode, final int count) { + public static Token IllegalCard(final String cardName, final String setCode, final int count) { String ttext = setCode == null || setCode.equals("") ? cardName : String.format("%s [%s]", cardName, setCode); - return new Token(null, TokenType.IllegalCard, count, ttext); + return new Token(null, TokenType.ILLEGAL_CARD_REQUEST, count, ttext); } - public static Token unknownCard(final String cardName, final String setCode, final int count) { + public static Token InvalidCard(final String cardName, final String setCode, final int count) { String ttext = setCode == null || setCode.equals("") ? cardName : String.format("%s [%s]", cardName, setCode); - return new Token(null, TokenType.UnknownCard, count, ttext); + return new Token(null, TokenType.INVALID_CARD_REQUEST, count, ttext); } - public static Token deckSection(final String sectionName){ - if (sectionName.contains("avatar")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Avatar.name()); - if (sectionName.contains("commander")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Commander.name()); - if (sectionName.contains("schemes")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Schemes.name()); - if (sectionName.contains("conspiracy")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Conspiracy.name()); - if (sectionName.contains("side") || sectionName.contains("sideboard")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Sideboard.name()); - if (sectionName.contains("main") || sectionName.contains("card")) - return new Token(TokenType.DeckSectionName, 1, DeckSection.Main.name()); + public static Token UnknownCard(final String cardName, final String setCode, final int count) { + String ttext = setCode == null || setCode.equals("") ? cardName : + String.format("%s [%s]", cardName, setCode); + return new Token(null, TokenType.UNKNOWN_CARD_REQUEST, count, ttext); + } + + public static Token DeckSection(final String sectionName){ + if (sectionName.equals("avatar")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Avatar.name()); + if (sectionName.equals("commander")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Commander.name()); + if (sectionName.equals("schemes")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Schemes.name()); + if (sectionName.equals("conspiracy")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Conspiracy.name()); + if (sectionName.equals("side") || sectionName.contains("sideboard")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Sideboard.name()); + if (sectionName.equals("main") || sectionName.contains("card") || sectionName.equals("mainboard")) + return new Token(TokenType.DECK_SECTION_NAME, DeckSection.Main.name()); return null; } @@ -109,11 +115,15 @@ public class DeckRecognizer { public Token(final TokenType type1, final int count, final String message) { this(null, type1, count, message); - if ((type1 == TokenType.KnownCard) || (type1 == TokenType.UnknownCard)) { + if ((type1 == TokenType.LEGAL_CARD_REQUEST) || (type1 == TokenType.UNKNOWN_CARD_REQUEST)) { throw new IllegalArgumentException("Use factory methods for recognized " + REGRP_CARD + " lines"); } } + public Token(final TokenType type1, final String message) { + this(type1, 0, message); + } + public final String getText() { return this.text; } @@ -146,38 +156,66 @@ public class DeckRecognizer { // private static final Pattern EDITION_AFTER_CARD_NAME = Pattern.compile("([\\d]{1,2})?\\s*([a-zA-Z',\\/\\-\\s]+)\\s*((\\(|\\[)([a-zA-Z0-9]{3})(\\)|\\])\\s*)?([\\d]{1,3})?"); // Core Matching Patterns (initialised in Constructor) - private static final String REGRP_DECKNAME = "deckName"; - private static final String DECK_NAME_REGEX = - String.format("(?
(deck(\\s+name)?)|((deck\\s+)?name))(\\:)?\\s*(?<%s>[a-zA-Z',\\/\\-\\s]+)\\s*",
+ public static final String REGRP_DECKNAME = "deckName";
+ public static final String REX_DECK_NAME =
+ String.format("^(//\\s*)?(?(deck name|name|deck))(\\:|\\s)\\s*(?<%s>[a-zA-Z0-9',\\/\\-\\s]+)\\s*(.*)$",
REGRP_DECKNAME);
- private static final Pattern DECK_NAME_PATTERN = Pattern.compile(DECK_NAME_REGEX, Pattern.CASE_INSENSITIVE);
+ public static final Pattern DECK_NAME_PATTERN = Pattern.compile(REX_DECK_NAME, Pattern.CASE_INSENSITIVE);
- private static final String REGRP_CARDNO = "count";
- private static final String REGRP_SET = "setCode";
- private static final String REGRP_COLLNO = "collectorNumber";
- private static final String REGRP_CARD = "card";
+ public static final String REGRP_NOCARD = "token";
+ public static final String REX_NOCARD = String.format("^(?[^a-zA-Z]*)\\s*(?(\\w+[:]\\s*))?(?<%s>[a-zA-Z]+)(?[^a-zA-Z]*)?$", REGRP_NOCARD);
+ public static final Pattern NONCARD_PATTERN = Pattern.compile(REX_NOCARD, Pattern.CASE_INSENSITIVE);
+
+ public static final String REGRP_SET = "setcode";
+ public static final String REGRP_COLLNR = "collnr";
+ public static final String REGRP_CARD = "cardname";
+ public static final String REGRP_CARDNO = "count";
+
+ public static final String REX_CARD_NAME = String.format("(?<%s>[a-zA-Z0-9',\\.:!\\+\\\"\\/\\-\\s]+)", REGRP_CARD);
+ public static final String REX_SET_CODE = String.format("(?<%s>[a-zA-Z0-9_]{2,7})", REGRP_SET);
+ public static final String REX_COLL_NUMBER = String.format("(?<%s>\\S?[0-9A-Z]+\\S?[A-Z]*)", REGRP_COLLNR);
+ public static final String REX_CARD_COUNT = String.format("(?<%s>[\\d]{1,2})(?x)?", REGRP_CARDNO);
+
+ // 1. Card-Set Request (Amount?, CardName, Set)
+ public static final String REX_CARD_SET_REQUEST = String.format(
+ "(%s\\s)?\\s*%s\\s*(\\s|\\||\\(|\\[|\\{)%s(\\s|\\)|\\]|\\})?",
+ REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE);
+ public static final Pattern CARD_SET_PATTERN = Pattern.compile(REX_CARD_SET_REQUEST);
+
+ // 2. Set-Card Request (Amount?, Set, CardName)
+ public static final String REX_SET_CARD_REQUEST = String.format(
+ "(%s\\s)?\\s*(\\(|\\[|\\{)?%s(\\s+|\\)|\\]|\\}|\\|)\\s*%s\\s*",
+ REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME);
+ public static final Pattern SET_CARD_PATTERN = Pattern.compile(REX_SET_CARD_REQUEST);
+
+ // 3. Full-Request (Amount?, CardName, Set, Collector Number|Art Index) - MTGArena Format
+ public static final String REX_FULL_REQUEST_CARD_SET = String.format(
+ "(%s\\s)?\\s*%s\\s*(\\||\\(|\\[|\\{|\\s)%s(\\s|\\)|\\]|\\})?\\s+%s",
+ REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE, REX_COLL_NUMBER);
+ public static final Pattern CARD_SET_COLLNO_PATTERN = Pattern.compile(REX_FULL_REQUEST_CARD_SET);
+
+ // 4. Full-Request (Amount?, Set, CardName, Collector Number|Art Index) - Alternative for flexibility
+ public static final String REX_FULL_REQUEST_SET_CARD = String.format(
+ "^(%s\\s)?\\s*(\\(|\\[|\\{)?%s(\\s+|\\)|\\]|\\}|\\|)\\s*%s\\s+%s$",
+ REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME, REX_COLL_NUMBER);
+ public static final Pattern SET_CARD_COLLNO_PATTERN = Pattern.compile(REX_FULL_REQUEST_SET_CARD);
+
+ // 5. Card-Only Request (Amount?)
+ public static final String REX_CARDONLY = String.format(
+ "(%s\\s)?\\s*%s", REX_CARD_COUNT, REX_CARD_NAME);
+ public static final Pattern CARD_ONLY_PATTERN = Pattern.compile(REX_CARDONLY);
- private static final String CARD_REGEX_SET_NAME = String.format(
- "(?<%s>[\\d]{1,2})?\\s*((\\(|\\[)?(?<%s>[a-zA-Z0-9]{3})(\\)|\\]|\\|)?)?" +
- "(?<%s>[a-zA-Z0-9',\\/\\-\\s]+)\\s*((\\|)(?<%s>[0-9A-Z]+\\S?[A-Z]*))?",
- REGRP_CARDNO, REGRP_SET, REGRP_CARD, REGRP_COLLNO);
- private static final String CARD_REGEX_NAME_SET =
- String.format("(?<%s>[\\d]{1,2})?\\s*(?<%s>[a-zA-Z0-9',\\/\\-\\s]+)\\s*((\\(|\\[|\\|)" +
- "(?<%s>[a-zA-Z0-9]{3})(\\)|\\])?)?\\s*(?<%s>[0-9A-Z]+\\S?[A-Z]*)?",
- REGRP_CARDNO, REGRP_CARD, REGRP_SET, REGRP_COLLNO);
- private static final Pattern CARD_SET_NAME_PATTERN = Pattern.compile(CARD_REGEX_SET_NAME);
- private static final Pattern CARD_NAME_SET_PATTERN = Pattern.compile(CARD_REGEX_NAME_SET);
// CoreTypes (to recognise Tokens of type CardType
- private static List cardTypes = null;
- // Note: Planes section is not included, as it is checked against "Plainswalker" (see `isDeckSectionName` method)
- private static final CharSequence[] DECK_SECTION_NAMES = {"avatar", "commander", "schemes", "conspiracy",
- "main", "card",
- "side", "sideboard"};
+ private static CharSequence[] CARD_TYPES = allCardTypes();
- private CardDb db;
- private CardDb altDb;
- private Date recognizeCardsPrintedBefore = null;
+ private static final CharSequence[] DECK_SECTION_NAMES = {"avatar", "commander",
+ "schemes", "conspiracy", "planes",
+ "main", "card", "mainboard", "side", "sideboard"};
+
+ private final CardDb db;
+ private final CardDb altDb;
+ private Date releaseDateConstraint = null;
// This two parameters are controlled only via setter methods
private List allowedSetCodes = null; // as imposed by current format
private DeckFormat deckFormat = null; //
@@ -185,34 +223,14 @@ public class DeckRecognizer {
public DeckRecognizer(CardDb db, CardDb altDb) {
this.db = db;
this.altDb = altDb;
-
- if (cardTypes == null) {
- // CoreTypesNames
- List coreTypes = Lists.newArrayList(CardType.CoreType.values());
- cardTypes = new ArrayList();
- coreTypes.forEach(new Consumer() {
- @Override
- public void accept(CardType.CoreType ctype) {
- cardTypes.add(ctype.name().toLowerCase());
- }
- });
- // Manual Additions:
- // NOTE: "sorceries" is also included as it can be found in exported deck, even if it's incorrect.
- // Example: https://deckstats.net/decks/70852/556925-artifacts/en - see Issue 1010
- cardTypes.add("sorceries"); // Sorcery is the only name with different plural form
- cardTypes.add("aura"); // in case.
- cardTypes.add("mana"); // "Mana" (see Issue 1010)
- cardTypes.add("spell");
- cardTypes.add("other spell");
- }
}
public Token recognizeLine(final String rawLine) {
if (StringUtils.isBlank(rawLine.trim()))
return null;
- if (StringUtils.startsWith(rawLine.trim(), LINE_COMMENT_DELIMITER)) {
- return new Token(TokenType.Comment, 0, rawLine);
- }
+ if (StringUtils.startsWith(rawLine.trim(), LINE_COMMENT_DELIMITER))
+ return new Token(TokenType.COMMENT, 0, rawLine);
+
final char smartQuote = (char) 8217;
String line = rawLine.trim().replace(smartQuote, '\'');
@@ -230,46 +248,63 @@ public class DeckRecognizer {
Token result = recogniseCardToken(line);
if (result == null)
- result = recogniseNonCardToken(line, 1);
- return result != null ? result : new Token(TokenType.UnknownText, 0, line);
+ result = recogniseNonCardToken(line);
+ return result != null ? result : new Token(TokenType.UNKNOWN_TEXT, 0, line);
}
- private Token recogniseCardToken(final String text) {
+ public Token recogniseCardToken(final String text) {
String line = text.trim();
+ Token uknonwnCardToken = null;
+
// TODO: recognize format: http://topdeck.ru/forum/index.php?showtopic=12711
// @leriomaggio: DONE!
- List cardMatchers = getRegexMatchers(line);
- for (Matcher cardMatcher : cardMatchers) {
- String cardName = cardMatcher.group(REGRP_CARD);
- String ccount = cardMatcher.group(REGRP_CARDNO);
- String setCode = cardMatcher.group(REGRP_SET);
- String collNo = cardMatcher.group(REGRP_COLLNO);
- int cardCount = ccount != null ? Integer.parseInt(ccount) : 1;
- // if any, it will be tried to convert specific collector number to art index (useful for lands).
- String collectorNumber = collNo != null ? collNo : IPaperCard.NO_COLLECTOR_NUMBER;
-
+ List cardMatchers = getRegExMatchers(line);
+ for (Matcher matcher : cardMatchers) {
+ String cardName = getRexGroup(matcher, REGRP_CARD);
+ if (cardName == null)
+ continue;
+ cardName = cardName.trim();
//Avoid hit the DB - check whether cardName is contained in the DB
CardDb.CardRequest cr = CardDb.CardRequest.fromString(cardName.trim()); // to account for any FOIL request
if (!foundInCardDb(cr.cardName))
continue; // skip to the next matcher!
- // Ok Now we're sure the cardName is correct. Now check for setCode
- CardEdition edition = StaticData.instance().getEditions().get(setCode);
- if (edition != null){
+ String ccount = getRexGroup(matcher, REGRP_CARDNO);
+ String setCode = getRexGroup(matcher, REGRP_SET);
+ String collNo = getRexGroup(matcher, REGRP_COLLNR);
+ int cardCount = ccount != null ? Integer.parseInt(ccount) : 1;
+ // if any, it will be tried to convert specific collector number to art index (useful for lands).
+ String collectorNumber = collNo != null ? collNo : IPaperCard.NO_COLLECTOR_NUMBER;
+ int artIndex;
+ try {
+ artIndex = Integer.parseInt(collectorNumber);
+ } catch (NumberFormatException ex){
+ artIndex = IPaperCard.NO_ART_INDEX;
+ }
+
+ if (setCode != null) {
+ // Ok Now we're sure the cardName is correct. Now check for setCode
+ CardEdition edition = StaticData.instance().getEditions().get(setCode);
+ if (edition == null) {
+ // set the case for unknown card (in case) and continue to the next for any better matching
+ uknonwnCardToken = Token.UnknownCard(cardName, setCode, cardCount);
+ continue;
+ }
+ if (isNotCompliantWithReleaseDateRestrictions(edition))
+ return Token.InvalidCard(cr.cardName, edition.getCode(), cardCount);
+
// we now name is ok, set is ok - we just need to be sure about collector number (if any)
// and if that card can be actually found in the requested set.
// IOW: we should account for wrong request, e.g. Counterspell|FEM - just doesn't exist!
- PaperCard pc = getCardFromSet(cr.cardName, edition, collectorNumber, cr.isFoil);
+ PaperCard pc = this.getCardFromSet(cr.cardName, edition, collectorNumber, artIndex, cr.isFoil);
if (pc != null) {
// ok so the card has been found - let's see if there's any restriction on the set
- if (this.allowedSetCodes != null && !this.allowedSetCodes.contains(setCode))
+ if (isIllegalSetInGameFormat(setCode) || isIllegalCardInDeckFormat(pc))
// Mark as illegal card
- return Token.illegalCard(pc.getName(), pc.getEdition(), cardCount);
- if (this.deckFormat != null && !deckFormat.isLegalCard(pc))
- return Token.illegalCard(pc.getName(), pc.getEdition(), cardCount);
- return Token.knownCard(pc, cardCount);
+ return Token.IllegalCard(pc.getName(), pc.getEdition(), cardCount);
+ return Token.KnownCard(pc, cardCount);
}
// UNKNOWN card as in the Counterspell|FEM case
- return Token.unknownCard(cardName, setCode, cardCount);
+ return Token.UnknownCard(cardName, setCode, cardCount);
}
// ok so we can simply ignore everything but card name - as set code does not exist
// At this stage, we know the card name exists in the DB so a Card MUST be found
@@ -277,43 +312,89 @@ public class DeckRecognizer {
// In that case, an illegalCard token will be returned!
PaperCard pc = this.getCardFromSupportedEditions(cr.cardName, cr.isFoil);
if (pc != null){
- if (this.deckFormat != null && !deckFormat.isLegalCard(pc))
- return Token.illegalCard(pc.getName(), pc.getEdition(), cardCount);
- return Token.knownCard(pc, cardCount);
+ if (isIllegalCardInDeckFormat(pc))
+ return Token.IllegalCard(pc.getName(), pc.getEdition(), cardCount);
+ return Token.KnownCard(pc, cardCount);
}
- return Token.illegalCard(cardName, "", cardCount);
+ return Token.IllegalCard(cardName, "", cardCount);
}
- return null;
+ return uknonwnCardToken; // either null or unknown card
+ }
+
+ private String getRexGroup(Matcher matcher, String groupName){
+ String rexGroup;
+ try{
+ rexGroup = matcher.group(groupName);
+ } catch (IllegalArgumentException ex) {
+ rexGroup = null;
+ }
+ return rexGroup;
+ }
+
+ private boolean isIllegalCardInDeckFormat(PaperCard pc) {
+ return this.deckFormat != null && !deckFormat.isLegalCard(pc);
+ }
+
+ private boolean isIllegalSetInGameFormat(String setCode) {
+ return this.allowedSetCodes != null && !this.allowedSetCodes.contains(setCode);
+ }
+
+ private boolean isNotCompliantWithReleaseDateRestrictions(CardEdition edition){
+ return this.releaseDateConstraint != null && edition.getDate().compareTo(this.releaseDateConstraint) >= 0;
}
private boolean foundInCardDb(String cardName){
- return (this.db.contains(cardName.trim()) || this.altDb.contains(cardName.trim()));
+ return this.db.contains(cardName) || this.altDb.contains(cardName);
}
- private List getRegexMatchers(String line){
- final Matcher setBeforeNameMatcher = CARD_SET_NAME_PATTERN.matcher(line);
- final Matcher setAfterNameMatcher = CARD_NAME_SET_PATTERN.matcher(line);
+ private List getRegExMatchers(String line) {
List matchers = new ArrayList<>();
- if (setBeforeNameMatcher.find())
- matchers.add(setAfterNameMatcher);
- if (setAfterNameMatcher.find())
- matchers.add(setBeforeNameMatcher);
+ Pattern[] patternsWithCollNumber = new Pattern[] {
+ CARD_SET_COLLNO_PATTERN,
+ SET_CARD_COLLNO_PATTERN
+ };
+ for (Pattern pattern : patternsWithCollNumber) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches() && getRexGroup(matcher, REGRP_SET) != null &&
+ getRexGroup(matcher, REGRP_COLLNR) != null)
+ matchers.add(matcher);
+ }
+ Pattern[] OtherPatterns = new Pattern[] { // Order counts
+ CARD_SET_PATTERN,
+ SET_CARD_PATTERN,
+ CARD_ONLY_PATTERN
+ };
+ for (Pattern pattern : OtherPatterns) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches())
+ matchers.add(matcher);
+ }
return matchers;
-
-// if (fullMatchSetAfter.length() == 0 && fullMatchSetBefore.length() == 0)
-// return null;
-// if (fullMatchSetBefore.equals(line))
-// return setBeforeNameMatcher;
-// if (fullMatchSetAfter.equals(line))
-// return setAfterNameMatcher;
-// return fullMatchSetAfter.length() > fullMatchSetBefore.length() ? setAfterNameMatcher : setBeforeNameMatcher;
}
private PaperCard getCardFromSet(final String cardName, final CardEdition edition,
- final String collectorNumber, final boolean isFoil) {
+ final String collectorNumber, final int artIndex,
+ final boolean isFoil) {
+ // Try with collector number first
PaperCard result = this.db.getCardFromSet(cardName, edition, collectorNumber, isFoil);
if (result == null)
result = this.altDb.getCardFromSet(cardName, edition, collectorNumber, isFoil);
+ if (result == null && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) &&
+ artIndex != IPaperCard.NO_ART_INDEX){
+ // So here we know cardName exists (checked before invoking this method)
+ // and also a Collector Number was specified.
+ // The only case we would reach this point is either due to a wrong edition-card match
+ // (later resulting in Unknown card - e.g. "Counterspell|FEM") or due to the fact that
+ // art Index was specified instead of collector number! Let's give it a go with that
+ // but only if artIndex is not NO_ART_INDEX (e.g. collectorNumber = "*32")
+ int maxArtForCard = this.db.contains(cardName) ? this.db.getMaxArtIndex(cardName) :
+ this.altDb.getMaxArtIndex(cardName);
+ if (artIndex <= maxArtForCard){
+ // if collNr was "78", it's hardly an artIndex. It was just the wrong collNr for the requested card
+ result = this.db.contains(cardName) ? this.db.getCardFromSet(cardName, edition, artIndex, isFoil) :
+ this.altDb.getCardFromSet(cardName, edition, artIndex, isFoil);
+ }
+ }
return result;
}
@@ -322,139 +403,109 @@ public class DeckRecognizer {
if (this.allowedSetCodes != null && this.allowedSetCodes.size() > 0)
filter = (Predicate) this.db.isLegal(this.allowedSetCodes);
String reqInfo = CardDb.CardRequest.compose(cardName, isFoil);
- PaperCard result = this.db.getCardFromEditions(reqInfo, filter);
- if (result == null)
- result = this.altDb.getCardFromEditions(reqInfo, filter);
+ PaperCard result;
+ if (this.releaseDateConstraint != null){
+ result = this.db.getCardFromEditionsReleasedBefore(reqInfo,
+ this.releaseDateConstraint, filter);
+ if (result == null)
+ result = this.altDb.getCardFromEditionsReleasedBefore(reqInfo,
+ this.releaseDateConstraint, filter);
+ }
+ else {
+ result = this.db.getCardFromEditions(reqInfo, filter);
+ if (result == null)
+ result = this.altDb.getCardFromEditions(reqInfo, filter);
+ }
return result;
}
- private static Token recogniseNonCardToken(final String text, final int n) {
+ public Token recogniseNonCardToken(final String text) {
if (isDeckSectionName(text)) {
- return Token.deckSection(text.toLowerCase().trim());
+ String tokenText = getNonCardTokenText(text.toLowerCase().trim());
+ return Token.DeckSection(tokenText);
}
if (isCardType(text)) {
- return new Token(TokenType.CardType, n, text);
+ String tokenText = getNonCardTokenText(text);
+ return new Token(TokenType.CARD_TYPE, tokenText);
}
if (isDeckName(text)) {
String deckName = getDeckName(text);
- return new Token(TokenType.DeckName, n, deckName);
+ return new Token(TokenType.DECK_NAME, deckName);
}
return null;
}
+ private static String getNonCardTokenText(final String line){
+ Matcher noncardMatcher = NONCARD_PATTERN.matcher(line);
+ if (!noncardMatcher.matches())
+ return "";
+ return noncardMatcher.group(REGRP_NOCARD);
+ }
+
+ private static CharSequence[] allCardTypes(){
+ List cardTypesList = new ArrayList<>();
+ // CoreTypesNames
+ List coreTypes = Lists.newArrayList(CardType.CoreType.values());
+ for (CardType.CoreType coreType : coreTypes)
+ cardTypesList.add(coreType.name().toLowerCase());
+ // Manual Additions:
+ // NOTE: "sorceries" is also included as it can be found in exported deck, even if it's incorrect.
+ // Example: https://deckstats.net/decks/70852/556925-artifacts/en - see Issue 1010
+ cardTypesList.add("sorceries"); // Sorcery is the only name with different plural form
+ cardTypesList.add("aura"); // in case.
+ cardTypesList.add("mana"); // "Mana" (see Issue 1010)
+ cardTypesList.add("spell");
+ cardTypesList.add("other spell");
+ cardTypesList.add("planeswalker");
+ return cardTypesList.toArray(new CharSequence[cardTypesList.size()]);
+ }
+
// NOTE: Card types recognition is ONLY used for style formatting in the Import Editor
// This won't affect the import process of cards in any way !-)
- private static boolean isCardType(final String lineAsIs) {
- final String line = lineAsIs.toLowerCase().trim();
- for (final String cardType : cardTypes) {
- if (line.startsWith(cardType))
- return true;
- }
- return false;
+ public static boolean isCardType(final String lineAsIs) {
+ String line = lineAsIs.toLowerCase().trim();
+ Matcher noncardMatcher = NONCARD_PATTERN.matcher(line);
+ if (!noncardMatcher.matches())
+ return false;
+ String nonCardToken = noncardMatcher.group(REGRP_NOCARD);
+ return StringUtils.containsAny(nonCardToken, CARD_TYPES);
}
- private static boolean isDeckName(final String lineAsIs) {
+ public static boolean isDeckName(final String lineAsIs) {
final String line = lineAsIs.trim();
final Matcher deckNameMatcher = DECK_NAME_PATTERN.matcher(line);
- return (deckNameMatcher.find());
+ boolean matches = deckNameMatcher.matches();
+ return matches;
}
- private static String getDeckName(final String text) {
+ public static String getDeckName(final String text) {
String line = text.trim();
final Matcher deckNamePattern = DECK_NAME_PATTERN.matcher(line);
- if (deckNamePattern.find())
+ if (deckNamePattern.matches())
return deckNamePattern.group(REGRP_DECKNAME); // Deck name is at match 7
- return line;
+ return "";
}
- private static boolean isDeckSectionName(final String text) {
+ public static boolean isDeckSectionName(final String text) {
String line = text.toLowerCase().trim();
- if (StringUtils.containsAny(line, DECK_SECTION_NAMES))
- return true;
- return line.contains("planes") && !line.contains("planeswalker");
+ Matcher noncardMatcher = NONCARD_PATTERN.matcher(line);
+ if (!noncardMatcher.matches())
+ return false;
+ String nonCardToken = noncardMatcher.group(REGRP_NOCARD);
+ return StringUtils.equalsAnyIgnoreCase(nonCardToken, DECK_SECTION_NAMES);
}
- public void setDateConstraint(int month, Integer year) {
+ public void setDateConstraint(int year, int month) {
Calendar ca = Calendar.getInstance();
ca.set(year, month, 1);
- recognizeCardsPrintedBefore = ca.getTime();
+ releaseDateConstraint = ca.getTime();
}
- public void setCardEditionConstrain(List allowedSetCodes){
+ public void setGameFormatConstraint(List allowedSetCodes){
this.allowedSetCodes = allowedSetCodes;
}
public void setDeckFormatConstraint(DeckFormat deckFormat0){
this.deckFormat = deckFormat0;
}
-
-// private Token recognizePossibleNameAndNumber(final String name, final int n) {
-// PaperCard pc = tryGetCard(name);
-// if (null != pc) {
-// return Token.knownCard(pc, n);
-// }
-//
-// // TODO: recognize format: http://topdeck.ru/forum/index.php?showtopic=12711
-// //final Matcher foundEditionName = READ_SEPARATED_EDITION.matcher(name);
-//
-// final Token known = DeckRecognizer.recognizeNonCard(name, n);
-// return null == known ? Token.unknownCard(name, n) : known;
-// }
-//
-// private static Token recognizeNonCard(final String text, final int n) {
-// if (DeckRecognizer.isDecoration(text)) {
-// return new Token(TokenType.Comment, n, text);
-// }
-// if (DeckRecognizer.isSectionName(text)) {
-// return new Token(TokenType.SectionName, n, text);
-// }
-// return null;
-// }
-
-// private static final String[] KNOWN_COMMENTS = new String[] { "land", "lands", "creatures", "creature", "spells",
-// "enchantments", "other spells", "artifacts" };
-// private static final String[] KNOWN_COMMENT_PARTS = new String[] { "card" };
-//
-// private static boolean isDecoration(final String lineAsIs) {
-// final String line = lineAsIs.toLowerCase();
-// for (final String s : DeckRecognizer.KNOWN_COMMENT_PARTS) {
-// if (line.contains(s)) {
-// return true;
-// }
-// }
-// for (final String s : DeckRecognizer.KNOWN_COMMENTS) {
-// if (line.equalsIgnoreCase(s)) {
-// return true;
-// }
-// }
-// return false;
-// }
-//
-// private static boolean isSectionName(final String line) {
-// if (line.toLowerCase().contains("side")) {
-// return true;
-// }
-// if (line.toLowerCase().contains("main")) {
-// return true;
-// }
-// if (line.toLowerCase().contains("commander")) {
-// return true;
-// }
-// if (line.toLowerCase().contains("planes")) {
-// return true;
-// }
-// if (line.toLowerCase().contains("schemes")) {
-// return true;
-// }
-// if (line.toLowerCase().contains("vanguard")) {
-// return true;
-// }
-// return false;
-// }
-//
-// public void setDateConstraint(int month, Integer year) {
-// Calendar ca = Calendar.getInstance();
-// ca.set(year, month, 1);
-// recognizeCardsPrintedBefore = ca.getTime();
-// }
}
diff --git a/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java b/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java
new file mode 100644
index 00000000000..d163a09528f
--- /dev/null
+++ b/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java
@@ -0,0 +1,1160 @@
+package forge.deck;
+
+import forge.StaticData;
+import forge.card.CardDb;
+import forge.card.CardEdition;
+import forge.card.ForgeCardMockTestCase;
+import forge.item.IPaperCard;
+import forge.item.PaperCard;
+import forge.deck.DeckRecognizer.Token;
+import forge.deck.DeckRecognizer.TokenType;
+import forge.model.FModel;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeckRecognizerTest extends ForgeCardMockTestCase {
+
+ private Set mtgUniqueCardNames;
+ private Set mtgUniqueSetCodes;
+ private Set mtgUniqueCollectorNumbers;
+
+ private void initMaps(){
+ StaticData magicDb = FModel.getMagicDb();
+ mtgUniqueCardNames = new HashSet<>();
+ mtgUniqueSetCodes = new HashSet<>();
+ mtgUniqueCollectorNumbers = new HashSet<>();
+ Collection fullCardDb = magicDb.getCommonCards().getAllCards();
+ for (PaperCard card : fullCardDb) {
+ this.mtgUniqueCardNames.add(card.getName());
+ CardEdition e = magicDb.getCardEdition(card.getEdition());
+ if (e != null) {
+ this.mtgUniqueSetCodes.add(e.getCode());
+ this.mtgUniqueSetCodes.add(e.getScryfallCode());
+ }
+ String cn = card.getCollectorNumber();
+ if (!cn.equals(IPaperCard.NO_COLLECTOR_NUMBER))
+ this.mtgUniqueCollectorNumbers.add(cn);
+ }
+ }
+
+ /* ======================================
+ * TEST SINGLE (Card and Non-Card) Parts
+ * - CardName
+ * - Card Amount
+ * - Set Code
+ * - Collector Number
+ * - Deck Name
+ * - Card Type
+ * - Deck Section
+ * ======================================
+ */
+
+ @Test void testMatchingCardName(){
+ if (mtgUniqueCardNames == null)
+ this.initMaps();
+ Pattern cardNamePattern = Pattern.compile(DeckRecognizer.REX_CARD_NAME);
+ for (String cardName : this.mtgUniqueCardNames){
+ Matcher cardNameMatcher = cardNamePattern.matcher(cardName);
+ assertTrue(cardNameMatcher.matches(), "Fail on " + cardName);
+ String matchedCardName = cardNameMatcher.group(DeckRecognizer.REGRP_CARD);
+ assertEquals(matchedCardName, cardName, "Fail on " + cardName);
+ }
+ }
+
+ @Test void testMatchingFoilCardName(){
+ String foilCardName = "Counter spell+";
+ Pattern cardNamePattern = Pattern.compile(DeckRecognizer.REX_CARD_NAME);
+ Matcher cardNameMatcher = cardNamePattern.matcher(foilCardName);
+ assertTrue(cardNameMatcher.matches(), "Fail on " + foilCardName);
+ String matchedCardName = cardNameMatcher.group(DeckRecognizer.REGRP_CARD);
+ assertEquals(matchedCardName, foilCardName, "Fail on " + foilCardName);
+ }
+
+
+ @Test void testMatchingSetCodes(){
+ if (mtgUniqueCardNames == null)
+ this.initMaps();
+ Pattern setCodePattern = Pattern.compile(DeckRecognizer.REX_SET_CODE);
+ for (String setCode : this.mtgUniqueSetCodes){
+ Matcher setCodeMatcher = setCodePattern.matcher(setCode);
+ assertTrue(setCodeMatcher.matches(), "Fail on " + setCode);
+ String matchedSetCode = setCodeMatcher.group(DeckRecognizer.REGRP_SET);
+ assertEquals(matchedSetCode, setCode, "Fail on " + setCode);
+ }
+ }
+
+ @Test void testMatchingCollectorNumber(){
+ if (mtgUniqueCardNames == null)
+ this.initMaps();
+ Pattern collNumberPattern = Pattern.compile(DeckRecognizer.REX_COLL_NUMBER);
+ for (String collectorNumber : this.mtgUniqueCollectorNumbers){
+ Matcher collNumberMatcher = collNumberPattern.matcher(collectorNumber);
+ assertTrue(collNumberMatcher.matches());
+ String matchedCollNr = collNumberMatcher.group(DeckRecognizer.REGRP_COLLNR);
+ assertEquals(matchedCollNr, collectorNumber, "Fail on " + collectorNumber);
+ }
+ }
+
+ @Test void testCardQuantityRequest(){
+ Pattern cardCountPattern = Pattern.compile(DeckRecognizer.REX_CARD_COUNT);
+ String[] correctAmountRequests = new String[] {"0", "2", "12", "4", "8x", "12x"};
+ String[] inCorrectAmountRequests = new String[] {"-2", "-23", "NO", "133"};
+
+ for (String correctReq : correctAmountRequests) {
+ Matcher matcher = cardCountPattern.matcher(correctReq);
+ assertTrue(matcher.matches());
+ String expectedRequestAmount = correctReq;
+ if (correctReq.endsWith("x"))
+ expectedRequestAmount = correctReq.substring(0, correctReq.length()-1);
+
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), expectedRequestAmount);
+ int expectedAmount = Integer.parseInt(expectedRequestAmount);
+ assertEquals(Integer.parseInt(matcher.group(DeckRecognizer.REGRP_CARDNO)), expectedAmount);
+ }
+
+ for (String incorrectReq : inCorrectAmountRequests) {
+ Matcher matcher = cardCountPattern.matcher(incorrectReq);
+ assertFalse(matcher.matches());
+ }
+ }
+
+ @Test void testMatchDeckName(){
+ Pattern deckNamePattern = DeckRecognizer.DECK_NAME_PATTERN;
+
+ String matchingDeckName = "Deck Name: Red Green Aggro";
+ Matcher deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ matchingDeckName = "Name: Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ matchingDeckName = "Name:Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ matchingDeckName = "Name: Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ matchingDeckName = "Deck:Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ // Case Insensitive
+ matchingDeckName = "deck name: Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertTrue(deckNameMatcher.matches());
+ assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro");
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
+
+ // Failing Cases
+ matchingDeckName = ":Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertFalse(deckNameMatcher.matches());
+ assertFalse(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "");
+
+ matchingDeckName = "Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertFalse(deckNameMatcher.matches());
+ assertFalse(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "");
+
+ matchingDeckName = "Name-Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertFalse(deckNameMatcher.matches());
+ assertFalse(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "");
+
+ matchingDeckName = "Deck.Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertFalse(deckNameMatcher.matches());
+ assertFalse(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "");
+
+ matchingDeckName = ":Red Green Aggro";
+ deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
+ assertFalse(deckNameMatcher.matches());
+ assertFalse(DeckRecognizer.isDeckName(matchingDeckName));
+ assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "");
+ }
+
+ @Test void testMatchDeckSectionNames(){
+ String[] dckSections = new String[] {"Main", "main", "Mainboard",
+ "Sideboard", "Side", "Schemes", "Avatar", "avatar", "Commander", "Conspiracy", "card"};
+ for (String section : dckSections)
+ assertTrue(DeckRecognizer.isDeckSectionName(section), "Unrecognised Deck Section: " + section);
+
+ assertFalse(DeckRecognizer.isDeckSectionName("Avatar of Hope"));
+ assertFalse(DeckRecognizer.isDeckSectionName("Conspiracy Theory"));
+ assertFalse(DeckRecognizer.isDeckSectionName("Planeswalker's Mischief"));
+ assertFalse(DeckRecognizer.isDeckSectionName("Demon of Dark Schemes"));
+
+ String[] deckSectionEntriesFoundInMDExportFromTappedOut = new String[]{
+ "## Sideboard (15)", "## Mainboard (86)"};
+ for (String entry: deckSectionEntriesFoundInMDExportFromTappedOut)
+ assertTrue(DeckRecognizer.isDeckSectionName(entry), "Fail on "+entry);
+
+ String[] deckSectionEntriesFoundInDCKFormat = new String[] {"[Main]", "[Sideboard]"};
+ for (String entry: deckSectionEntriesFoundInDCKFormat)
+ assertTrue(DeckRecognizer.isDeckSectionName(entry), "Fail on "+entry);
+ }
+
+ @Test void testMatchCardTypes(){
+ String[] cardTypes = new String[] {"Spell", "instants", "Sorceries", "Sorcery",
+ "Artifact", "creatures", "land"};
+ for (String cardType : cardTypes)
+ assertTrue(DeckRecognizer.isCardType(cardType), "Fail on "+cardType);
+
+ String[] cardTypesEntriesFoundInMDExportFromTappedOut = new String[]{
+ "### Instant (14)", "### Sorcery (9)", "### Artifact (14)",
+ "### Creature (2)", "### Land (21)", };
+ for (String entry: cardTypesEntriesFoundInMDExportFromTappedOut)
+ assertTrue(DeckRecognizer.isCardType(entry), "Fail on "+entry);
+
+ String[] cardTypesInDecFormat = new String[] {
+ "//Lands", "//Artifacts", "//Enchantments",
+ "//Instants", "//Sorceries", "//Planeswalkers",
+ "//Creatures"};
+ for (String entry : cardTypesInDecFormat)
+ assertTrue(DeckRecognizer.isCardType(entry), "Fail on " + entry);
+ }
+
+ /*=============================
+ * TEST RECOGNISE NON-CARD LINES
+ * =============================
+ */
+ @Test void testMatchNonCardLine(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ // Test Token Types
+ Token t = recognizer.recogniseNonCardToken("//Lands");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.CARD_TYPE);
+ assertEquals(t.getText(), "Lands");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("[Main]");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.DECK_SECTION_NAME);
+ assertEquals(t.getText(), "Main");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("## Mainboard (75)");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.DECK_SECTION_NAME);
+ assertEquals(t.getText(), "Main");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("### Artifact (3)");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.CARD_TYPE);
+ assertEquals(t.getText(), "Artifact");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("Enchantments");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.CARD_TYPE);
+ assertEquals(t.getText(), "Enchantments");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("//Name: Artifacts from DeckStats.net");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.DECK_NAME);
+ assertEquals(t.getText(), "Artifacts from DeckStats");
+ assertEquals(t.getNumber(), 0);
+
+ t = recognizer.recogniseNonCardToken("Name: OLDSCHOOL 93-94 Red Green Aggro by Zombies with JetPack");
+ assertNotNull(t);
+ assertEquals(t.getType(), TokenType.DECK_NAME);
+ assertEquals(t.getText(), "OLDSCHOOL 93-94 Red Green Aggro by Zombies with JetPack");
+ assertEquals(t.getNumber(), 0);
+ }
+
+ /*=============================
+ * TEST RECOGNISE CARD LINES
+ * =============================
+ */
+
+ // === Card-Set Request
+ @Test void testValidMatchCardSetLine(){
+ String validRequest = "1 Power Sink TMP";
+ Matcher matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink TMP"; // no count
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink tmp"; // set cde in lower case
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "tmp");
+
+ validRequest = "10 Power Sink TMP"; // double digits
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "10");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // x multiplier symbol in card count
+ validRequest = "12x Power Sink TMP";
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "12");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // -- Set code with delimiters
+ validRequest = "Power Sink|TMP"; // pipe delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink (TMP)"; // MTGArena-like
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // requires trim
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink|TMP"; // pipe delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink [TMP]"; // .Dec-like delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink|TMP"; // pipe delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink {TMP}"; // Alternative braces delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // -- also valid = delimiters can also be partial
+
+ validRequest = "Power Sink (TMP"; // pipe delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink(TMP)"; // No space
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink|TMP"; // pipe delimiter
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink [TMP)"; // Mixed
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "Power Sink TMP]"; // last bracket to be stripped, but using space from card name
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ }
+
+ @Test void testInvalidMatchCardSetLine(){
+ // == Invalid Cases for this REGEX
+ // Remeber: this rex *always* expects a Set Code!
+
+ String invalidRequest = "Power Sink "; // mind last space
+ Matcher matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power");
+ assertNotNull(matcher.group(DeckRecognizer.REGRP_SET));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Sink");
+
+ invalidRequest = "22 Power Sink"; // mind last space
+ matcher = DeckRecognizer.CARD_SET_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "22");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power");
+ assertNotNull(matcher.group(DeckRecognizer.REGRP_SET));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Sink");
+ }
+
+ // === Set-Card Request
+ @Test void testValidMatchSetCardLine(){
+ String validRequest = "1 TMP Power Sink";
+ Matcher matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "TMP Power Sink"; // no count
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "tmp Power Sink"; // set code in lowercase
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "tmp");
+
+ validRequest = "10 TMP Power Sink"; // double digits
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "10");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // x multiplier symbol in card count
+ validRequest = "12x TMP Power Sink";
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "12");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // -- separators
+ validRequest = "TMP|Power Sink"; // pipe-after set code
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "(TMP)Power Sink"; // parenthesis
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "[TMP]Power Sink"; // brackets
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "{TMP}Power Sink"; // braces
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "TMP Power Sink"; // lots of spaces
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "(TMP|Power Sink"; // mixed
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "(TMP]Power Sink"; // mixed 2
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "TMP]Power Sink"; // partial
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "TMP] Power Sink"; // partial with spaces
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ validRequest = "(TMP Power Sink"; // only left-handside
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ }
+
+ @Test void testInvalidMatchSetCardLine(){
+ // == Invalid Cases for this REGEX
+ // Remeber: this rex *always* expects a Set Code!
+
+ String invalidRequest = "Power Sink"; // mind last space
+ Matcher matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Sink");
+ assertNotNull(matcher.group(DeckRecognizer.REGRP_SET));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Power");
+
+ invalidRequest = "22 Power Sink"; // mind last space
+ matcher = DeckRecognizer.SET_CARD_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "22");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Sink");
+ assertNotNull(matcher.group(DeckRecognizer.REGRP_SET));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Power");
+ }
+
+ //=== Card-Set-CollectorNumber request
+ @Test void testMatchFullCardSetRequest(){
+ String validRequest = "1 Power Sink TMP 78";
+ Matcher matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "1 Power Sink|TMP 78";
+ matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "1 Power Sink (TMP) 78"; // MTG Arena alike
+ matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "Power Sink (TMP) 78"; // MTG Arena alike
+ matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+ }
+
+ @Test void testInvalidMatchFullCardSetRequest(){
+ // NOTE: this will be matcher by another pattern
+ String invalidRequest = "1 Power Sink TMP"; // missing collector number
+ Matcher matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "TMP");
+
+ // Note: this will be matched by another pattern
+ invalidRequest = "1 TMP Power Sink";
+ matcher = DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(invalidRequest);
+ assertFalse(matcher.matches());
+ }
+
+ // === Set-Card-CollectorNumber Request
+ @Test void testMatchFullSetCardRequest(){
+ String validRequest = "1 TMP Power Sink 78";
+ Matcher matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "4x TMP Power Sink 78";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "TMP Power Sink 78";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "(TMP) Power Sink 78";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "TMP|Power Sink 78";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "[TMP] Power Sink 78";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "TMP| Power Sink 78"; // extra space
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "tmp Power Sink 78"; // set code lowercase
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "tmp");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ validRequest = "(TMP} Power Sink 78"; // mixed delimiters - still valid :D
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+ }
+
+ @Test void testInvalidMatchFullSetCardRequest(){
+ System.out.println(DeckRecognizer.REX_FULL_REQUEST_SET_CARD);
+ // NOTE: this will be matcher by another pattern
+ String invalidRequest = "1 Power Sink TMP"; // missing collector number
+ Matcher matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "1");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "Power");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "TMP");
+
+ // Note: this will be matched by another pattern
+ invalidRequest = "1 TMP Power Sink";
+ matcher = DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(invalidRequest);
+ assertFalse(matcher.matches());
+ }
+
+ @Test void testCrossRexForDifferentLineRequests(){
+ String cardRequest = "4x Power Sink TMP 78";
+ assertTrue(DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.CARD_SET_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_PATTERN.matcher(cardRequest).matches());
+
+ cardRequest = "4x TMP Power Sink 78";
+ assertTrue(DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.CARD_SET_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_PATTERN.matcher(cardRequest).matches());
+
+ cardRequest = "4x Power Sink TMP";
+ assertTrue(DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.CARD_SET_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_PATTERN.matcher(cardRequest).matches());
+
+ cardRequest = "4x TMP Power Sink";
+ // ONLY CASES IN WHICH THIS WILL FAIL
+ assertFalse(DeckRecognizer.CARD_SET_COLLNO_PATTERN.matcher(cardRequest).matches());
+ assertFalse(DeckRecognizer.SET_CARD_COLLNO_PATTERN.matcher(cardRequest).matches());
+
+ assertTrue(DeckRecognizer.CARD_SET_PATTERN.matcher(cardRequest).matches());
+ assertTrue(DeckRecognizer.SET_CARD_PATTERN.matcher(cardRequest).matches());
+ }
+
+ // === Card Only Request
+
+ @Test void testMatchCardOnlyRequest(){
+ String validRequest = "4x Power Sink";
+ Matcher matcher = DeckRecognizer.CARD_ONLY_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+
+ validRequest = "Power Sink";
+ matcher = DeckRecognizer.CARD_ONLY_PATTERN.matcher(validRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink");
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+
+ String invalidRequest = "";
+ matcher = DeckRecognizer.CARD_ONLY_PATTERN.matcher(invalidRequest);
+ assertFalse(matcher.matches());
+
+ invalidRequest = "TMP"; // set code (there is no way for this to not match)
+ matcher = DeckRecognizer.CARD_ONLY_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "TMP");
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+
+ invalidRequest = "78"; // collector number (there is no way for this to not match)
+ matcher = DeckRecognizer.CARD_ONLY_PATTERN.matcher(invalidRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "78");
+ assertNull(matcher.group(DeckRecognizer.REGRP_CARDNO));
+ }
+
+ @Test void testMatchFoilCardRequest(){
+ // card-set-collnr
+ String foilRequest = "4x Power Sink+ (TMP) 78";
+ Pattern target = DeckRecognizer.CARD_SET_COLLNO_PATTERN;
+ Matcher matcher = target.matcher(foilRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+ "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ // Set-card-collnr
+ foilRequest = "4x (TMP) Power Sink+ 78";
+ target = DeckRecognizer.SET_CARD_COLLNO_PATTERN;
+ matcher = target.matcher(foilRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "78");
+
+ // set-card
+ foilRequest = "4x (TMP) Power Sink+";
+ target = DeckRecognizer.SET_CARD_PATTERN;
+ matcher = target.matcher(foilRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // card-set
+ foilRequest = "4x Power Sink+ (TMP)";
+ target = DeckRecognizer.CARD_SET_PATTERN;
+ matcher = target.matcher(foilRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+ "); // TRIM
+ assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "TMP");
+
+ // card-only
+ foilRequest = "4x Power Sink+";
+ target = DeckRecognizer.CARD_ONLY_PATTERN;
+ matcher = target.matcher(foilRequest);
+ assertTrue(matcher.matches());
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+ assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+");
+ }
+
+ @Test void testRecogniseCardToken(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "4x Power Sink+ (TMP) 78";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertTrue(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "4x Power Sink TMP 78";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "4x TMP Power Sink 78";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "4x TMP Power Sink";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "4x Power Sink TMP";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "Power Sink TMP";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ lineRequest = "[TMP] Power Sink";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+
+ // Relax Set Preference
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+
+ lineRequest = "4x Power Sink";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "VMA");
+
+ lineRequest = "Power Sink";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertFalse(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "VMA");
+ }
+
+ @Test void testRecognisingCardFromSetUsingAlternateCode(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "4x Power Sink+ TE 78";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 4);
+ assertEquals(tokenCard.getName(), "Power Sink");
+ assertTrue(tokenCard.isFoil());
+ assertEquals(tokenCard.getEdition(), "TMP");
+ assertEquals(tokenCard.getCollectorNumber(), "78");
+ }
+
+ @Test void testSingleWordCardNameMatchesCorrectly(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "2x Counterspell ICE";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 2);
+ assertEquals(tokenCard.getName(), "Counterspell");
+ assertEquals(tokenCard.getEdition(), "ICE");
+
+ // Remove Set code
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+ lineRequest = "2x Counterspell";
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 2);
+ assertEquals(tokenCard.getName(), "Counterspell");
+ assertEquals(tokenCard.getEdition(), "MH2");
+
+ }
+
+ @Test void testPassingInArtIndexRatherThanCollectorNumber(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "20x Mountain MIR 3";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 20);
+ assertEquals(tokenCard.getName(), "Mountain");
+ assertEquals(tokenCard.getEdition(), "MIR");
+ assertEquals(tokenCard.getArtIndex(), 3);
+ assertEquals(tokenCard.getCollectorNumber(), "345");
+ }
+
+ @Test void testCollectorNumberIsNotConfusedAsArtIndexInstead(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "2x Auspicious Ancestor MIR 3";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard tokenCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 2);
+ assertEquals(tokenCard.getName(), "Auspicious Ancestor");
+ assertEquals(tokenCard.getEdition(), "MIR");
+ assertEquals(tokenCard.getArtIndex(), 1);
+ assertEquals(tokenCard.getCollectorNumber(), "3");
+ }
+
+ @Test void testRequestingCardWithWrongCollectorNumberReturnsUnknownCard(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "2x Power Sink MIR 3";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.UNKNOWN_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+
+ @Test void testRequestingCardFromTheWrongSetReturnsUnknownCard(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "2x Counterspell FEM";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.UNKNOWN_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+
+ @Test void testRequestingCardFromNonExistingSetReturnsUnknownCard(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "2x Counterspell BOU";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.UNKNOWN_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+
+ @Test void testRequestingCardWithRestrictionsOnSetsFromGameFormat(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+ // Setting Fantasy Constructed Game Format: Urza's Block Format
+ List allowedSets = Arrays.asList("USG", "ULG", "UDS", "PUDS", "PULG", "PUSG");
+ recognizer.setGameFormatConstraint(allowedSets);
+
+ String lineRequest = "2x Counterspell ICE";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+
+ lineRequest = "2x Counterspell"; // It does not exist any Counterspell in Urza's block
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+
+ @Test void testRequestingCardWithRestrictionsOnDeckFormat(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ String lineRequest = "Ancestral Recall";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard ancestralCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(ancestralCard.getName(), "Ancestral Recall");
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+ assertEquals(ancestralCard.getEdition(), "VMA");
+
+ recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+
+ @Test void testRequestingCardWithReleaseDateConstraints(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+ recognizer.setDateConstraint(2002, 1);
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+
+ String lineRequest = "Ancestral Recall";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard ancestralCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(ancestralCard.getName(), "Ancestral Recall");
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+ assertEquals(ancestralCard.getEdition(), "2ED");
+
+ // Counterspell editions
+ lineRequest = "Counterspell";
+ recognizer.setDateConstraint(1999, 10);
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard counterSpellCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(counterSpellCard.getName(), "Counterspell");
+ assertEquals(counterSpellCard.getEdition(), "MMQ");
+ }
+
+ @Test void testInvalidCardRequestWhenReleaseDAteConstraintsAreUp(){
+ StaticData magicDb = FModel.getMagicDb();
+ CardDb db = magicDb.getCommonCards();
+ CardDb altDb = magicDb.getVariantCards();
+ DeckRecognizer recognizer = new DeckRecognizer(db, altDb);
+
+ assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
+
+ // First run without constraint to check that it's a valid request
+ String lineRequest = "Counterspell|MH2";
+ Token cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+ assertNotNull(cardToken.getCard());
+ PaperCard counterSpellCard = cardToken.getCard();
+ assertEquals(cardToken.getNumber(), 1);
+ assertEquals(counterSpellCard.getName(), "Counterspell");
+ assertEquals(counterSpellCard.getEdition(), "MH2");
+
+ recognizer.setDateConstraint(1999, 10);
+ cardToken = recognizer.recogniseCardToken(lineRequest);
+ assertNotNull(cardToken);
+ assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST);
+ assertNull(cardToken.getCard());
+ }
+}