diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index 9b3104a83ab..b35e944511c 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -141,13 +141,6 @@ public class DeckRecognizer { } } -// // Let's think about it numbers in the back later -// // private static final Pattern searchNumbersInBack = -// // Pattern.compile("(.*)[^A-Za-wyz]*\\s+([\\d]{1,2})"); -// private static final Pattern SEARCH_NUMBERS_IN_FRONT = Pattern.compile("([\\d]{1,2})[^A-Za-wyz]*\\s+(.*)"); -// //private static final Pattern READ_SEPARATED_EDITION = Pattern.compile("[[\\(\\{]([a-zA-Z0-9]){1,3})[]*\\s+(.*)"); -// private static final Pattern SEARCH_SINGLE_SLASH = Pattern.compile("(?<=[^/])\\s*/\\s*(?=[^/])"); - // Utility Constants private final Pattern SEARCH_SINGLE_SLASH = Pattern.compile("(?<=[^/])\\s*/\\s*(?=[^/])"); private static final String LINE_COMMENT_DELIMITER = "#"; @@ -158,7 +151,7 @@ public class DeckRecognizer { // Core Matching Patterns (initialised in Constructor) 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*(.*)$",
+            String.format("^(//\\s*)?(?
(deck|name))(\\:|\\s)\\s*(?<%s>[a-zA-Z0-9',\\/\\-\\s]+)\\s*(.*)$",
                     REGRP_DECKNAME);
     public static final Pattern DECK_NAME_PATTERN = Pattern.compile(REX_DECK_NAME, Pattern.CASE_INSENSITIVE);
 
@@ -173,36 +166,47 @@ public class DeckRecognizer {
 
     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_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|\\)|\\]|\\})?",
-            REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE);
+            "(%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*",
-            REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME);
+            "(%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",
-            REX_CARD_COUNT, REX_CARD_NAME, REX_SET_CODE, REX_COLL_NUMBER);
+            "(%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$",
-            REX_CARD_COUNT, REX_SET_CODE, REX_CARD_NAME, REX_COLL_NUMBER);
+            "^(%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. Card-Only Request (Amount?)
+    // 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. Card-Only Request (Amount?)
     public static final String REX_CARDONLY = String.format(
-            "(%s\\s)?\\s*%s", REX_CARD_COUNT, REX_CARD_NAME);
+            "(%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);
 
 
@@ -271,6 +275,9 @@ public class DeckRecognizer {
             String ccount = getRexGroup(matcher, REGRP_CARDNO);
             String setCode = getRexGroup(matcher, REGRP_SET);
             String collNo = getRexGroup(matcher, REGRP_COLLNR);
+            String foilGr = getRexGroup(matcher, REGRP_FOIL_GFISH);
+            if (foilGr != null)
+                cr.isFoil = true;
             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;
@@ -298,7 +305,7 @@ public class DeckRecognizer {
                 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 (isIllegalSetInGameFormat(setCode) || isIllegalCardInDeckFormat(pc))
+                    if (isIllegalSetInGameFormat(edition.getCode()) || isIllegalCardInDeckFormat(pc))
                         // Mark as illegal card
                         return Token.IllegalCard(pc.getName(), pc.getEdition(), cardCount);
                     return Token.KnownCard(pc, cardCount);
@@ -351,7 +358,8 @@ public class DeckRecognizer {
         List matchers = new ArrayList<>();
         Pattern[] patternsWithCollNumber = new Pattern[] {
                 CARD_SET_COLLNO_PATTERN,
-                SET_CARD_COLLNO_PATTERN
+                SET_CARD_COLLNO_PATTERN,
+                CARD_COLLNO_SET_PATTERN
         };
         for (Pattern pattern : patternsWithCollNumber) {
             Matcher matcher = pattern.matcher(line);
@@ -375,24 +383,26 @@ public class DeckRecognizer {
     private PaperCard getCardFromSet(final String cardName, final CardEdition edition,
                                      final String collectorNumber, final int artIndex,
                                      final boolean isFoil) {
+        CardDb targetDb = this.db.contains(cardName) ? this.db : this.altDb;
         // 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);
+        PaperCard result = targetDb.getCardFromSet(cardName, edition, collectorNumber, isFoil);
+        if (result == null && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
+            if (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 = targetDb.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 = targetDb.getCardFromSet(cardName, edition, artIndex, isFoil);
+                }
+            }
+            if (result == null){
+                // Last chance, try without collector number and see if any match is found
+                result = targetDb.getCardFromSet(cardName, edition, isFoil);
             }
         }
         return result;
@@ -463,6 +473,8 @@ public class DeckRecognizer {
     // 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 !-)
     public static boolean isCardType(final String lineAsIs) {
+        if (lineAsIs == null)
+            return false;
         String line = lineAsIs.toLowerCase().trim();
         Matcher noncardMatcher = NONCARD_PATTERN.matcher(line);
         if (!noncardMatcher.matches())
@@ -472,6 +484,8 @@ public class DeckRecognizer {
     }
 
     public static boolean isDeckName(final String lineAsIs) {
+        if (lineAsIs == null)
+            return false;
         final String line = lineAsIs.trim();
         final Matcher deckNameMatcher = DECK_NAME_PATTERN.matcher(line);
         boolean matches = deckNameMatcher.matches();
@@ -479,6 +493,8 @@ public class DeckRecognizer {
     }
 
     public static String getDeckName(final String text) {
+        if (text == null)
+            return "";
         String line = text.trim();
         final Matcher deckNamePattern = DECK_NAME_PATTERN.matcher(line);
         if (deckNamePattern.matches())
@@ -487,6 +503,8 @@ public class DeckRecognizer {
     }
 
     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())
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 d163a09528f..c6c6491aeb8 100644
--- a/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java
+++ b/forge-gui-desktop/src/test/java/forge/deck/DeckRecognizerTest.java
@@ -125,7 +125,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
     @Test void testMatchDeckName(){
         Pattern deckNamePattern = DeckRecognizer.DECK_NAME_PATTERN;
 
-        String matchingDeckName = "Deck Name: Red Green Aggro";
+        String matchingDeckName = "Deck: Red Green Aggro";
         Matcher deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
         assertTrue(deckNameMatcher.matches());
         assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
@@ -161,7 +161,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
         assertEquals(DeckRecognizer.getDeckName(matchingDeckName), "Red Green Aggro");
 
         // Case Insensitive
-        matchingDeckName = "deck name: Red Green Aggro";
+        matchingDeckName = "deck: Red Green Aggro";
         deckNameMatcher = deckNamePattern.matcher(matchingDeckName);
         assertTrue(deckNameMatcher.matches());
         assertTrue(DeckRecognizer.isDeckName(matchingDeckName));
@@ -812,6 +812,69 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
         assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Power Sink+");
     }
 
+    @Test void testMatchFoilCardRequestMTGGoldfishFormat(){
+        // card-set-collnr
+        String foilRequest = "4 Aspect of Hydra [BNG] (F)";
+        Pattern target = DeckRecognizer.CARD_SET_COLLNO_PATTERN;
+        Matcher matcher = target.matcher(foilRequest);
+        assertFalse(matcher.matches());
+
+        foilRequest = "4 Aspect of Hydra [BNG] 117 (F)";
+        target = DeckRecognizer.CARD_SET_COLLNO_PATTERN;
+        matcher = target.matcher(foilRequest);
+        assertTrue(matcher.matches());
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "4");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Aspect of Hydra ");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "BNG");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "117");
+        assertNotNull(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH));
+
+        // Set-card-collnr
+        foilRequest = "4 [BNG] Aspect of Hydra (F)";
+        target = DeckRecognizer.SET_CARD_COLLNO_PATTERN;
+        matcher = target.matcher(foilRequest);
+        assertFalse(matcher.matches());
+
+        foilRequest = "4 [BNG] Aspect of Hydra 117 (F)";
+        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), "Aspect of Hydra");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "BNG");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "117");
+        assertNotNull(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH));
+
+        // set-card
+        foilRequest = "4 [BNG] Aspect of Hydra (F)";
+        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), "Aspect of Hydra ");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "BNG");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH), "(F)");
+
+        // card-set
+        foilRequest = "4 Aspect of Hydra [BNG] (F)";
+        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), "Aspect of Hydra ");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "BNG");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH), "(F)");
+
+        // card-only
+        foilRequest = "4 Aspect of Hydra (F)";
+        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), "Aspect of Hydra ");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH), "(F)");
+    }
+
     @Test void testRecogniseCardToken(){
         StaticData magicDb = FModel.getMagicDb();
         CardDb db = magicDb.getCommonCards();
@@ -1015,17 +1078,30 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
         assertEquals(tokenCard.getCollectorNumber(), "3");
     }
 
-    @Test void testRequestingCardWithWrongCollectorNumberReturnsUnknownCard(){
+    @Test void testCardRequestWithWrongCollectorNumberStillReturnsTheCardFromSetIfAny(){
         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);
+        String requestLine = "3 Jayemdae Tome (LEB) 231";  // actually found in TappedOut Deck Export
+        // NOTE: Expected Coll Nr should be 255
+        Token cardToken = recognizer.recogniseCardToken(requestLine);
+        assertNotNull(cardToken);
+        assertNotNull(cardToken.getCard());
+        assertEquals(cardToken.getNumber(), 3);
+        PaperCard card = cardToken.getCard();
+        assertEquals(card.getName(), "Jayemdae Tome");
+        assertEquals(card.getEdition(), "LEB");
+        assertEquals(card.getCollectorNumber(), "255");
+
+        // No Match - Unknown card
+        requestLine = "3 Jayemdae Tome (TMP)";  // actually found in TappedOut Deck Export
+        // NOTE: Expected Coll Nr should be 255
+        cardToken = recognizer.recogniseCardToken(requestLine);
         assertNotNull(cardToken);
-        assertEquals(cardToken.getType(), TokenType.UNKNOWN_CARD_REQUEST);
         assertNull(cardToken.getCard());
+        assertEquals(cardToken.getType(), TokenType.UNKNOWN_CARD_REQUEST);
     }
 
     @Test void testRequestingCardFromTheWrongSetReturnsUnknownCard(){
@@ -1157,4 +1233,83 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
         assertEquals(cardToken.getType(), TokenType.INVALID_CARD_REQUEST);
         assertNull(cardToken.getCard());
     }
+
+    @Test void testFoilRequestInMTGGoldfishExportFormat(){
+        String mtgGoldfishRequest = "18 Forest <254> [THB]";
+        Pattern target = DeckRecognizer.CARD_COLLNO_SET_PATTERN;
+        Matcher matcher = target.matcher(mtgGoldfishRequest);
+        assertTrue(matcher.matches());
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "18");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Forest");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "THB");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "254");
+        assertNull(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH));
+
+        mtgGoldfishRequest = "18 Forest <254> [THB] (F)";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertTrue(matcher.matches());
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARDNO), "18");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_CARD), "Forest");  // TRIM
+        assertEquals(matcher.group(DeckRecognizer.REGRP_SET), "THB");
+        assertEquals(matcher.group(DeckRecognizer.REGRP_COLLNR), "254");
+        assertNotNull(matcher.group(DeckRecognizer.REGRP_FOIL_GFISH));
+
+        mtgGoldfishRequest = "18 Forest [THB]";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 [THB] Forest";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 Forest [THB] (F)";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 [THB] Forest (F)";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 Forest 254 [THB] (F)";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 Forest 254 [THB]";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+
+        mtgGoldfishRequest = "18 [THB] Forest 254";
+        matcher = target.matcher(mtgGoldfishRequest);
+        assertFalse(matcher.matches());
+    }
+
+    @Test void testCardRecognisedMTGGoldfishFormat(){
+        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);
+
+        String lineRequest = "4 Aspect of Hydra [BNG] (F)";
+        Token cardToken = recognizer.recogniseCardToken(lineRequest);
+        assertNotNull(cardToken);
+        assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+        assertNotNull(cardToken.getCard());
+        PaperCard aspectOfHydraCard = cardToken.getCard();
+        assertEquals(cardToken.getNumber(), 4);
+        assertEquals(aspectOfHydraCard.getName(), "Aspect of Hydra");
+        assertEquals(aspectOfHydraCard.getEdition(), "BNG");
+        assertTrue(aspectOfHydraCard.isFoil());
+
+        lineRequest = "18 Forest <254> [THB] (F)";
+        cardToken = recognizer.recogniseCardToken(lineRequest);
+        assertNotNull(cardToken);
+        assertEquals(cardToken.getType(), TokenType.LEGAL_CARD_REQUEST);
+        assertNotNull(cardToken.getCard());
+        PaperCard forestCard = cardToken.getCard();
+        assertEquals(cardToken.getNumber(), 18);
+        assertEquals(forestCard.getName(), "Forest");
+        assertEquals(forestCard.getEdition(), "THB");
+        assertTrue(forestCard.isFoil());
+    }
 }