From 2297f34ce238dc09c6685eb3963b4df04b19628d Mon Sep 17 00:00:00 2001
From: leriomaggio
[^a-zA-Z]*)\\s*(?(\\w+[:]\\s*))?(?<%s>[a-zA-Z]+)(? [^a-zA-Z]*)?$", REGRP_NOCARD); + public static final String REGRP_TOKEN = "token"; + public static final String REX_NOCARD = String.format("^(? [^a-zA-Z]*)\\s*(?(\\w+[:]\\s*))?(?<%s>[a-zA-Z]+)(? [^a-zA-Z]*)?$", REGRP_TOKEN); + public static final String REX_CMC = String.format("^(? [^a-zA-Z]*)\\s*(?<%s>(C(M)?C(\\s)?\\d{1,2}))(?[^\\d]*)?$", REGRP_TOKEN); + public static final String REX_RARITY = String.format("^(? [^a-zA-Z]*)\\s*(?<%s>((un)?common|(mythic)?\\s*(rare)?|land))(?[^a-zA-Z]*)?$", REGRP_TOKEN); + public static final String REX_COLOUR = String.format("^(? [^a-zA-Z]*)\\s*(?<%s>(white|blue|black|red|green|colorless))(?[^a-zA-Z]*)?$", REGRP_TOKEN); public static final Pattern NONCARD_PATTERN = Pattern.compile(REX_NOCARD, Pattern.CASE_INSENSITIVE); + public static final Pattern CMC_PATTERN = Pattern.compile(REX_CMC, Pattern.CASE_INSENSITIVE); + public static final Pattern CARD_RARITY_PATTERN = Pattern.compile(REX_RARITY, Pattern.CASE_INSENSITIVE); + public static final Pattern MANA_PATTERN = Pattern.compile(REX_COLOUR, Pattern.CASE_INSENSITIVE); public static final String REGRP_SET = "setcode"; public static final String REGRP_COLLNR = "collnr"; @@ -170,61 +172,69 @@ public class DeckRecognizer { 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>\\*?[0-9A-Z]+\\S?[A-Z]*)", REGRP_COLLNR); public static final String REX_CARD_COUNT = String.format("(?<%s>[\\d]{1,2})(? x)?", REGRP_CARDNO); - // EXTRA public static final String REGRP_FOIL_GFISH = "foil"; private static final String REX_FOIL_MTGGOLDFISH = String.format( "(?<%s>\\(F\\))?", REGRP_FOIL_GFISH); - // 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|\\)|\\]|\\})?\\s*%s", REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE, REX_FOIL_MTGGOLDFISH); 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*%s\\s*", REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME, REX_FOIL_MTGGOLDFISH); 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\\s*%s\\s*", REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE, REX_COLL_NUMBER, REX_FOIL_MTGGOLDFISH); 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\\s*%s$", REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME, REX_COLL_NUMBER, REX_FOIL_MTGGOLDFISH); public static final Pattern SET_CARD_COLLNO_PATTERN = Pattern.compile(REX_FULL_REQUEST_SET_CARD); - // 5. (MTGGoldfish mostly) (Amount?, Card Name, , Set) public static final String REX_FULL_REQUEST_CARD_COLLNO_SET = String.format( "^(%s\\s)?\\s*%s\\s+(\\<%s\\>)\\s*(\\(|\\[|\\{)?%s(\\s+|\\)|\\]|\\}|\\|)\\s*%s$", REX_CARD_COUNT, REX_CARD_NAME, REX_COLL_NUMBER, REX_SET_CODE, REX_FOIL_MTGGOLDFISH); public static final Pattern CARD_COLLNO_SET_PATTERN = Pattern.compile(REX_FULL_REQUEST_CARD_COLLNO_SET); - // 6. XMage format (Amount?, [Set:Collector Number] Card Name) public static final String REX_FULL_REQUEST_XMAGE = String.format( "^(%s\\s)?\\s*(\\[)?%s:%s(\\])\\s+%s\\s*%s$", REX_CARD_COUNT, REX_SET_CODE, REX_COLL_NUMBER, REX_CARD_NAME, REX_FOIL_MTGGOLDFISH); public static final Pattern SET_COLLNO_CARD_XMAGE_PATTERN = Pattern.compile(REX_FULL_REQUEST_XMAGE); - // 7. Card-Only Request (Amount?) public static final String REX_CARDONLY = String.format( "(%s\\s)?\\s*%s\\s*%s", REX_CARD_COUNT, REX_CARD_NAME, REX_FOIL_MTGGOLDFISH); public static final Pattern CARD_ONLY_PATTERN = Pattern.compile(REX_CARDONLY); - // CoreTypes (to recognise Tokens of type CardType - private static CharSequence[] CARD_TYPES = allCardTypes(); - + private static final CharSequence[] CARD_TYPES = allCardTypes(); private static final CharSequence[] DECK_SECTION_NAMES = {"avatar", "commander", "schemes", "conspiracy", "planes", "deck", "main", "card", "mainboard", "side", "sideboard"}; + 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()]); + } + private final CardDb db; private final CardDb altDb; private Date releaseDateConstraint = null; @@ -244,32 +254,33 @@ public class DeckRecognizer { return null; final char smartQuote = (char) 8217; - String line = rawLine.trim().replace(smartQuote, '\''); + String refLine = rawLine.trim().replace(smartQuote, '\''); // Remove any link (e.g. Markdown Export format from TappedOut) - line = purgeAllLinks(line); + refLine = purgeAllLinks(refLine); - if (StringUtils.startsWith(line, LINE_COMMENT_DELIMITER_OR_MD_HEADER)) - line = line.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, ""); + String line; + if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER)) + line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, ""); + else + line = refLine.trim(); // Remove any trailing formatting // Some websites export split card names with a single slash. Replace with double slash. line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // "); - line = line.trim(); // Remove any trailing formattings - if (StringUtils.startsWith(line, DOUBLE_SLASH)) - line = line.substring(2); // In this case, we are sure to support split cards - - if (StringUtils.startsWith(line, ASTERISK)) + if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export) line = line.substring(2); // In some format, cards in the Sideboard have an SB: prefix. // We won't support that as it is, due to how the recognition process works, so a section must be // specified (e.g. Sideboard or Side) to allow that those cards will be imported in sideboard. if (StringUtils.startsWith(line.trim(), "SB:")) - line = StringUtils.replace(line, "SB:", "").trim(); + //refLine = StringUtils.replace(refLine, "SB:", "").trim(); + return new Token(TokenType.COMMENT, 0, line); Token result = recogniseCardToken(line); if (result == null) result = recogniseNonCardToken(line); - return result != null ? result : new Token(TokenType.UNKNOWN_TEXT, 0, line); + return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) || StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ? + new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine); } public static String purgeAllLinks(String line){ @@ -288,9 +299,6 @@ public class DeckRecognizer { 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 matcher : cardMatchers) { String cardName = getRexGroup(matcher, REGRP_CARD); @@ -313,7 +321,7 @@ public class DeckRecognizer { int artIndex; try { artIndex = Integer.parseInt(collectorNumber); - } catch (NumberFormatException ex){ + } catch (NumberFormatException ex) { artIndex = IPaperCard.NO_ART_INDEX; } @@ -344,19 +352,31 @@ public class DeckRecognizer { } // 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 - // unless it is illegal for current format. - // In that case, an illegalCard token will be returned! - PaperCard pc = this.getCardFromSupportedEditions(cr.cardName, cr.isFoil); - if (pc != null){ - if (isIllegalCardInDeckFormat(pc)) + // unless it is illegal for current format or invalid with selected date. + PaperCard pc = null; + if (hasGameFormatConstraints()) { + Predicate filter = (Predicate ) this.db.isLegal(this.allowedSetCodes); + pc = this.getCardFromSupportedEditions(cr.cardName, cr.isFoil, filter); + } + if (pc == null) + pc = this.getCardFromSupportedEditions(cr.cardName, cr.isFoil, null); + + if (pc != null) { + if (isIllegalSetInGameFormat(pc.getEdition()) || isIllegalCardInDeckFormat(pc)) return Token.IllegalCard(pc.getName(), pc.getEdition(), cardCount); + CardEdition edition = StaticData.instance().getCardEdition(pc.getEdition()); + if (isNotCompliantWithReleaseDateRestrictions(edition)) + return Token.InvalidCard(pc.getName(), pc.getEdition(), cardCount); return Token.KnownCard(pc, cardCount); } - return Token.IllegalCard(cardName, "", cardCount); } return uknonwnCardToken; // either null or unknown card } + private boolean hasGameFormatConstraints() { + return this.allowedSetCodes != null && this.allowedSetCodes.size() > 0; + } + private String getRexGroup(Matcher matcher, String groupName){ String rexGroup; try{ @@ -368,7 +388,7 @@ public class DeckRecognizer { } private boolean isIllegalCardInDeckFormat(PaperCard pc) { - return this.deckFormat != null && !deckFormat.isLegalCard(pc); + return this.deckFormat != null && !this.deckFormat.isLegalCard(pc); } private boolean isIllegalSetInGameFormat(String setCode) { @@ -438,79 +458,119 @@ public class DeckRecognizer { return result; } - private PaperCard getCardFromSupportedEditions(final String cardName, boolean isFoil){ - Predicate filter = null; - if (this.allowedSetCodes != null && this.allowedSetCodes.size() > 0) - filter = (Predicate ) this.db.isLegal(this.allowedSetCodes); + private PaperCard getCardFromSupportedEditions(final String cardName, boolean isFoil, + Predicate filter){ String reqInfo = CardDb.CardRequest.compose(cardName, isFoil); + CardDb targetDb = this.db.contains(cardName) ? this.db : this.altDb; PaperCard result; - if (this.releaseDateConstraint != null){ - result = this.db.getCardFromEditionsReleasedBefore(reqInfo, + if (this.releaseDateConstraint != null) { + result = targetDb.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); + result = targetDb.getCardFromEditions(reqInfo, filter); } + else + result = targetDb.getCardFromEditions(reqInfo, filter); return result; } public Token recogniseNonCardToken(final String text) { if (isDeckSectionName(text)) { - String tokenText = getNonCardTokenText(text.toLowerCase().trim()); + String tokenText = nonCardTokenMatch(text); return Token.DeckSection(tokenText); } - if (isCardType(text)) { - String tokenText = getNonCardTokenText(text); + if (isCardRarity(text)){ + String tokenText = cardRarityTokenMatch(text); + return new Token(TokenType.CARD_RARITY, tokenText); + } + if (isCardCMC(text)){ + String tokenText = cardCMCTokenMatch(text); + return new Token(TokenType.CARD_CMC, tokenText); + } + if (isCardType(text)){ + String tokenText = nonCardTokenMatch(text); return new Token(TokenType.CARD_TYPE, tokenText); } + if(isManaToken(text)){ + String tokenText = manaTokenMatch(text); + return new Token(TokenType.MANA_COLOUR, tokenText); + } if (isDeckName(text)) { - String deckName = getDeckName(text); + String deckName = deckNameMatch(text); 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 !-) + /* ----------------------------------------------------------------------------- + Note: Card types, CMC, and Rarity Tokens are **only** used for style formatting + in the Import Editor. This won't affect the import process in any way. + The use of this token has been borrowed by Deckstats.net format export. + ----------------------------------------------------------------------------- */ public static boolean isCardType(final String lineAsIs) { - if (lineAsIs == null) + String nonCardToken = nonCardTokenMatch(lineAsIs); + if (nonCardToken == null) return false; - String line = lineAsIs.toLowerCase().trim(); + return StringUtils.containsAny(nonCardToken.toLowerCase(), CARD_TYPES); + } + + public static boolean isCardRarity(final String lineAsIs){ + return cardRarityTokenMatch(lineAsIs) != null; + } + + public static boolean isCardCMC(final String lineAsIs) { + return cardCMCTokenMatch(lineAsIs) != null; + } + + public static boolean isManaToken(final String lineAsIs) { + return manaTokenMatch(lineAsIs) != null; + } + + public static boolean isDeckSectionName(final String lineAsIs) { + String nonCardToken = nonCardTokenMatch(lineAsIs); + if (nonCardToken == null) + return false; + return StringUtils.equalsAnyIgnoreCase(nonCardToken, DECK_SECTION_NAMES); + } + + private static String nonCardTokenMatch(final String lineAsIs){ + if (lineAsIs == null) + return null; + String line = lineAsIs.trim(); Matcher noncardMatcher = NONCARD_PATTERN.matcher(line); if (!noncardMatcher.matches()) - return false; - String nonCardToken = noncardMatcher.group(REGRP_NOCARD); - return StringUtils.containsAny(nonCardToken, CARD_TYPES); + return null; + return noncardMatcher.group(REGRP_TOKEN); + } + + private static String cardRarityTokenMatch(final String lineAsIs){ + if (lineAsIs == null) + return null; + String line = lineAsIs.trim(); + Matcher cardRarityMatcher = CARD_RARITY_PATTERN.matcher(line); + if (!cardRarityMatcher.matches()) + return null; + return cardRarityMatcher.group(REGRP_TOKEN); + } + + private static String cardCMCTokenMatch(final String lineAsIs){ + if (lineAsIs == null) + return null; + String line = lineAsIs.trim(); + Matcher cardCMCmatcher = CMC_PATTERN.matcher(line); + if (!cardCMCmatcher.matches()) + return null; + return cardCMCmatcher.group(REGRP_TOKEN); + } + + private static String manaTokenMatch(final String lineAsIs){ + if (lineAsIs == null) + return null; + String line = lineAsIs.trim(); + Matcher manaMatcher = MANA_PATTERN.matcher(line); + if (!manaMatcher.matches()) + return null; + return manaMatcher.group(REGRP_TOKEN); } public static boolean isDeckName(final String lineAsIs) { @@ -518,11 +578,10 @@ public class DeckRecognizer { return false; final String line = lineAsIs.trim(); final Matcher deckNameMatcher = DECK_NAME_PATTERN.matcher(line); - boolean matches = deckNameMatcher.matches(); - return matches; + return deckNameMatcher.matches(); } - public static String getDeckName(final String text) { + public static String deckNameMatch(final String text) { if (text == null) return ""; String line = text.trim(); @@ -532,17 +591,6 @@ public class DeckRecognizer { return ""; } - public static boolean isDeckSectionName(final String text) { - if (text == null) - return false; - String line = text.toLowerCase().trim(); - 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 year, int month) { Calendar ca = Calendar.getInstance(); ca.set(year, month, 1); diff --git a/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java b/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java index 820f6954b5a..adc6a8a0177 100644 --- a/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java +++ b/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java @@ -130,35 +130,35 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertTrue(deckNameMatcher.matches()); assertTrue(DeckRecognizer.isDeckName(matchingDeckName)); assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro"); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro"); + assertEquals(DeckRecognizer.deckNameMatch(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"); + assertEquals(DeckRecognizer.deckNameMatch(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"); + assertEquals(DeckRecognizer.deckNameMatch(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"); + assertEquals(DeckRecognizer.deckNameMatch(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"); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), "Red Green Aggro"); // Case Insensitive matchingDeckName = "deck: Red Green Aggro"; @@ -166,7 +166,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertTrue(deckNameMatcher.matches()); assertTrue(DeckRecognizer.isDeckName(matchingDeckName)); assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Red Green Aggro"); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro"); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), "Red Green Aggro"); // Forge deck format matchingDeckName = "Name=Sliver Overlord (Commander)"; @@ -174,38 +174,38 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertTrue(deckNameMatcher.matches()); assertTrue(DeckRecognizer.isDeckName(matchingDeckName)); assertEquals(deckNameMatcher.group(DeckRecognizer.REGRP_DECKNAME), "Sliver Overlord (Commander)"); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Sliver Overlord (Commander)"); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), "Sliver Overlord (Commander)"); // Failing Cases matchingDeckName = ":Red Green Aggro"; deckNameMatcher = deckNamePattern.matcher(matchingDeckName); assertFalse(deckNameMatcher.matches()); assertFalse(DeckRecognizer.isDeckName(matchingDeckName)); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), ""); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), ""); matchingDeckName = "Red Green Aggro"; deckNameMatcher = deckNamePattern.matcher(matchingDeckName); assertFalse(deckNameMatcher.matches()); assertFalse(DeckRecognizer.isDeckName(matchingDeckName)); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), ""); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), ""); matchingDeckName = "Name-Red Green Aggro"; deckNameMatcher = deckNamePattern.matcher(matchingDeckName); assertFalse(deckNameMatcher.matches()); assertFalse(DeckRecognizer.isDeckName(matchingDeckName)); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), ""); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), ""); matchingDeckName = "Deck.Red Green Aggro"; deckNameMatcher = deckNamePattern.matcher(matchingDeckName); assertFalse(deckNameMatcher.matches()); assertFalse(DeckRecognizer.isDeckName(matchingDeckName)); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), ""); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), ""); matchingDeckName = ":Red Green Aggro"; deckNameMatcher = deckNamePattern.matcher(matchingDeckName); assertFalse(deckNameMatcher.matches()); assertFalse(DeckRecognizer.isDeckName(matchingDeckName)); - assertEquals(DeckRecognizer.getDeckName(matchingDeckName), ""); + assertEquals(DeckRecognizer.deckNameMatch(matchingDeckName), ""); } @Test void testMatchDeckSectionNames(){ @@ -249,6 +249,43 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertTrue(DeckRecognizer.isCardType(entry), "Fail on " + entry); } + @Test void testOnlyContainingCardTypeWontMatchCardTypeToken(){ + String[] nonCardTypes = new String[] {"Spell collection", "instants list", + "creatures elves", "land list"}; + for (String nonCardTypeTokens : nonCardTypes) + assertFalse(DeckRecognizer.isCardType(nonCardTypeTokens), "Fail on "+nonCardTypeTokens); + } + + @Test void testRarityTypeTokenMatch(){ + String[] rarityTokens = new String[] {"Common", "uncommon", "rare", "mythic", "mythic rare", "land"}; + for (String line : rarityTokens) + assertTrue(DeckRecognizer.isCardRarity(line), "Fail on "+line); + + String[] nonRarityTokens = new String[] {"Common cards", "uncommon cards", "mythics", "rares", "lands"}; + for (String line : nonRarityTokens) + assertFalse(DeckRecognizer.isCardRarity(line), "Fail on "+line); + } + + @Test void testCMCTokenMatch(){ + String[] cmcTokens = new String[] {"CC0", "CMC2", "CMC11", "cc3"}; + for (String line : cmcTokens) + assertTrue(DeckRecognizer.isCardCMC(line), "Fail on "+line); + + String[] nonCMCtokens = new String[] {"cc", "CMC", "cc322", "cmc111"}; + for (String line : nonCMCtokens) + assertFalse(DeckRecognizer.isCardCMC(line), "Fail on "+line); + } + + @Test void testManaTokenMatch(){ + String[] cmcTokens = new String[] {"Blue", "red", "White", "// Black", " //Colorless----", "(green)"}; + for (String line : cmcTokens) + assertTrue(DeckRecognizer.isManaToken(line), "Fail on " + line); + + String[] nonCMCtokens = new String[] {"blues", "red more words", "mainboard"}; + for (String line : nonCMCtokens) + assertFalse(DeckRecognizer.isManaToken(line), "Fail on "+line); + } + /*============================= * TEST RECOGNISE NON-CARD LINES * ============================= @@ -266,6 +303,13 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertEquals(t.getText(), "Lands"); assertEquals(t.getNumber(), 0); + // Test Token Types + t = recognizer.recogniseNonCardToken("//Land"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.CARD_RARITY); + assertEquals(t.getText(), "Land"); + assertEquals(t.getNumber(), 0); + t = recognizer.recogniseNonCardToken("[Main]"); assertNotNull(t); assertEquals(t.getType(), TokenType.DECK_SECTION_NAME); @@ -301,6 +345,42 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertEquals(t.getType(), TokenType.DECK_NAME); assertEquals(t.getText(), "OLDSCHOOL 93-94 Red Green Aggro by Zombies with JetPack"); assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("CMC0"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.CARD_CMC); + assertEquals(t.getText(), "CMC0"); + assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("CC1"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.CARD_CMC); + assertEquals(t.getText(), "CC1"); + assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("//Common"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.CARD_RARITY); + assertEquals(t.getText(), "Common"); + assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("(mythic rare)"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.CARD_RARITY); + assertEquals(t.getText(), "mythic rare"); + assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("//Blue"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.MANA_COLOUR); + assertEquals(t.getText(), "Blue"); + assertEquals(t.getNumber(), 0); + + t = recognizer.recogniseNonCardToken("(Colorless)"); + assertNotNull(t); + assertEquals(t.getType(), TokenType.MANA_COLOUR); + assertEquals(t.getText(), "Colorless"); + assertEquals(t.getNumber(), 0); } /*============================= @@ -696,7 +776,6 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { } @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); @@ -1242,6 +1321,274 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertNull(cardToken.getCard()); } + @Test void testCardMatchWithDateANDGameFormatConstraints(){ + StaticData magicDb = FModel.getMagicDb(); + CardDb db = magicDb.getCommonCards(); + CardDb altDb = magicDb.getVariantCards(); + DeckRecognizer recognizer = new DeckRecognizer(db, altDb); + + // Baseline - no constraints + assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); + String lineRequest = "2x Lightning Dragon"; + Token cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getNumber(), 2); + PaperCard tc = cardToken.getCard(); + assertEquals(tc.getName(), "Lightning Dragon"); + assertEquals(tc.getEdition(), "VMA"); + + recognizer.setDateConstraint(2000, 0); // Jan 2000 + // Setting Fantasy Constructed Game Format: Urza's Block Format (no promo) + List allowedSets = Arrays.asList("USG", "ULG", "UDS"); + recognizer.setGameFormatConstraint(allowedSets); + + lineRequest = "2x Lightning Dragon|USG"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getNumber(), 2); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Lightning Dragon"); + assertEquals(tc.getEdition(), "USG"); + + // Relaxing Constraint on Set + lineRequest = "2x Lightning Dragon"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNotNull(cardToken.getCard()); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Lightning Dragon"); + assertEquals(tc.getEdition(), "USG"); // the latest available within set requested + + // Now setting a tighter date constraint + recognizer.setDateConstraint(1998, 0); // Jan 1998 + lineRequest = "2x Lightning Dragon|USG"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNull(cardToken.getCard()); + + lineRequest = "2x Lightning Dragon"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Lightning Dragon (USG)"); + + // Now relaxing date constraint but removing USG from allowed sets + // VMA release date: 2014-06-16 + recognizer.setDateConstraint(2015, 0); // This will match VMA + recognizer.setGameFormatConstraint(Arrays.asList("ULG", "UDS")); + + lineRequest = "2x Lightning Dragon|USG"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNull(cardToken.getCard()); + + lineRequest = "2x Lightning Dragon"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNull(cardToken.getCard()); + + // Now relaxing date constraint but removing USG from allowed sets + // VMA release date: 2014-06-16 + recognizer.setDateConstraint(2015, 0); // This will match VMA + recognizer.setGameFormatConstraint(Arrays.asList("VMA", "ULG", "UDS")); + lineRequest = "2x Lightning Dragon"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNotNull(cardToken.getCard()); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Lightning Dragon"); + assertEquals(tc.getEdition(), "VMA"); + } + + @Test void testCardMatchWithDateANDdeckFormatConstraints(){ + StaticData magicDb = FModel.getMagicDb(); + CardDb db = magicDb.getCommonCards(); + CardDb altDb = magicDb.getVariantCards(); + DeckRecognizer recognizer = new DeckRecognizer(db, altDb); + + // Baseline - no constraints + assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); + + String lineRequest = "Flash"; + Token cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getNumber(), 1); + PaperCard tc = cardToken.getCard(); + assertEquals(tc.getName(), "Flash"); + assertEquals(tc.getEdition(), "A25"); + + recognizer.setDateConstraint(2012, 0); // Jan 2012 + recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); + + lineRequest = "Flash"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Flash (6ED)"); + + lineRequest = "2x Cancel"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNotNull(cardToken.getCard()); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Cancel"); + assertEquals(tc.getEdition(), "M12"); // the latest within date constraint + + lineRequest = "2x Cancel|M21"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Cancel (M21)"); + } + + @Test void testCardMatchWithGameANDdeckFormatConstraints(){ + StaticData magicDb = FModel.getMagicDb(); + CardDb db = magicDb.getCommonCards(); + CardDb altDb = magicDb.getVariantCards(); + DeckRecognizer recognizer = new DeckRecognizer(db, altDb); + + // Baseline - no constraints + assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); + + String lineRequest = "Flash"; + Token cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getNumber(), 1); + PaperCard tc = cardToken.getCard(); + assertEquals(tc.getName(), "Flash"); + assertEquals(tc.getEdition(), "A25"); + + recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH")); + recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); + + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Flash (MIR)"); + + lineRequest = "2x Femeref Knight"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNotNull(cardToken.getCard()); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Femeref Knight"); + assertEquals(tc.getEdition(), "MIR"); + + lineRequest = "2x Incinerate"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertEquals(cardToken.getNumber(), 2); + assertNotNull(cardToken.getCard()); + tc = cardToken.getCard(); + assertEquals(tc.getName(), "Incinerate"); + assertEquals(tc.getEdition(), "MIR"); + + lineRequest = "Noble Elephant"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); // violating Deck format + assertEquals(cardToken.getNumber(), 1); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Noble Elephant (MIR)"); + + lineRequest = "Incinerate|ICE"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); // violating Game format + assertEquals(cardToken.getNumber(), 1); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Incinerate (ICE)"); + } + + @Test void testCardMatchWitDateANDgameANDdeckFormatConstraints(){ + StaticData magicDb = FModel.getMagicDb(); + CardDb db = magicDb.getCommonCards(); + CardDb altDb = magicDb.getVariantCards(); + DeckRecognizer recognizer = new DeckRecognizer(db, altDb); + + // Baseline - no constraints + assertEquals(db.getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); + + String lineRequest = "Flash"; + Token cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getNumber(), 1); + PaperCard tc = cardToken.getCard(); + assertEquals(tc.getName(), "Flash"); + assertEquals(tc.getEdition(), "A25"); + + recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH")); + recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); + recognizer.setDateConstraint(1999, 2); // March '99 + + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Flash (MIR)"); + + lineRequest = "Ardent Militia"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.ILLEGAL_CARD_REQUEST); // illegal in deck format + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Ardent Militia (WTH)"); // within set constraints + + lineRequest = "Buried Alive|UMA"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST); // illegal in game format + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Buried Alive (UMA)"); // within set constraints + + lineRequest = "Buried Alive"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST); // illegal in deck format + assertNotNull(cardToken.getCard()); + assertEquals(cardToken.getCard().getName(), "Buried Alive"); + assertEquals(cardToken.getCard().getEdition(), "WTH"); // within set constraints + + recognizer.setDateConstraint(1997, 2); // March '97 - before WTH + lineRequest = "Buried Alive"; + cardToken = recognizer.recogniseCardToken(lineRequest); + assertNotNull(cardToken); + assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST); + assertNull(cardToken.getCard()); + assertEquals(cardToken.getText(), "Buried Alive (WTH)"); + } + /*================================== * TEST RECOGNISE CARD EXTRA FORMATS * ================================= @@ -1413,4 +1760,90 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase { assertEquals(acCard.getEdition(), "LRW"); assertEquals(acCard.getCollectorNumber(), "51"); } + + /*==================================== + * TEST RECOGNISE LINES (MIXED inputs) + * =================================== + */ + @Test void testRecognizeLines(){ + StaticData magicDb = FModel.getMagicDb(); + CardDb db = magicDb.getCommonCards(); + CardDb altDb = magicDb.getVariantCards(); + DeckRecognizer recognizer = new DeckRecognizer(db, altDb); + + String lineRequest = "// MainBoard"; + Token token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.DECK_SECTION_NAME); + assertEquals(token.getText(), "Main"); + + lineRequest = "## Sideboard (15)"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.DECK_SECTION_NAME); + assertEquals(token.getText(), "Sideboard"); + + lineRequest = "Normal Text"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.UNKNOWN_TEXT); + assertEquals(token.getText(), "Normal Text"); + + lineRequest = "//Creatures"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.CARD_TYPE); + assertEquals(token.getText(), "Creatures"); + + lineRequest = "//Lands"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.CARD_TYPE); + assertEquals(token.getText(), "Lands"); + + lineRequest = "//Land"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.CARD_RARITY); + assertEquals(token.getText(), "Land"); + + lineRequest = "//Creatures with text"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.COMMENT); + assertEquals(token.getText(), "//Creatures with text"); + + lineRequest = "SB:Ancestral Recall"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.COMMENT); + assertEquals(token.getText(), "SB:Ancestral Recall"); + + lineRequest = "Ancestral Recall"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNull(token.getText()); + assertNotNull(token.getCard()); + + lineRequest = "* 4 [Counterspell](http://tappedout.nethttp://tappedout.net/mtg-card/counterspell/)"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.LEGAL_CARD_REQUEST); + assertNull(token.getText()); + assertNotNull(token.getCard()); + assertEquals(token.getNumber(), 4); + + lineRequest = "### Instant (14)"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.CARD_TYPE); + assertEquals(token.getText(), "Instant"); + + lineRequest = "### General line as comment"; + token = recognizer.recognizeLine(lineRequest); + assertNotNull(token); + assertEquals(token.getType(), TokenType.COMMENT); + assertEquals(token.getText(), "### General line as comment"); + } }