Added searching of card mana costs. Added simple boolean search parsing to card search boxes.

This commit is contained in:
Krazy
2014-07-17 22:38:11 +00:00
parent 354a802044
commit 0728a42eb7
4 changed files with 311 additions and 19 deletions

View File

@@ -42,9 +42,13 @@ public final class CardRulesPredicates {
* the what
* @return the predicate
*/
public static Predicate<CardRules> cmc(final ComparableOp op, final int what) {
return new LeafNumber(LeafNumber.CardField.CMC, op, what);
}
public static Predicate<CardRules> cmc(final ComparableOp op, final int what) {
return new LeafNumber(LeafNumber.CardField.CMC, op, what);
}
public static Predicate<CardRules> cost(final PredicateString.StringOp op, final String what) {
return new LeafString(LeafString.CardField.COST, op, what);
}
/**
*
@@ -288,7 +292,7 @@ public final class CardRulesPredicates {
private static class LeafString extends PredicateString<CardRules> {
public enum CardField {
ORACLE_TEXT, NAME, SUBTYPE, JOINED_TYPE
ORACLE_TEXT, NAME, SUBTYPE, JOINED_TYPE, COST
}
private final String operand;
@@ -307,6 +311,9 @@ public final class CardRulesPredicates {
return op(card.getOracleText(), operand);
case JOINED_TYPE:
return op(card.getType().toString(), operand);
case COST:
String cost = card.getManaCost().toString();
return op(cost, operand);
default:
return false;
}
@@ -358,7 +365,7 @@ public final class CardRulesPredicates {
public static class LeafNumber implements Predicate<CardRules> {
public enum CardField {
CMC, POWER, TOUGHNESS,
CMC, POWER, TOUGHNESS
}
private final LeafNumber.CardField field;

View File

@@ -20,7 +20,7 @@ import java.awt.event.ItemListener;
public class CardSearchFilter extends TextSearchFilter<PaperCard> {
private FComboBoxWrapper<String> cbSearchMode;
private FLabel btnName, btnType, btnText;
private FLabel btnName, btnType, btnText, btnCost;
public CardSearchFilter(ItemManager<? super PaperCard> itemManager0) {
super(itemManager0);
@@ -35,6 +35,7 @@ public class CardSearchFilter extends TextSearchFilter<PaperCard> {
copy.btnName.setSelected(this.btnName.isSelected());
copy.btnType.setSelected(this.btnType.isSelected());
copy.btnText.setSelected(this.btnText.isSelected());
copy.btnCost.setSelected(this.btnCost.isSelected());
return copy;
}
@@ -44,7 +45,8 @@ public class CardSearchFilter extends TextSearchFilter<PaperCard> {
this.cbSearchMode.setSelectedIndex(0);
this.btnName.setSelected(true);
this.btnType.setSelected(true);
this.btnText.setSelected(true);
this.btnText.setSelected(true);
this.btnCost.setSelected(false);
}
@Override
@@ -67,6 +69,10 @@ public class CardSearchFilter extends TextSearchFilter<PaperCard> {
btnName = addButton(widget, "Name");
btnType = addButton(widget, "Type");
btnText = addButton(widget, "Text");
btnCost = addButton(widget, "Cost");
btnCost.setSelected(false);
}
@Override
@@ -74,11 +80,12 @@ public class CardSearchFilter extends TextSearchFilter<PaperCard> {
final int comboBoxWidth = 61;
final int buttonWidth = 51;
helper.fillLine(txtSearch, FTextField.HEIGHT, comboBoxWidth + buttonWidth * 3 + 12); //leave space for combo box and buttons
helper.fillLine(txtSearch, FTextField.HEIGHT, comboBoxWidth + buttonWidth * 4 + 16); //leave space for combo box and buttons
helper.include(cbSearchMode.getComponent(), comboBoxWidth, FTextField.HEIGHT);
helper.include(btnName, buttonWidth, FTextField.HEIGHT);
helper.include(btnType, buttonWidth, FTextField.HEIGHT);
helper.include(btnText, buttonWidth, FTextField.HEIGHT);
helper.include(btnText, buttonWidth, FTextField.HEIGHT);
helper.include(btnCost, buttonWidth, FTextField.HEIGHT);
}
@SuppressWarnings("serial")
@@ -103,7 +110,8 @@ public class CardSearchFilter extends TextSearchFilter<PaperCard> {
cbSearchMode.getSelectedIndex() != 0,
btnName.isSelected(),
btnType.isSelected(),
btnText.isSelected());
btnText.isSelected(),
btnCost.isSelected());
}
@Override

View File

@@ -12,6 +12,29 @@ Release Notes
Forge now includes many of the new Magic 2015 cards. It may take a few days/weeks before these new card pictures become available for downloading via the "Download LQ Card Pictures" button. The LQ set pictures tend to take a few more weeks/months to process before they become available for downloading via the "Download LQ Set Pictures" button. Please be patient. The Forge devs are not involved in maintaining the servers that house these pictures.
- Mana Cost Searching -
There's now a button (disabled by default) that allows you to search for cards by their mana costs. It's a little finicky right now, but improvements will happen for the next version of Forge.
- Boolean Expressions -
You can now filter cards with simple boolean expressions. Forge automatically detects when you want to use one, so just start typing away in the search box.
As an example,
"warrior" || "cleric"
will find all cards with the type of either "warrior" or "cleric" (when just the type search is enabled).
You can also use an AND operator to find cards with specific mana costs:
"g" && "w"
will find all cards with both green and white mana symbols.
You can then construct large and very specific expressions to find exactly what you need:
("human" || "cat") && ("warrior" || "cleric" || "soldier")
will find all humans or cats that are also warriors, clerics, or soldiers.
More improvements are on their way in the next version of Forge, including removing the need for quotation marks and adding NOT operators.
---------
New Cards
---------

View File

@@ -2,7 +2,6 @@ package forge.itemmanager;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.card.CardRulesPredicates.Presets;
@@ -16,10 +15,9 @@ import forge.itemmanager.SItemManagerUtil.StatTypes;
import forge.util.BinaryUtil;
import forge.util.PredicateString.StringOp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
/**
* Static factory; holds blocks of form elements and predicates
@@ -31,11 +29,26 @@ public class SFilterUtil {
/**
* builds a string search filter
*/
public static Predicate<PaperCard> buildTextFilter(String text, boolean invert, boolean inName, boolean inType, boolean inText) {
if (text.trim().isEmpty()) {
public static Predicate<PaperCard> buildTextFilter(String text, boolean invert, boolean inName, boolean inType, boolean inText, boolean inCost) {
text = text.trim();
if (text.isEmpty()) {
return Predicates.alwaysTrue();
}
if (BooleanExpression.isBooleanExpression(text)) {
BooleanExpression expression = new BooleanExpression(text);
try {
Predicate<CardRules> filter = expression.evaluate(inName, inType, inText, inCost);
if (filter != null) {
return Predicates.compose(filter, PaperCard.FN_GET_RULES);
} //The expression is not valid, let the regular filters work
} catch (IOException e) {
e.printStackTrace();
}
}
String[] splitText = text.replaceAll(",", "").replaceAll(" ", " ").split(" ");
List<Predicate<CardRules>> terms = new ArrayList<Predicate<CardRules>>();
@@ -44,15 +57,256 @@ public class SFilterUtil {
if (inName) { subands.add(CardRulesPredicates.name(StringOp.CONTAINS_IC, s)); }
if (inType) { subands.add(CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, s)); }
if (inText) { subands.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, s)); }
if (inText) { subands.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, s)); }
if (inCost) { subands.add(CardRulesPredicates.cost(StringOp.CONTAINS_IC, s)); }
terms.add(Predicates.or(subands));
}
Predicate<CardRules> textFilter = invert ? Predicates.not(Predicates.or(terms)) : Predicates.and(terms);
return Predicates.compose(textFilter, PaperCard.FN_GET_RULES);
}
private static class BooleanExpression {
private static enum Operation {
AND("&&"), OR("||"), OPEN_PAREN("("), CLOSE_PAREN(")"), VALUE("\""), ESCAPE("\\");
private final String token;
private Operation(String token) {
this.token = token;
}
}
private Stack<StackItem> stack = new Stack<StackItem>();
private String expression;
private BooleanExpression(String expression) {
this.expression = expression;
}
private Predicate<CardRules> evaluate(boolean inName, boolean inType, boolean inText, boolean inCost) throws IOException {
StringReader reader = new StringReader(expression);
String currentItem = "";
boolean escapeNext = false;
boolean gettingValue = false;
int character;
while ((character = reader.read()) != -1) {
String token = Character.toString((char) character);
if (token.equals(Operation.OPEN_PAREN.token)) {
StackOperation operation = new StackOperation();
operation.operation = Operation.OPEN_PAREN;
stack.push(operation);
continue;
}
if (token.equals(Operation.CLOSE_PAREN.token)) {
character = reader.read();
if (character != -1) {
evaluateStack();
continue;
} else {
break;
}
}
if (token.equals("&")) {
character = reader.read();
if (character != -1 && character == '&') {
StackOperation operation = new StackOperation();
operation.operation = Operation.AND;
stack.push(operation);
currentItem = "";
} else if (gettingValue) {
currentItem += token + Character.toString((char) character);
continue;
}
}
if (token.equals("|")) {
character = reader.read();
if (character != -1 && character == '|') {
StackOperation operation = new StackOperation();
operation.operation = Operation.OR;
stack.push(operation);
currentItem = "";
} else if (gettingValue) {
currentItem += token + Character.toString((char) character);
continue;
}
}
if (token.equals(Operation.VALUE.token) && !escapeNext) {
if (gettingValue) {
StackValue stackValue = new StackValue();
stackValue.value = evaluateValue(currentItem.trim(), inName, inType, inText, inCost);
stack.push(stackValue);
currentItem = "";
gettingValue = false;
} else {
gettingValue = true;
continue; //Don't add the quotation mark
}
}
if (token.equals(Operation.ESCAPE.token) && !escapeNext) {
escapeNext = true;
} else if (gettingValue) {
currentItem += token;
}
}
while (stack.size() > 1) {
evaluateStack();
}
if (stack.isEmpty()) {
return null; //The expression is not valid, let the regular filters work
}
return ((StackValue) stack.pop()).value;
}
private Predicate<CardRules> evaluateValue(String value, boolean inName, boolean inType, boolean inText, boolean inCost) {
if (inName) { return CardRulesPredicates.name(StringOp.CONTAINS_IC, value); }
if (inType) { return CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, value); }
if (inText) { return CardRulesPredicates.rules(StringOp.CONTAINS_IC, value); }
if (inCost) { return CardRulesPredicates.cost(StringOp.CONTAINS_IC, value); }
return Predicates.alwaysTrue();
}
private void evaluateStack() {
StackItem stackItem;
Predicate<CardRules> rules = null;
Operation operation = null;
boolean finishedStack = false;
while (stack.size() > 0) {
stackItem = stack.pop();
if (stackItem.isOperation()) {
operation = ((StackOperation) stackItem).operation;
if (operation.equals(Operation.OPEN_PAREN)) {
if (rules != null) {
StackValue value = new StackValue();
value.value = rules;
stack.push(value);
}
break;
}
} else if (rules == null) {
rules = ((StackValue) stackItem).value;
} else {
if (operation == null) {
return;
}
StackValue value = new StackValue();
if (operation.equals(Operation.AND)) {
value.value = Predicates.and(rules, ((StackValue) stackItem).value);
} else if (operation.equals(Operation.OR)) {
value.value = Predicates.or(rules, ((StackValue) stackItem).value);
} else {
return;
}
if (stack.size() == 0) {
finishedStack = true;
}
stack.push(value);
rules = null;
}
if (finishedStack) {
return;
}
}
}
private static boolean isBooleanExpression(String s) {
return s.contains(Operation.AND.token) || s.contains(Operation.OR.token);
}
private static abstract class StackItem {
private boolean isOperation() {
return this instanceof StackOperation;
}
}
private static class StackOperation extends StackItem {
private Operation operation;
}
private static class StackValue extends StackItem {
private Predicate<CardRules> value;
}
/*private static abstract class BooleanExpressionNode {
protected abstract Predicate<CardRules> evaluate(boolean inName, boolean inType, boolean inText, boolean inCost);
}
private static class ExpressionNode extends BooleanExpressionNode {
private BooleanExpressionNode left;
private BooleanExpressionNode right;
private Operation operation;
@Override
protected Predicate<CardRules> evaluate(boolean inName, boolean inType, boolean inText, boolean inCost) {
switch (operation) {
case AND:
return Predicates.and(left.evaluate(inName, inType, inText, inCost), right.evaluate(inName, inType, inText, inCost));
case OR:
return Predicates.or(left.evaluate(inName, inType, inText, inCost), right.evaluate(inName, inType, inText, inCost));
}
return Predicates.alwaysTrue();
}
}
private static class ValueNode extends BooleanExpressionNode {
private String value;
@Override
protected Predicate<CardRules> evaluate(boolean inName, boolean inType, boolean inText, boolean inCost) {
if (inName) { return CardRulesPredicates.name(StringOp.CONTAINS_IC, value); }
if (inType) { return CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, value); }
if (inText) { return CardRulesPredicates.rules(StringOp.CONTAINS_IC, value); }
if (inCost) { return CardRulesPredicates.cost(StringOp.CONTAINS_IC, value); }
return Predicates.alwaysTrue();
}
}*/
}
public static <T extends InventoryItem> Predicate<T> buildItemTextFilter(String text) {
if (text.trim().isEmpty()) {
return Predicates.alwaysTrue();