From 6c77de9d4ccf56ba5b66e0345004c1724e01b195 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 28 Aug 2011 02:05:04 +0000 Subject: [PATCH] New lightweight classes for cards management in deck editors and quest mode (no other parts or project changed) --- .gitattributes | 16 +- src/main/java/forge/card/CardColor.java | 66 ++++ src/main/java/forge/card/CardCoreType.java | 7 + src/main/java/forge/card/CardDb.java | 145 ++++++++ src/main/java/forge/card/CardInSet.java | 45 +++ src/main/java/forge/card/CardManaCost.java | 154 ++++++++ .../java/forge/card/CardManaCostShard.java | 124 +++++++ src/main/java/forge/card/CardPool.java | 67 ++++ src/main/java/forge/card/CardPoolView.java | 94 +++++ src/main/java/forge/card/CardPrinted.java | 166 +++++++++ src/main/java/forge/card/CardRarity.java | 28 ++ src/main/java/forge/card/CardReference.java | 89 ----- src/main/java/forge/card/CardRules.java | 348 ++++++++++++++++++ src/main/java/forge/card/CardSet.java | 47 +++ src/main/java/forge/card/CardSuperType.java | 6 + src/main/java/forge/card/CardType.java | 113 ++++++ src/main/java/forge/card/MtgDataParser.java | 68 ++++ .../java/forge/properties/NewConstants.java | 2 + 18 files changed, 1495 insertions(+), 90 deletions(-) create mode 100644 src/main/java/forge/card/CardColor.java create mode 100644 src/main/java/forge/card/CardCoreType.java create mode 100644 src/main/java/forge/card/CardDb.java create mode 100644 src/main/java/forge/card/CardInSet.java create mode 100644 src/main/java/forge/card/CardManaCost.java create mode 100644 src/main/java/forge/card/CardManaCostShard.java create mode 100644 src/main/java/forge/card/CardPool.java create mode 100644 src/main/java/forge/card/CardPoolView.java create mode 100644 src/main/java/forge/card/CardPrinted.java create mode 100644 src/main/java/forge/card/CardRarity.java delete mode 100644 src/main/java/forge/card/CardReference.java create mode 100644 src/main/java/forge/card/CardRules.java create mode 100644 src/main/java/forge/card/CardSet.java create mode 100644 src/main/java/forge/card/CardSuperType.java create mode 100644 src/main/java/forge/card/CardType.java create mode 100644 src/main/java/forge/card/MtgDataParser.java diff --git a/.gitattributes b/.gitattributes index 1dfc3c6b2a5..7dab4192efa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9583,7 +9583,21 @@ src/main/java/forge/Time.java svneol=native#text/plain src/main/java/forge/UndoCommand.java svneol=native#text/plain src/main/java/forge/Upkeep.java svneol=native#text/plain src/main/java/forge/ZCTrigger.java svneol=native#text/plain -src/main/java/forge/card/CardReference.java -text +src/main/java/forge/card/CardColor.java -text +src/main/java/forge/card/CardCoreType.java -text +src/main/java/forge/card/CardDb.java -text +src/main/java/forge/card/CardInSet.java -text +src/main/java/forge/card/CardManaCost.java -text +src/main/java/forge/card/CardManaCostShard.java -text +src/main/java/forge/card/CardPool.java -text +src/main/java/forge/card/CardPoolView.java -text +src/main/java/forge/card/CardPrinted.java -text +src/main/java/forge/card/CardRarity.java -text +src/main/java/forge/card/CardRules.java -text +src/main/java/forge/card/CardSet.java -text +src/main/java/forge/card/CardSuperType.java -text +src/main/java/forge/card/CardType.java -text +src/main/java/forge/card/MtgDataParser.java -text src/main/java/forge/card/abilityFactory/AbilityFactory.java svneol=native#text/plain src/main/java/forge/card/abilityFactory/AbilityFactory_AlterLife.java svneol=native#text/plain src/main/java/forge/card/abilityFactory/AbilityFactory_Animate.java svneol=native#text/plain diff --git a/src/main/java/forge/card/CardColor.java b/src/main/java/forge/card/CardColor.java new file mode 100644 index 00000000000..f48c551c843 --- /dev/null +++ b/src/main/java/forge/card/CardColor.java @@ -0,0 +1,66 @@ +package forge.card; + +import org.apache.tools.ant.taskdefs.Concat; + +import forge.Constant; + +/** + *

CardColor class.

+ * + * @author Forge + * @version $Id: CardColor.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardColor implements Comparable { + + public static final byte WHITE = 1 << 1; + public static final byte BLUE = 1 << 2; + public static final byte BLACK = 1 << 3; + public static final byte RED = 1 << 4; + public static final byte GREEN = 1 << 5; + + private final byte myColor; + + // TODO: some cards state "CardName is %color%" (e.g. pacts of...) - fix this later + public CardColor(final CardManaCost mana) { + myColor = mana.getColorProfile(); + } + + public boolean hasAnyColor(final byte colormask) { return (myColor & colormask) != 0; } + public boolean hasAllColors(final byte colormask) { return (myColor & colormask) == colormask; } + + public int countColors() { byte v = myColor; int c = 0; for (; v != 0; c++) { v &= v - 1; } return c; } // bit count + + public boolean isColorless() { return myColor == 0; } + public boolean isMulticolor() { return countColors() > 1; } + public boolean isMonoColor() { return countColors() == 1; } + public boolean isEqual(final byte color) { return color == myColor; } + + @Override + public int compareTo(final CardColor other) { return myColor - other.myColor; } + + // Presets + public boolean hasWhite() { return hasAnyColor(WHITE); } + public boolean hasBlue() { return hasAnyColor(BLUE); } + public boolean hasBlack() { return hasAnyColor(BLACK); } + public boolean hasRed() { return hasAnyColor(RED); } + public boolean hasGreen() { return hasAnyColor(GREEN); } + + public boolean isWhite() { return isEqual(WHITE); } + public boolean isBlue() { return isEqual(BLUE); } + public boolean isBlack() { return isEqual(BLACK); } + public boolean isRed() { return isEqual(RED); } + public boolean isGreen() { return isEqual(GREEN); } + + @Override + public String toString() { + switch (myColor) { + case 0: return ""; + case WHITE: return "White"; // Constant.Color.White; + case BLUE: return "Blue"; // Constant.Color.Blue; + case BLACK: return "Black"; // Constant.Color.Black; + case RED: return "Red"; // Constant.Color.Red; + case GREEN: return "Green"; // Constant.Color.Green; + default: return "Multi"; + } + } +} diff --git a/src/main/java/forge/card/CardCoreType.java b/src/main/java/forge/card/CardCoreType.java new file mode 100644 index 00000000000..11d355405a3 --- /dev/null +++ b/src/main/java/forge/card/CardCoreType.java @@ -0,0 +1,7 @@ +package forge.card; + + +public enum CardCoreType +{ + Artifact, Creature, Enchantment, Instant, Land, Plane, Planeswalker, Scheme, Sorcery, Tribal, Vanguard +} diff --git a/src/main/java/forge/card/CardDb.java b/src/main/java/forge/card/CardDb.java new file mode 100644 index 00000000000..e9a3a01725c --- /dev/null +++ b/src/main/java/forge/card/CardDb.java @@ -0,0 +1,145 @@ +package forge.card; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; + +import forge.Card; +import forge.FileUtil; +import forge.properties.ForgeProps; +import forge.properties.NewConstants; + + +/** + *

CardOracleDatabase class.

+ * + * @author Forge + * @version $Id: CardOracleDatabase.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardDb { + private static volatile CardDb onlyInstance = null; // 'volatile' keyword makes this working + public static CardDb instance() { + if (onlyInstance == null) { + synchronized (CardDb.class) { + if (onlyInstance == null) { // It's broken under 1.4 and below, on 1.5+ works again! + onlyInstance = new CardDb(); + } + } + } + return onlyInstance; + } + + // Here oracle cards + private final Map cards = new Hashtable(); + // Here are refs, get them by name + private final Map uniqueCards = new Hashtable(); + + // need this to obtain cardReference by name+set+artindex + private final Map> allCardsBySet = new Hashtable>(); + // this is the same list in flat storage + private final List allCardsFlat = new ArrayList(); + + + private CardDb() { + List mtgDataLines = FileUtil.readFile(ForgeProps.getFile(NewConstants.MTG_DATA)); + MtgDataParser parser = new MtgDataParser(mtgDataLines); + while (parser.hasNext()) { + addNewCard(parser.next()); + } + } + + public void addNewCard(final CardRules card) { + if (null == card) { return; } + //System.out.println(card.getName()); + String cardName = card.getName(); + + // 1. register among oracle uniques + cards.put(cardName, card); + + // 2. fill refs into two lists: one with + CardPrinted lastAdded = null; + for (Entry s : card.getSetsPrinted()) { + String set = s.getKey(); + + // get this set storage, if not found, create it! + Map setMap = allCardsBySet.get(set); + if (null == setMap) { + setMap = new Hashtable(); + allCardsBySet.put(set, setMap); + } + + int count = s.getValue().getCopiesCount(); + CardPrinted[] cards = new CardPrinted[count]; + setMap.put(cardName, cards); + for (int i = 0; i < count; i++) { + lastAdded = CardPrinted.build(card, set, s.getValue().getRarity(), i+1); + allCardsFlat.add(lastAdded); + cards[i] = lastAdded; + } + + } + + uniqueCards.put(cardName, lastAdded); + } + + // Single fetch + public CardPrinted getCard(final String name) { + // Sometimes they read from decks things like "CardName|Set" - but we can handle it + int pipePos = name.indexOf('|'); + if (pipePos >= 0) { return getCard(name.substring(0, pipePos), name.substring(pipePos+1)); } + // OK, plain name here + CardPrinted card = uniqueCards.get(name); + if (card != null) { return card; } + throw new NoSuchElementException(String.format("Card '%s' not found in our database.", name)); + } + // Advanced fetch by name+set + public CardPrinted getCard(final String name, final String set) { return getCard(name, set, 1); } + public CardPrinted getCard(final String name, final String set, final int artIndex) { + // 1. get set + Map cardsFromset = allCardsBySet.get(set); + if (null == cardsFromset) { + String err = String.format("Asked for card '%s' from set '%s': that set was not found. :(", name, set); + throw new NoSuchElementException(err); + } + // 2. Find the card itself + CardPrinted[] cards = cardsFromset.get(name); + if (null == cards) { + String err = String.format("Asked for card '%s' from '%s': set found, but the card wasn't. :(", name, set); + throw new NoSuchElementException(err); + } + // 3. Get the proper copy + if (artIndex > 0 && artIndex <= cards.length) { return cards[artIndex-1]; } + String err = String.format("Asked for '%s' from '%s' #%d: db didn't find that copy. Note: artIndex is 1-based", name, set, artIndex); + throw new NoSuchElementException(err); + } + + // Fetch from Forge's Card instance. Well, there should be no errors, but we'll still check + public CardPrinted getCard(final Card forgeCard) { + String name = forgeCard.getName(); + String set = forgeCard.getCurSetCode(); + if (StringUtils.isNotBlank(set)) { return getCard(name, set); } + return getCard(name); + } + + // Multiple fetch + public List getCards(final List names) { + List result = new ArrayList(); + for (String name : names) { result.add(getCard(name)); } + return result; + } + + // returns a list of all cards from their respective latest editions + public Iterable getAllUniqueCards() { + return uniqueCards.values(); + } + + public List getAllCards() { return allCardsFlat; } + +} diff --git a/src/main/java/forge/card/CardInSet.java b/src/main/java/forge/card/CardInSet.java new file mode 100644 index 00000000000..621a3cfcc0c --- /dev/null +++ b/src/main/java/forge/card/CardInSet.java @@ -0,0 +1,45 @@ +package forge.card; + +/** + *

CardInSet class.

+ * + * @author Forge + * @version $Id: CardInSet.java 9708 2011-08-09 19:34:12Z jendave $ + */ + +public class CardInSet { + private CardRarity rarity; + private int numCopies; + + public CardInSet(final CardRarity rarity, final int cntCopies) { + this.rarity = rarity; + this.numCopies = cntCopies; + } + + public static CardInSet parse(final String unparsed) { + int spaceAt = unparsed.indexOf(' '); + char rarity = unparsed.charAt(spaceAt + 1); + CardRarity rating; + switch (rarity) { + case 'L': rating = CardRarity.BasicLand; break; + case 'C': rating = CardRarity.Common; break; + case 'U': rating = CardRarity.Uncommon; break; + case 'R': rating = CardRarity.Rare; break; + case 'M': rating = CardRarity.MythicRare; break; + case 'S': rating = CardRarity.Special; break; + default: rating = CardRarity.MythicRare; break; + } + + int number = 1; + int bracketAt = unparsed.indexOf('('); + if (-1 != bracketAt) { + String sN = unparsed.substring(bracketAt + 2, bracketAt + 3); + number = Integer.parseInt(sN); + } + return new CardInSet(rating, number); + + } + + public final int getCopiesCount() { return numCopies; } + public final CardRarity getRarity() { return rarity; } +} diff --git a/src/main/java/forge/card/CardManaCost.java b/src/main/java/forge/card/CardManaCost.java new file mode 100644 index 00000000000..9ac79b909b5 --- /dev/null +++ b/src/main/java/forge/card/CardManaCost.java @@ -0,0 +1,154 @@ +package forge.card; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; + + +/** + *

CardManaCost class.

+ * + * @author Forge + * @version $Id: CardManaCost.java 9708 2011-08-09 19:34:12Z jendave $ + */ + + +public final class CardManaCost implements Comparable { + private final List shards = new ArrayList(); + private final int genericCost; + private final boolean isEmpty; // lands cost + private final String stringValue; // precalculated for toString; + + private Float compareWeight = null; + + + // pass mana cost parser here + private CardManaCost() { + isEmpty = true; + genericCost = 0; + stringValue = ""; + } + + public static final CardManaCost empty = new CardManaCost(); + + public CardManaCost(final String cost) { + ParserMtgData parser = new ParserMtgData(cost); + if (!parser.hasNext()) { + throw new RuntimeException("Empty manacost passed to parser (this should have been handled before)"); + } + isEmpty = false; + while (parser.hasNext()) { + CardManaCostShard shard = parser.next(); + if (shard != null) { shards.add(shard); } // null is OK - that was generic mana + } + genericCost = parser.getColorlessCost(); // collect generic mana here + stringValue = getSimpleString(); + } + + private String getSimpleString() { + if (shards.isEmpty()) return Integer.toString(genericCost); + + StringBuilder sb = new StringBuilder(); + boolean isFirst = false; + if (genericCost > 0) { sb.append(genericCost); isFirst = false; } + for (CardManaCostShard s : shards) { + if ( !isFirst ) { sb.append(' '); } + else { isFirst = false; } + sb.append(s.toString()); + } + return sb.toString(); + } + + public int getCMC() { + int sum = 0; + for (CardManaCostShard s : shards) { sum += s.cmc; } + return sum + genericCost; + } + + public byte getColorProfile() { + byte result = 0; + for (CardManaCostShard s : shards) { result |= s.getColorMask(); } + return result; + } + + @Override + public int compareTo(final CardManaCost o) { return getCompareWeight().compareTo(o.getCompareWeight()); } + private Float getCompareWeight() { + if (compareWeight == null) { + float weight = genericCost; + for (CardManaCostShard s : shards) { weight += s.cmpc; } + if (isEmpty) { + weight = -1; // for those who doesn't even have a 0 sign on card + } + compareWeight = Float.valueOf(weight); + } + return compareWeight; + } + + @Override + public String toString() { + return stringValue; + } + + public class ParserMtgData implements Iterator { + private final String cost; + + private int nextBracket; + private int colorlessCost; + + + public ParserMtgData(final String cost) { + this.cost = cost; + // System.out.println(cost); + nextBracket = cost.indexOf('{'); + colorlessCost = 0; + } + + public int getColorlessCost() { + if ( hasNext() ) { + throw new RuntimeException("Colorless cost should be obtained after iteration is complete"); + } + return colorlessCost; + } + + @Override + public boolean hasNext() { return nextBracket != -1; } + + @Override + public CardManaCostShard next() { + int closeBracket = cost.indexOf('}', nextBracket); + String unparsed = cost.substring(nextBracket + 1, closeBracket); + nextBracket = cost.indexOf('{', closeBracket + 1); + + // System.out.println(unparsed); + if (StringUtils.isNumeric(unparsed)) { + colorlessCost += Integer.parseInt(unparsed); + return null; + } + + int atoms = 0; + for (int iChar = 0; iChar < unparsed.length(); iChar++) { + switch (unparsed.charAt(iChar)) { + case 'W': atoms |= CardManaCostShard.Atom.WHITE; break; + case 'U': atoms |= CardManaCostShard.Atom.BLUE; break; + case 'B': atoms |= CardManaCostShard.Atom.BLACK; break; + case 'R': atoms |= CardManaCostShard.Atom.RED; break; + case 'G': atoms |= CardManaCostShard.Atom.GREEN; break; + case '2': atoms |= CardManaCostShard.Atom.OR_2_COLORLESS; break; + case 'P': atoms |= CardManaCostShard.Atom.OR_2_LIFE; break; + case 'X': atoms |= CardManaCostShard.Atom.IS_X; break; + default: break; + } + } + return CardManaCostShard.valueOf(atoms); + } + + @Override + public void remove() { } // unsuported + } +} + diff --git a/src/main/java/forge/card/CardManaCostShard.java b/src/main/java/forge/card/CardManaCostShard.java new file mode 100644 index 00000000000..9ba09cdacd5 --- /dev/null +++ b/src/main/java/forge/card/CardManaCostShard.java @@ -0,0 +1,124 @@ +package forge.card; + +import forge.card.mana.ManaCost; + +public class CardManaCostShard { + + private final int shard; + public final int cmc; + public final float cmpc; + private final String stringValue; + protected CardManaCostShard(int value, String sValue) { + shard = value; + cmc = getCMC(); + cmpc = getCmpCost(); + stringValue = sValue; + } + + public interface Atom { + //int COLORLESS = 1 << 0; + int WHITE = 1 << 1; + int BLUE = 1 << 2; + int BLACK = 1 << 3; + int RED = 1 << 4; + int GREEN = 1 << 5; + + int IS_X = 1 << 8; + int OR_2_COLORLESS = 1 << 9; + int OR_2_LIFE = 1 << 10; + + } + + /* Why boxed values are here? + * They is meant to be constant and require no further boxing if added into an arraylist or something else, + * with generic parameters that would require Object's descendant. + * + * Choosing between "let's have some boxing" and "let's have some unboxing", + * I choose the latter, + * because memory for boxed objects will be taken from heap, + * while unboxed values will lay on stack, which is faster + */ + public static final CardManaCostShard X = new CardManaCostShard(Atom.IS_X, "X"); + + public static final CardManaCostShard WHITE = new CardManaCostShard(Atom.WHITE, "W"); + public static final CardManaCostShard BLUE = new CardManaCostShard(Atom.BLUE, "U"); + public static final CardManaCostShard BLACK = new CardManaCostShard(Atom.BLACK, "B"); + public static final CardManaCostShard RED = new CardManaCostShard(Atom.RED, "R"); + public static final CardManaCostShard GREEN = new CardManaCostShard(Atom.GREEN, "G"); + + public static final CardManaCostShard PW = new CardManaCostShard(Atom.WHITE | Atom.OR_2_LIFE, "W/P"); + public static final CardManaCostShard PU = new CardManaCostShard(Atom.BLUE | Atom.OR_2_LIFE, "U/P"); + public static final CardManaCostShard PB = new CardManaCostShard(Atom.BLACK | Atom.OR_2_LIFE, "B/P"); + public static final CardManaCostShard PR = new CardManaCostShard(Atom.RED | Atom.OR_2_LIFE, "R/P"); + public static final CardManaCostShard PG = new CardManaCostShard(Atom.GREEN | Atom.OR_2_LIFE, "G/P"); + + public static final CardManaCostShard WU = new CardManaCostShard(Atom.WHITE | Atom.BLUE, "W/U"); + public static final CardManaCostShard WB = new CardManaCostShard(Atom.WHITE | Atom.BLACK, "W/B"); + public static final CardManaCostShard WR = new CardManaCostShard(Atom.WHITE | Atom.RED, "W/R"); + public static final CardManaCostShard WG = new CardManaCostShard(Atom.WHITE | Atom.GREEN, "W/G"); + public static final CardManaCostShard UB = new CardManaCostShard(Atom.BLUE | Atom.BLACK, "U/B"); + public static final CardManaCostShard UR = new CardManaCostShard(Atom.BLUE | Atom.RED, "U/R"); + public static final CardManaCostShard UG = new CardManaCostShard(Atom.BLUE | Atom.GREEN, "U/G"); + public static final CardManaCostShard BR = new CardManaCostShard(Atom.BLACK | Atom.RED, "B/R"); + public static final CardManaCostShard BG = new CardManaCostShard(Atom.BLACK | Atom.GREEN, "B/G"); + public static final CardManaCostShard RG = new CardManaCostShard(Atom.RED | Atom.GREEN, "R/G"); + + public static final CardManaCostShard W2 = new CardManaCostShard(Atom.WHITE | Atom.OR_2_COLORLESS, "2/W"); + public static final CardManaCostShard U2 = new CardManaCostShard(Atom.BLUE | Atom.OR_2_COLORLESS, "2/U"); + public static final CardManaCostShard B2 = new CardManaCostShard(Atom.BLACK | Atom.OR_2_COLORLESS, "2/B"); + public static final CardManaCostShard R2 = new CardManaCostShard(Atom.RED | Atom.OR_2_COLORLESS, "2/R"); + public static final CardManaCostShard G2 = new CardManaCostShard(Atom.GREEN | Atom.OR_2_COLORLESS, "2/G"); + + private static final CardManaCostShard[] allPossible = new CardManaCostShard[] { + X, WHITE, BLUE, BLACK, RED, GREEN, + PW, PU, PB, PR, PG, + WU, WB, WR, WG, UB, UR, UG, BR, BG, RG, + W2, U2, B2, R2, G2 + }; + + private int getCMC() { + if (0 != (shard & Atom.IS_X)) { return 0; } + if (0 != (shard & Atom.OR_2_COLORLESS)) { return 2; } + return 1; + } + + /** + * Returns Mana cost, adjusted slightly to make colored mana parts more significant. + * Should only be used for comparison purposes; using this method allows the sort: + * 2 < X 2 < 1 U < U U < UR U < X U U < X X U U + * + * @return The converted cost + 0.0005* the number of colored mana in the cost + 0.00001 * + * the number of X's in the cost + */ + private float getCmpCost() { + if (0 != (shard & Atom.IS_X)) { return 0.0001f; } + float cost = 0 != (shard & Atom.OR_2_COLORLESS) ? 2 : 1; + // yes, these numbers are magic, slightly-magic + if (0 != (shard & Atom.WHITE)) { cost += 0.0005f; } + if (0 != (shard & Atom.BLUE)) { cost += 0.0020f; } + if (0 != (shard & Atom.BLACK)) { cost += 0.0080f; } + if (0 != (shard & Atom.RED)) { cost += 0.0320f; } + if (0 != (shard & Atom.GREEN)) { cost += 0.1280f; } + return cost; + } + + final byte getColorMask() { + byte result = 0; + if (0 != (shard & Atom.WHITE)) { result |= CardColor.WHITE; } + if (0 != (shard & Atom.BLUE)) { result |= CardColor.BLUE; } + if (0 != (shard & Atom.BLACK)) { result |= CardColor.BLACK; } + if (0 != (shard & Atom.RED)) { result |= CardColor.RED; } + if (0 != (shard & Atom.GREEN)) { result |= CardColor.GREEN; } + return result; + } + + public static CardManaCostShard valueOf(final int atoms) { + for (int i = 0; i < allPossible.length; i++) { + if (allPossible[i].shard == atoms) { return allPossible[i]; } + } + throw new RuntimeException(String.format("Not fount: mana shard with profile = %x", atoms)); + } + + @Override + public String toString() { return stringValue; } +} diff --git a/src/main/java/forge/card/CardPool.java b/src/main/java/forge/card/CardPool.java new file mode 100644 index 00000000000..4a106420737 --- /dev/null +++ b/src/main/java/forge/card/CardPool.java @@ -0,0 +1,67 @@ +package forge.card; + +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Map.Entry; + +/** + *

CardPool class.

+ * + * @author Forge + * @version $Id: CardReference.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardPool extends CardPoolView { + + // Contructors here + public CardPool() { super(); } + public CardPool(final List names) { super(); addAllCards(CardDb.instance().getCards(names)); } + + // Copy ctor will create its own modifiable pool + @SuppressWarnings("unchecked") + public CardPool(final CardPoolView from) { + super(); + cards = (Hashtable) ((Hashtable) (from.cards)).clone(); + } + public CardPool(final Iterable list) { + this(); addAllCards(list); + } + + + + // get + public CardPoolView getView() { return new CardPoolView(Collections.unmodifiableMap(cards)); } + + // Cards manipulation + public void add(final CardPrinted card) { add(card, 1); } + public void add(final CardPrinted card, final int amount) { + cards.put(card, count(card) + amount); + isListInSync = false; + } + public void addAllCards(final Iterable cards) { + for (CardPrinted cr : cards) { add(cr); } + isListInSync = false; + } + public void addAll(final Iterable> map) { + for (Entry e : map) { add(e.getKey(), e.getValue()); } + isListInSync = false; + } + public void addAll(final Entry[] map) { + for (Entry e : map) { add(e.getKey(), e.getValue()); } + isListInSync = false; + } + + public void remove(final CardPrinted card) { remove(card, 1); } + public void remove(final CardPrinted card, final int amount) { + int count = count(card); + if (count <= amount) { cards.remove(card); } + else { cards.put(card, count - amount); } + isListInSync = false; + } + public void removeAll(final Iterable> map) { + for (Entry e : map) { remove(e.getKey(), e.getValue()); } + isListInSync = false; + } + + public void clear() { cards.clear(); isListInSync = false; } +} diff --git a/src/main/java/forge/card/CardPoolView.java b/src/main/java/forge/card/CardPoolView.java new file mode 100644 index 00000000000..a57daacf9a6 --- /dev/null +++ b/src/main/java/forge/card/CardPoolView.java @@ -0,0 +1,94 @@ +package forge.card; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import forge.CardList; + +import net.slightlymagic.braids.util.lambda.Lambda1; + +/** + *

CardPoolView class.

+ * + * @author Forge + * @version $Id: CardPoolView.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public class CardPoolView implements Iterable> { + + // Field Accessors for select/aggregate operations with filters. + public final static Lambda1> fnToCard = + new Lambda1>() { + @Override public CardRules apply(final Entry from) { return from.getKey().getCard(); } + }; + public final static Lambda1> fnToReference = + new Lambda1>() { + @Override public CardPrinted apply(final Entry from) { return from.getKey(); } + }; + + public final static Lambda1> fnToCount = + new Lambda1>() { + @Override public Integer apply(final Entry from) { return from.getValue(); } + }; + + // Constructors + public CardPoolView() { cards = new Hashtable(); } + public CardPoolView(final Map inMap) { cards = inMap; } + + // Data members + protected Map cards; + + // same thing as above, it was copied to provide sorting (needed by table views in deck editors) + protected List> cardsListOrdered = new ArrayList>(); + protected boolean isListInSync = false; + + @Override + public final Iterator> iterator() { return cards.entrySet().iterator(); } + + // Cards manipulation + public final int count(final CardPrinted card) { + if (cards == null) { return 0; } + Integer boxed = cards.get(card); + return boxed == null ? 0 : boxed.intValue(); + } + public final int countAll() { + int result = 0; + if (cards != null) { for (Integer n : cards.values()) { result += n; } } + return result; + } + public final int countDistinct() { return cards.size(); } + + public final List> getOrderedList() { + if (!isListInSync) { + cardsListOrdered.clear(); + if (cards != null) { + for (Entry e : cards.entrySet()) { + cardsListOrdered.add(e); + } + } + isListInSync = true; + } + return cardsListOrdered; + } + + public final List toFlatList() { + List result = new ArrayList(); + for (Entry e : this) { + for (int i = 0; i < e.getValue(); i++) { result.add(e.getKey()); } + } + return result; + } + + public final CardList toForgeCardList() { + CardList result = new CardList(); + for (Entry e : this) { + for (int i = 0; i < e.getValue(); i++) { + result.add(e.getKey().toForgeCard()); + } + } + return result; + } +} diff --git a/src/main/java/forge/card/CardPrinted.java b/src/main/java/forge/card/CardPrinted.java new file mode 100644 index 00000000000..dab82243c77 --- /dev/null +++ b/src/main/java/forge/card/CardPrinted.java @@ -0,0 +1,166 @@ +package forge.card; + +import java.util.Arrays; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import net.slightlymagic.braids.util.lambda.Lambda1; +import net.slightlymagic.maxmtg.Predicate; +import forge.AllZone; +import forge.Card; + +/** + *

CardReference class.

+ * + * @author Forge + * @version $Id: CardReference.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardPrinted implements Comparable { + // Reference to rules + private final transient CardRules card; + + // These fields are kinda PK for PrintedCard + private final String name; + private final String cardSet; + private final int artIndex; + private final boolean foiled; + + // Calculated fields are below: + private final transient CardRarity rarity; // rarity is given in ctor when set is assigned + + // need this to be sure that different cased names won't break the system (and create uniqie cardref entries) + private final transient String _name_lcase; + + // field RO accessors + public String getName() { return name; } + public String getSet() { return cardSet; } + public int getArtIndex() { return artIndex; } + public boolean isFoil() { return foiled; } + public CardRules getCard() { return card; } + public CardRarity getRarity() { return rarity; } + + + // Lambda to get rules for selects from list of printed cards + public static final Lambda1 fnGetRules = new Lambda1() { + @Override public CardRules apply(final CardPrinted from) { return from.card; } + }; + + // Constructor is private. All non-foiled instances are stored in CardDb + private CardPrinted(final CardRules c, final String set, final CardRarity rare, final int index, boolean foil) { + card = c; + name = c.getName(); + cardSet = set; + artIndex = index; + foiled = foil; + rarity = rare; + _name_lcase = name.toLowerCase(); + } + + /* package visibility */ + static CardPrinted build(final CardRules c, final String set, final CardRarity rare, final int index) { + return new CardPrinted(c, set, rare, index, false); + } + + /* foiled don't need to stay in CardDb's structures, so u'r free to create */ + public static CardPrinted makeFoiled(final CardPrinted c) { + return new CardPrinted(c.card, c.cardSet, c.rarity, c.artIndex, true); + } + + // Want this class to be a key for HashTable + @Override + public boolean equals(final Object obj) { + if (this == obj) { return true; } + if (obj == null) { return false; } + if (getClass() != obj.getClass()) { return false; } + + CardPrinted other = (CardPrinted) obj; + if (!name.equals(other.name)) { return false; } + if (!cardSet.equals(other.cardSet)) { return false; } + if (other.foiled != this.foiled || other.artIndex != this.artIndex) { return false; } + + return true; + } + + @Override + public int hashCode() { + int code = _name_lcase.hashCode() * 11 + cardSet.hashCode() * 59 + artIndex * 2; + if (foiled) { return code + 1; } + return code; + } + + @Override + public String toString() { + return String.format("%s|%s", name, cardSet); + } + + public Card toForgeCard() { + Card c = AllZone.getCardFactory().getCard(name, null); + c.setCurSetCode(getSet()); + return c; + } + + @Override + public int compareTo(final CardPrinted o) { + int nameCmp = _name_lcase.compareTo(o._name_lcase); + if (0 != nameCmp) { return nameCmp; } + // TODO: compare sets properly + return cardSet.compareTo(o.cardSet); + } + + + public static abstract class Predicates { + public static Predicate rarity(final boolean isEqual, final CardRarity value) + { + return new PredicateRarity(value, isEqual); + } + public static Predicate printedInSets(final String[] value) + { + return new PredicateSets(value); + } + + private static class PredicateRarity extends Predicate { + private final CardRarity operand; + private final boolean shouldBeEqual; + + @Override + public boolean isTrue(final CardPrinted card) { + return card.rarity.equals(operand) == shouldBeEqual; + } + + public PredicateRarity(final CardRarity type, final boolean wantEqual) { + operand = type; + shouldBeEqual = wantEqual; + } + } + + private static class PredicateSets extends Predicate { + private final String[] sets; + @Override public boolean isTrue(final CardPrinted card) { + return Arrays.binarySearch(sets, card.rarity) >= 0; + } + public PredicateSets(final String[] wantSets) { + sets = wantSets.clone(); + Arrays.sort(sets); + } + } + + public abstract static class Presets { + // Think twice before using these, since rarity is a prop of printed card. + public static final Predicate isCommon = rarity(true, CardRarity.Common); + public static final Predicate isUncommon = rarity(true, CardRarity.Uncommon); + public static final Predicate isRare = rarity(true, CardRarity.Rare); + public static final Predicate isMythicRare = rarity(true, CardRarity.MythicRare); + public static final Predicate isRareOrMythic = Predicate.or(isRare, isMythicRare); + + public static final Predicate isSpecial = rarity(true, CardRarity.Special); + + public static final Predicate exceptLands = rarity(false, CardRarity.BasicLand); + + // TODO: Update this code on each rotation (or move this list to a file) + public static final Predicate isStandard = printedInSets( + new String[] {"M12", "NPH", "MBS", "SOM", "M11", "ROE", "WWK", "ZEN"}); + + } + } +} diff --git a/src/main/java/forge/card/CardRarity.java b/src/main/java/forge/card/CardRarity.java new file mode 100644 index 00000000000..d22af703acc --- /dev/null +++ b/src/main/java/forge/card/CardRarity.java @@ -0,0 +1,28 @@ +package forge.card; + +/** + *

CardRarity class.

+ * + * @author Forge + * @version $Id: CardRarity.java 9708 2011-08-09 19:34:12Z jendave $ + */ + +public enum CardRarity { + BasicLand(0, "L"), + Common(1, "C"), + Uncommon(2, "U"), + Rare(3, "R"), + MythicRare(4, "M"), + Special(10, "S"); // Timeshifted + + private final int rating; + private final String strValue; + private CardRarity(final int value, final String sValue) { + rating = value; + strValue = sValue; + } + + @Override + public String toString() { return strValue; } + +} diff --git a/src/main/java/forge/card/CardReference.java b/src/main/java/forge/card/CardReference.java deleted file mode 100644 index c63bb0b2d1c..00000000000 --- a/src/main/java/forge/card/CardReference.java +++ /dev/null @@ -1,89 +0,0 @@ -package forge.card; - -import java.security.InvalidParameterException; -import java.util.ArrayList; - -import forge.AllZone; -import forge.Card; -import forge.SetInfo; - -/** - *

CardReference class.

- * - * @author Forge - * @version $Id: CardReference.java 9708 2011-08-09 19:34:12Z jendave $ - */ -public final class CardReference { - private static final boolean ENABLE_CONSISTENCY_CHECK = true; - - private String name; - private String cardSet; - private short pictureNumber = 0; - private boolean foiled = false; - - public CardReference(final String named, final String set, final int picNum, final boolean foil) { - this(named, set, picNum); - foiled = foil; - } - - public CardReference(final String named, final String set, final int picNum) { - this(named, set); - pictureNumber = (short) picNum; - } - - public CardReference(final String named, final String set) { - name = named; - cardSet = set; - - if (set == null || ENABLE_CONSISTENCY_CHECK) { - Card c = AllZone.getCardFactory().getCard(name, null); - if (c == null) { - String error = String.format("Invalid reference! The card named '%s' is unknown to Forge", name); - throw new InvalidParameterException(error); - } - - ArrayList validSets = c.getSets(); - boolean isSetValid = false; - if (cardSet != null) { - for (SetInfo si : validSets) { - if (si.Code.equals(set)) { isSetValid = true; break; } - } - } - - if (!isSetValid) { - cardSet = c.getMostRecentSet(); - // String error = String.format("The card '%s' is not a part of '%s' set", name, set); - // throw new InvalidParameterException(error); - } - } - } - - public CardReference(final String named) { this(named, (String) null); } - - public String getName() { return name; } - public String getSet() { return cardSet; } - public short getPictureIndex() { return pictureNumber; } - public boolean isFoil() { return foiled; } - - // Want this class to be a key for HashTable - @Override - public boolean equals(Object obj) { - if (this == obj) { return true; } - if (obj == null) { return false; } - if (getClass() != obj.getClass()) { return false; } - - CardReference other = (CardReference) obj; - if (!name.equals(other.name)) { return false; } - if (!cardSet.equals(other.cardSet)) { return false; } - if (other.foiled != this.foiled || other.pictureNumber != this.pictureNumber) { return false; } - - return true; - } - - @Override - public int hashCode() { - int code = name.hashCode() * 11 + cardSet.hashCode() * 59 + pictureNumber * 2; - if (foiled) { return code + 1; } - return code; - } -} diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java new file mode 100644 index 00000000000..adb48b0ed0e --- /dev/null +++ b/src/main/java/forge/card/CardRules.java @@ -0,0 +1,348 @@ +package forge.card; + +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; + +import net.slightlymagic.maxmtg.Predicate; +import net.slightlymagic.maxmtg.Predicate.ComparableOp; +import net.slightlymagic.maxmtg.Predicate.PredicatesOp; +import net.slightlymagic.maxmtg.Predicate.StringOp; + +import org.apache.commons.lang3.StringUtils; + +/** + *

CardOracle class.

+ * + * @author Forge + * @version $Id: CardOracle.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardRules { + private final String name; + private final CardType type; + private final CardManaCost cost; + private CardColor color = null; // color is subject to change yet (parse %cardname% is %color% rule) + private final String[] rules; + + private int iPower = -1; + private int iToughness = -1; + private String power = null; + private String toughness = null; + + private String loyalty = null; + + private HashMap setsPrinted = null; + + // Ctor and builders are needed here + public String getName() { return name; } + public CardType getType() { return type; } + public CardManaCost getManaCost() { return cost; } + public CardColor getColor() { return color; } + public String[] getRules() { return rules; } + public Set> getSetsPrinted() { return setsPrinted.entrySet(); } + + public String getPower() { return power; } + public int getIntPower() { return iPower; } + public String getToughness() { return toughness; } + public int getIntToughness() { return iToughness; } + public String getLoyalty() { return loyalty; } + + public String getPTorLoyalty() { + if (getType().isCreature()) { return power + "/" + toughness; } + if (getType().isPlaneswalker()) { return loyalty; } + return ""; + } + + public CardRules(final String cardName, final CardType cardType, final String manacost, + final String ptLine, final String[] cardRules, final String[] setsData) + { + this.name = cardName; + this.type = cardType; + this.cost = manacost == null ? CardManaCost.empty : new CardManaCost(manacost); + this.rules = cardRules; + this.color = new CardColor(cost); + if (cardType.isCreature()) { + int slashPos = ptLine.indexOf('/'); + if (slashPos == -1) { + throw new RuntimeException(String.format("Creature '%s' has bad p/t stats", cardName)); + } + this.power = ptLine.substring(0, slashPos); + this.toughness = ptLine.substring(slashPos + 1, ptLine.length()); + this.iPower = StringUtils.isNumeric(power) ? Integer.parseInt(power) : 0; + this.iToughness = StringUtils.isNumeric(toughness) ? Integer.parseInt(toughness) : 0; + } else if (cardType.isPlaneswalker()) { + this.loyalty = ptLine; + } + + this.setsPrinted = new HashMap(); + for (int iSet = 0; iSet < setsData.length; iSet++) { + String setCode = setsData[iSet].substring(0, setsData[iSet].indexOf(' ')); + setsPrinted.put(setCode, CardInSet.parse(setsData[iSet])); + } + } + + public boolean rulesContain(final String text) { + for (String r : rules) { if (r.contains(text)) { return true; } } + return false; + } + public String getLatestSetPrinted() { + String lastSet = null; + // TODO: Make a true release-date based sorting + for (String cs : setsPrinted.keySet()) { + lastSet = cs; + } + return lastSet; + } + public CardInSet getSetInfo(final String setCode) { + CardInSet result = setsPrinted.get(setCode); + if (result != null) { return result; } + throw new RuntimeException(String.format("Card '%s' was never printed in set '%s'", name, setCode)); + + } + public CardRarity getRarityFromLatestSet() { + CardInSet cis = setsPrinted.get(getLatestSetPrinted()); + return cis.getRarity(); + } + + public abstract static class Predicates { + + // Static builder methods - they choose concrete implementation by themselves + public static Predicate cmc(final ComparableOp op, final int what) + { + return new LeafNumber(LeafNumber.CardField.CMC, op, what); + } + // Power + // Toughness + public static Predicate rules(final StringOp op, final String what) + { + return new LeafString(LeafString.CardField.RULES, op, what); + } + public static Predicate name(final StringOp op, final String what) + { + return new LeafString(LeafString.CardField.NAME, op, what); + } + public static Predicate subType(final StringOp op, final String what) + { + return new LeafString(LeafString.CardField.SUBTYPE, op, what); + } + public static Predicate coreType(final boolean isEqual, final String what) + { + try { return coreType(isEqual, CardCoreType.valueOf(CardCoreType.class, what)); } + catch (Exception e) { return Predicate.getFalse(CardRules.class); } + } + public static Predicate coreType(final boolean isEqual, final CardCoreType type) + { + return new PredicateCoreType(type, isEqual); + } + public static Predicate superType(final boolean isEqual, final String what) + { + try { return superType(isEqual, CardSuperType.valueOf(CardSuperType.class, what)); } + catch (Exception e) { return Predicate.getFalse(CardRules.class); } + } + public static Predicate superType(final boolean isEqual, final CardSuperType type) + { + return new PredicateSuperType(type, isEqual); + } + public static Predicate rarityInCardsLatestSet(final boolean isEqual, final CardRarity value) + { + return new PredicateLastesSetRarity(value, isEqual); + } + public static Predicate hasColor(final byte thatColor) { + return new LeafColor(LeafColor.ColorOperator.HasAllOf, thatColor); + } + public static Predicate isColor(final byte thatColor) { + return new LeafColor(LeafColor.ColorOperator.HasAnyOf, thatColor); + } + public static Predicate hasCntColors(final byte cntColors) { + return new LeafColor(LeafColor.ColorOperator.Equals, cntColors); + } + public static Predicate hasAtLeastCntColors(final byte cntColors) { + return new LeafColor(LeafColor.ColorOperator.CountColorsGreaterOrEqual, cntColors); + } + + private static class LeafString extends Predicate { + public enum CardField { + RULES, + NAME, + SUBTYPE + } + + private final String operand; + private final StringOp operator; + private final CardField field; + + @Override + public boolean isTrue(final CardRules card) { + boolean shouldConatin; + switch (field) { + case NAME: + return op(card.getName(), operand); + case SUBTYPE: + shouldConatin = operator == StringOp.CONTAINS || operator == StringOp.EQUALS; + return shouldConatin == card.getType().subTypeContains(operand); + case RULES: + shouldConatin = operator == StringOp.CONTAINS || operator == StringOp.EQUALS; + return shouldConatin == card.rulesContain(operand); + default: + return false; + } + } + + private boolean op(final String op1, final String op2) { + if (operator == StringOp.CONTAINS) { return op1.contains(op2); } + if (operator == StringOp.NOT_CONTAINS) { return op1.contains(op2); } + if (operator == StringOp.EQUALS) { return op1.equals(op2); } + return false; + } + + public LeafString(final CardField field, final StringOp operator, final String operand) + { + this.field = field; + this.operand = operand; + this.operator = operator; + } + } + + private static class LeafColor extends Predicate { + public enum ColorOperator { + CountColors, + CountColorsGreaterOrEqual, + HasAnyOf, + HasAllOf, + Equals + } + + private final ColorOperator op; + private final byte color; + + public LeafColor(final ColorOperator operator, final byte thatColor) + { + op = operator; + color = thatColor; + } + + @Override + public boolean isTrue(final CardRules subject) { + switch(op) { + case CountColors: return subject.getColor().countColors() == color; + case CountColorsGreaterOrEqual: return subject.getColor().countColors() >= color; + case Equals: return subject.getColor().isEqual(color); + case HasAllOf: return subject.getColor().hasAllColors(color); + case HasAnyOf: return subject.getColor().hasAnyColor(color); + default: return false; + } + } + } + + private static class LeafNumber extends Predicate { + protected enum CardField { + CMC, + POWER, + TOUGHNESS, + } + + private final CardField field; + private final ComparableOp operator; + private final int operand; + + public LeafNumber(final CardField field, final ComparableOp op, final int what) { + this.field = field; + operand = what; + operator = op; + } + + @Override + public boolean isTrue(final CardRules card) { + int value; + switch (field) { + case CMC: return op(card.getManaCost().getCMC(), operand); + case POWER: value = card.getIntPower(); return value >= 0 ? op(value, operand) : false; + case TOUGHNESS: value = card.getIntToughness(); return value >= 0 ? op(value, operand) : false; + default: return false; + } + } + + private boolean op(final int op1, final int op2) { + switch (operator) { + case EQUALS: return op1 == op2; + case GREATER_THAN: return op1 > op2; + case GT_OR_EQUAL: return op1 >= op2; + case LESS_THAN: return op1 < op2; + case LT_OR_EQUAL: return op1 <= op2; + case NOT_EQUALS: return op1 != op2; + default: return false; + } + } + } + + private static class PredicateCoreType extends Predicate { + private final CardCoreType operand; + private final boolean shouldBeEqual; + + @Override + public boolean isTrue(final CardRules card) { return shouldBeEqual == card.getType().typeContains(operand); } + + public PredicateCoreType(final CardCoreType type, final boolean wantEqual) { + operand = type; + shouldBeEqual = wantEqual; + } + } + + private static class PredicateSuperType extends Predicate { + private final CardSuperType operand; + private final boolean shouldBeEqual; + + @Override + public boolean isTrue(final CardRules card) { + return shouldBeEqual == card.getType().superTypeContains(operand); + } + + public PredicateSuperType(final CardSuperType type, final boolean wantEqual) { + operand = type; + shouldBeEqual = wantEqual; + } + } + private static class PredicateLastesSetRarity extends Predicate { + private final CardRarity operand; + private final boolean shouldBeEqual; + + @Override + public boolean isTrue(final CardRules card) { + return card.getRarityFromLatestSet().equals(operand); + } + + public PredicateLastesSetRarity(final CardRarity type, final boolean wantEqual) { + operand = type; + shouldBeEqual = wantEqual; + } + } + + public static class Presets { + public static final Predicate isCreature = coreType(true, CardCoreType.Creature); + public static final Predicate isArtifact = coreType(true, CardCoreType.Artifact); + public static final Predicate isLand = coreType(true, CardCoreType.Land); + public static final Predicate isPlaneswalker = coreType(true, CardCoreType.Planeswalker); + public static final Predicate isInstant = coreType(true, CardCoreType.Instant); + public static final Predicate isSorcery = coreType(true, CardCoreType.Sorcery); + public static final Predicate isEnchantment = coreType(true, CardCoreType.Enchantment); + + public static final Predicate isNonLand = coreType(false, CardCoreType.Land); + public static final Predicate isNonCreatureSpell = Predicate.compose(isCreature, PredicatesOp.NOR, isLand); + + public static final Predicate isWhite = isColor(CardColor.WHITE); + public static final Predicate isBlue = isColor(CardColor.BLUE); + public static final Predicate isBlack = isColor(CardColor.BLACK); + public static final Predicate isRed = isColor(CardColor.RED); + public static final Predicate isGreen = isColor(CardColor.GREEN); + public static final Predicate isColorless = hasCntColors((byte) 0); + public static final Predicate isMulticolor = hasAtLeastCntColors((byte) 2); + + // Think twice before using these, since rarity is a prop of printed card. + public static final Predicate isInLatestSetCommon = rarityInCardsLatestSet(true, CardRarity.Common); + public static final Predicate isInLatestSetUncommon = rarityInCardsLatestSet(true, CardRarity.Uncommon); + public static final Predicate isInLatestSetRare = rarityInCardsLatestSet(true, CardRarity.Rare); + public static final Predicate isInLatestSetMythicRare = rarityInCardsLatestSet(true, CardRarity.MythicRare); + public static final Predicate isInLatestSetSpecial = rarityInCardsLatestSet(true, CardRarity.Special); + } + } +} diff --git a/src/main/java/forge/card/CardSet.java b/src/main/java/forge/card/CardSet.java new file mode 100644 index 00000000000..bf99f678b5d --- /dev/null +++ b/src/main/java/forge/card/CardSet.java @@ -0,0 +1,47 @@ +package forge.card; + +/** + *

CardSet class.

+ * + * @author Forge + * @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $ + */ +public final class CardSet implements Comparable { // immutable + private int index; + private String code; + private String code2; + private String name; + + public CardSet(final int index, final String name, final String code, final String code2) { + this.code = code; + this.code2 = code2; + this.index = index; + this.name = name; + } + + public String getName() { return name; } + public String getCode() { return code; } + public String getCode2() { return code2; } + public int getIndex() { return index; } + + @Override + public int compareTo(final CardSet o) { + if (o == null) { return 1; } + return o.index - this.index; + } + + @Override + public int hashCode() { + return code.hashCode() * 17 + name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { return true; } + if (obj == null) { return false; } + if (getClass() != obj.getClass()) { return false; } + + CardSet other = (CardSet) obj; + return other.name.equals(this.name) && this.code.equals(other.code); + } +} diff --git a/src/main/java/forge/card/CardSuperType.java b/src/main/java/forge/card/CardSuperType.java new file mode 100644 index 00000000000..ec4a3c7d264 --- /dev/null +++ b/src/main/java/forge/card/CardSuperType.java @@ -0,0 +1,6 @@ +package forge.card; + +public enum CardSuperType +{ + Basic, Legendary, Show, Ongoing, World +} diff --git a/src/main/java/forge/card/CardType.java b/src/main/java/forge/card/CardType.java new file mode 100644 index 00000000000..149e153d94a --- /dev/null +++ b/src/main/java/forge/card/CardType.java @@ -0,0 +1,113 @@ +package forge.card; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import org.apache.commons.lang3.StringUtils; + +/** + *

Immutable Card type. Can be build only from parsing a string.

+ * + * @author Forge + * @version $Id: CardType.java 9708 2011-08-09 19:34:12Z jendave $ + */ + +public final class CardType implements Comparable { + private List subType = new ArrayList(); + private EnumSet coreType = EnumSet.noneOf(CardCoreType.class); + private EnumSet superType = EnumSet.noneOf(CardSuperType.class); + private String calculatedType = null; // since obj is immutable, this is calc'd once + + // This will be useful for faster parses + private static HashMap stringToCoreType = new HashMap(); + private static HashMap stringToSuperType = new HashMap(); + static { + for (CardSuperType st : CardSuperType.values()) { stringToSuperType.put(st.name(), st); } + for (CardCoreType ct : CardCoreType.values()) { stringToCoreType.put(ct.name(), ct); } + } + + private CardType() { } // use static ctors! + + // TODO: Debug this code + public static CardType parse(final String typeText) { + // Most types and subtypes, except "Serra�s Realm" and "Bolas�s Meditation Realm" consist of only one word + final char space = ' '; + CardType result = new CardType(); + + int iTypeStart = 0; + int iSpace = typeText.indexOf(space); + boolean hasMoreTypes = typeText.length() > 0; + while (hasMoreTypes) { + String type = typeText.substring(iTypeStart, iSpace == -1 ? typeText.length() : iSpace ); + if (!isMultiwordType(type)) { + iTypeStart = iSpace + 1; + result.parseAndAdd(type); + } + hasMoreTypes = iSpace != -1; + iSpace = typeText.indexOf(space, iSpace + 1); + } + return result; + } + + private static boolean isMultiwordType(final String type) { + final String[] multiWordTypes = {"Serra's Realm", "Bolas's Meditation Realm"}; + // no need to loop for only 2 exceptions! + if (multiWordTypes[0].startsWith(type) && !multiWordTypes[0].equals(type)) { return true; } + if (multiWordTypes[1].startsWith(type) && !multiWordTypes[1].equals(type)) { return true; } + return false; + } + + private void parseAndAdd(final String type) { + if ("-".equals(type)) { return; } + + CardCoreType ct = stringToCoreType.get(type); + if (ct != null) { coreType.add(ct); return; } + + CardSuperType st = stringToSuperType.get(type); + if (st != null) { superType.add(st); return; } + + // If not recognized by super- and core- this must be subtype + subType.add(type); + } + + public boolean subTypeContains(final String operand) { return subType.contains(operand); } + public boolean typeContains(final CardCoreType operand) { return coreType.contains(operand); } + public boolean superTypeContains(final CardSuperType operand) { return superType.contains(operand); } + + public boolean isCreature() { return coreType.contains(CardCoreType.Creature); } + public boolean isPlaneswalker() { return coreType.contains(CardCoreType.Planeswalker); } + public boolean isLand() { return coreType.contains(CardCoreType.Land); } + public boolean isArtifact() { return coreType.contains(CardCoreType.Artifact); } + public boolean isInstant() { return coreType.contains(CardCoreType.Instant); } + public boolean isSorcery() { return coreType.contains(CardCoreType.Sorcery); } + public boolean isEnchantment() { return coreType.contains(CardCoreType.Enchantment); } + + public String getTypesBeforeDash() { + ArrayList types = new ArrayList(); + for (CardSuperType st : superType) { types.add(st.name()); } + for (CardCoreType ct : coreType) { types.add(ct.name()); } + return StringUtils.join(types, ' '); + } + + public String getTypesAfterDash() { + return StringUtils.join(subType, " "); + } + + @Override + public String toString() { + if (null == calculatedType) { calculatedType = toStringImpl(); } + return calculatedType; + } + + private String toStringImpl() { + if (subType.isEmpty()) { return getTypesBeforeDash(); } + else { return String.format("%s - %s", getTypesBeforeDash(), getTypesAfterDash()); } + } + + @Override + public int compareTo(final CardType o) { + return toString().compareTo(o.toString()); + } +} + diff --git a/src/main/java/forge/card/MtgDataParser.java b/src/main/java/forge/card/MtgDataParser.java new file mode 100644 index 00000000000..8ad17798706 --- /dev/null +++ b/src/main/java/forge/card/MtgDataParser.java @@ -0,0 +1,68 @@ +package forge.card; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.lang3.StringUtils; + +public final class MtgDataParser implements Iterator { + + private Iterator it; + public MtgDataParser(final Iterable data) { + it = data.iterator(); + skipSetList(); + } + + private boolean weHaveNext; + private void skipSetList() { + String nextLine = it.next(); + while (nextLine.length() > 0 && it.hasNext()) { + nextLine = it.next(); + } + weHaveNext = it.hasNext(); + } + + @Override + public boolean hasNext() { return weHaveNext; } + + private static final String[] emptyArray = new String[0]; // list.toArray() needs this =( + + @Override + public CardRules next() { + if (!it.hasNext()) { weHaveNext = false; return null; } + String name = it.next(); + if (!it.hasNext()) { weHaveNext = false; return null; } + String manaCost = it.next(); + CardType type = null; + if (manaCost.startsWith("{")) { + if (!it.hasNext()) { weHaveNext = false; return null; } + type = CardType.parse(it.next()); + } else { // Land? + type = CardType.parse(manaCost); + manaCost = null; + } + String ptOrLoyalty = null; + if (type.isCreature() || type.isPlaneswalker()) { + if (!it.hasNext()) { weHaveNext = false; return null; } + ptOrLoyalty = it.next(); + } + + List strs = new ArrayList(); + if (!it.hasNext()) { weHaveNext = false; return null; } + String nextLine = it.next(); + while (StringUtils.isNotBlank(nextLine) && it.hasNext()) { + strs.add(nextLine); + nextLine = it.next(); + } + String[] sets = strs.remove(strs.size() - 1).split(", "); + + return new CardRules(name, type, manaCost, ptOrLoyalty, strs.toArray(emptyArray), sets); + } + + @Override public void remove() { } + + +} diff --git a/src/main/java/forge/properties/NewConstants.java b/src/main/java/forge/properties/NewConstants.java index 3aa7f7936c6..9ba381019c7 100644 --- a/src/main/java/forge/properties/NewConstants.java +++ b/src/main/java/forge/properties/NewConstants.java @@ -101,6 +101,8 @@ public interface NewConstants { /** Constant BOOSTERDATA="boosterdata". */ String BOOSTERDATA = "boosterdata"; + String MTG_DATA = "mtg-data"; + /** Constant IMAGE_BASE="image/base". */ String IMAGE_BASE = "image/base"; /** Constant IMAGE_TOKEN="image/token". */