diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index a21625cbf2f..2f9d35332ed 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1044,7 +1044,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg selectBySearchPanelC.fill = GridBagConstraints.VERTICAL; searchByTextField = new JTextField(); - searchByTextField.setToolTipText("Searches for card names, types, rarity, casting cost and rules text. NB: Mana symbols are written like {W},{U},{C} etc"); + searchByTextField.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)"); searchByTextField.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form index 88895a22e1b..94ff570fdef 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form @@ -780,7 +780,7 @@ - + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index cc2c4cc9657..88e006f6f84 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -1085,7 +1085,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } }); - jTextFieldSearch.setToolTipText("Searches for card names and in the rule text of the card."); + jTextFieldSearch.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)"); chkNames.setSelected(true); chkNames.setText("Names"); diff --git a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java index dc15882ba86..a1cfb2ce14e 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java @@ -1,19 +1,27 @@ package mage.filter.predicate.card; -import mage.cards.*; +import mage.cards.Card; +import mage.cards.CardWithSpellOption; +import mage.cards.ModalDoubleFacedCard; +import mage.cards.SplitCard; import mage.cards.mock.MockCard; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.predicate.Predicate; import mage.game.Game; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Special predicate to search cards in deck editor * - * @author North + * @author North, JayDi85 */ public class CardTextPredicate implements Predicate { @@ -22,7 +30,10 @@ public class CardTextPredicate implements Predicate { private final boolean inTypes; private final boolean inRules; private final boolean isUnique; - private HashMap seenCards = null; + private HashMap seenCards; + private final Pattern pattern; + private final Matcher matcher; + private final List textTokens; public CardTextPredicate(String text, boolean inNames, boolean inTypes, boolean inRules, boolean isUnique) { this.text = text; @@ -30,22 +41,33 @@ public class CardTextPredicate implements Predicate { this.inTypes = inTypes; this.inRules = inRules; this.isUnique = isUnique; - seenCards = new HashMap<>(); + this.seenCards = new HashMap<>(); + + // regexp to find texts inside "xxx" like + // "123 345" → ["123", "345"] + // "123 345 678" → ["123", "345", "678"] + // "123 "345 678"" → ["123", "345 678"] + this.textTokens = new ArrayList<>(); + this.pattern = Pattern.compile("\"([^\"]*)\"|(\\S+)"); + this.matcher = this.pattern.matcher(text.toLowerCase(Locale.ENGLISH)); + while (matcher.find()) { + if (matcher.group(1) != null) { + // inside "xxx" + this.textTokens.add(matcher.group(1)); + } else { + // normal xxx + this.textTokens.add(matcher.group(2)); + } + } } @Override public boolean apply(Card input, Game game) { - if (text.isEmpty() && !isUnique) { - return true; + if (this.textTokens.isEmpty()) { + return saveAndReturnUniqueFind(input); } - if (text.isEmpty() && isUnique) { - boolean found = !seenCards.containsKey(input.getName()); - seenCards.put(input.getName(), true); - return found; - } - - // first check in card name + // name: need all tokens if (inNames) { String fullName = input.getName(); if (input instanceof MockCard) { @@ -55,99 +77,74 @@ public class CardTextPredicate implements Predicate { } else if (input instanceof CardWithSpellOption) { fullName = input.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + ((CardWithSpellOption) input).getSpellCard().getName(); } - - if (fullName.toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) { - if (isUnique && seenCards.containsKey(input.getName())) { - return false; - } - if (isUnique) { - seenCards.put(input.getName(), true); - } - return true; + if (textHasTokens(fullName, true)) { + return saveAndReturnUniqueFind(input); } } - // separate by spaces - String[] tokens = text.toLowerCase(Locale.ENGLISH).split(" "); - for (String token : tokens) { - boolean found = false; - if (!token.isEmpty()) { - // then try to find in rules - if (inRules) { - if (input instanceof SplitCard) { - for (String rule : ((SplitCard) input).getLeftHalfCard().getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - for (String rule : ((SplitCard) input).getRightHalfCard().getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - } - - if (input instanceof ModalDoubleFacedCard) { - for (String rule : ((ModalDoubleFacedCard) input).getLeftHalfCard().getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - for (String rule : ((ModalDoubleFacedCard) input).getRightHalfCard().getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - } - - if (input instanceof CardWithSpellOption) { - for (String rule : ((CardWithSpellOption) input).getSpellCard().getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - } - - for (String rule : input.getRules(game)) { - if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { - found = true; - break; - } - } - } - if (inTypes) { - for (SubType subType : input.getSubtype(game)) { - if (subType.toString().equalsIgnoreCase(token)) { - found = true; - break; - } - } - for (SuperType superType : input.getSuperType(game)) { - if (superType.toString().equalsIgnoreCase(token)) { - found = true; - break; - } - } - } + // rules: need all tokens + if (inRules) { + List fullRules = new ArrayList<>(input.getRules(game)); + if (input instanceof SplitCard) { + fullRules.addAll(((SplitCard) input).getLeftHalfCard().getRules(game)); + fullRules.addAll(((SplitCard) input).getRightHalfCard().getRules(game)); } - - if (found && isUnique && seenCards.containsKey(input.getName())) { - found = false; + if (input instanceof ModalDoubleFacedCard) { + fullRules.addAll(((ModalDoubleFacedCard) input).getLeftHalfCard().getRules(game)); + fullRules.addAll(((ModalDoubleFacedCard) input).getRightHalfCard().getRules(game)); } - if (!found) { - return false; + if (input instanceof CardWithSpellOption) { + fullRules.addAll(((CardWithSpellOption) input).getSpellCard().getRules(game)); + } + if (textHasTokens(String.join("@", fullRules), true)) { + return saveAndReturnUniqueFind(input); } } + // types: need all tokens + if (inTypes) { + List fullTypes = new ArrayList<>(); + fullTypes.addAll(input.getSubtype(game).stream().map(SubType::toString).collect(Collectors.toList())); + fullTypes.addAll(input.getSuperType(game).stream().map(SuperType::toString).collect(Collectors.toList())); + if (textHasTokens(String.join("@", fullTypes), true)) { + return saveAndReturnUniqueFind(input); + } + } + + // not found + return false; + } + + private boolean saveAndReturnUniqueFind(Card card) { if (isUnique) { - seenCards.put(input.getName(), true); + boolean found = !seenCards.containsKey(card.getName()); + seenCards.put(card.getName(), true); + return found; + } else { + return true; } - return true; + } + + private boolean textHasTokens(String text, boolean needAllTokens) { + String searchingText = text + "@" + text.replace(", ", " "); + searchingText += "@" + searchingText.toLowerCase(Locale.ENGLISH); + + boolean found = false; + for (String token : this.textTokens) { + if (searchingText.contains(token)) { + found = true; + if (!needAllTokens) { + break; + } + } else { + if (needAllTokens) { + found = false; + break; + } + } + } + + return found; } @Override