New lightweight classes for cards management in deck editors and quest mode (no other parts or project changed)

This commit is contained in:
Maxmtg
2011-08-28 02:05:04 +00:00
parent 09b868adde
commit 6c77de9d4c
18 changed files with 1495 additions and 90 deletions

16
.gitattributes vendored
View File

@@ -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/UndoCommand.java svneol=native#text/plain
src/main/java/forge/Upkeep.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/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.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_AlterLife.java svneol=native#text/plain
src/main/java/forge/card/abilityFactory/AbilityFactory_Animate.java svneol=native#text/plain src/main/java/forge/card/abilityFactory/AbilityFactory_Animate.java svneol=native#text/plain

View File

@@ -0,0 +1,66 @@
package forge.card;
import org.apache.tools.ant.taskdefs.Concat;
import forge.Constant;
/**
* <p>CardColor class.</p>
*
* @author Forge
* @version $Id: CardColor.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardColor implements Comparable<CardColor> {
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";
}
}
}

View File

@@ -0,0 +1,7 @@
package forge.card;
public enum CardCoreType
{
Artifact, Creature, Enchantment, Instant, Land, Plane, Planeswalker, Scheme, Sorcery, Tribal, Vanguard
}

View File

@@ -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;
/**
* <p>CardOracleDatabase class.</p>
*
* @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<String, CardRules> cards = new Hashtable<String, CardRules>();
// Here are refs, get them by name
private final Map<String, CardPrinted> uniqueCards = new Hashtable<String, CardPrinted>();
// need this to obtain cardReference by name+set+artindex
private final Map<String, Map<String, CardPrinted[]>> allCardsBySet = new Hashtable<String, Map<String, CardPrinted[]>>();
// this is the same list in flat storage
private final List<CardPrinted> allCardsFlat = new ArrayList<CardPrinted>();
private CardDb() {
List<String> 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<String, CardInSet> s : card.getSetsPrinted()) {
String set = s.getKey();
// get this set storage, if not found, create it!
Map<String, CardPrinted[]> setMap = allCardsBySet.get(set);
if (null == setMap) {
setMap = new Hashtable<String, CardPrinted[]>();
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<String, CardPrinted[]> 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<CardPrinted> getCards(final List<String> names) {
List<CardPrinted> result = new ArrayList<CardPrinted>();
for (String name : names) { result.add(getCard(name)); }
return result;
}
// returns a list of all cards from their respective latest editions
public Iterable<CardPrinted> getAllUniqueCards() {
return uniqueCards.values();
}
public List<CardPrinted> getAllCards() { return allCardsFlat; }
}

View File

@@ -0,0 +1,45 @@
package forge.card;
/**
* <p>CardInSet class.</p>
*
* @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; }
}

View File

@@ -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;
/**
* <p>CardManaCost class.</p>
*
* @author Forge
* @version $Id: CardManaCost.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardManaCost implements Comparable<CardManaCost> {
private final List<CardManaCostShard> shards = new ArrayList<CardManaCostShard>();
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<CardManaCostShard> {
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
}
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,67 @@
package forge.card;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;
/**
* <p>CardPool class.</p>
*
* @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<String> 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<CardPrinted, Integer>) ((Hashtable<CardPrinted, Integer>) (from.cards)).clone();
}
public CardPool(final Iterable<CardPrinted> 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<CardPrinted> cards) {
for (CardPrinted cr : cards) { add(cr); }
isListInSync = false;
}
public void addAll(final Iterable<Entry<CardPrinted, Integer>> map) {
for (Entry<CardPrinted, Integer> e : map) { add(e.getKey(), e.getValue()); }
isListInSync = false;
}
public void addAll(final Entry<CardPrinted, Integer>[] map) {
for (Entry<CardPrinted, Integer> 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<Entry<CardPrinted, Integer>> map) {
for (Entry<CardPrinted, Integer> e : map) { remove(e.getKey(), e.getValue()); }
isListInSync = false;
}
public void clear() { cards.clear(); isListInSync = false; }
}

View File

@@ -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;
/**
* <p>CardPoolView class.</p>
*
* @author Forge
* @version $Id: CardPoolView.java 9708 2011-08-09 19:34:12Z jendave $
*/
public class CardPoolView implements Iterable<Entry<CardPrinted, Integer>> {
// Field Accessors for select/aggregate operations with filters.
public final static Lambda1<CardRules, Entry<CardPrinted, Integer>> fnToCard =
new Lambda1<CardRules, Entry<CardPrinted, Integer>>() {
@Override public CardRules apply(final Entry<CardPrinted, Integer> from) { return from.getKey().getCard(); }
};
public final static Lambda1<CardPrinted, Entry<CardPrinted, Integer>> fnToReference =
new Lambda1<CardPrinted, Entry<CardPrinted, Integer>>() {
@Override public CardPrinted apply(final Entry<CardPrinted, Integer> from) { return from.getKey(); }
};
public final static Lambda1<Integer, Entry<CardPrinted, Integer>> fnToCount =
new Lambda1<Integer, Entry<CardPrinted, Integer>>() {
@Override public Integer apply(final Entry<CardPrinted, Integer> from) { return from.getValue(); }
};
// Constructors
public CardPoolView() { cards = new Hashtable<CardPrinted, Integer>(); }
public CardPoolView(final Map<CardPrinted, Integer> inMap) { cards = inMap; }
// Data members
protected Map<CardPrinted, Integer> cards;
// same thing as above, it was copied to provide sorting (needed by table views in deck editors)
protected List<Entry<CardPrinted, Integer>> cardsListOrdered = new ArrayList<Map.Entry<CardPrinted,Integer>>();
protected boolean isListInSync = false;
@Override
public final Iterator<Entry<CardPrinted, Integer>> 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<Entry<CardPrinted, Integer>> getOrderedList() {
if (!isListInSync) {
cardsListOrdered.clear();
if (cards != null) {
for (Entry<CardPrinted, Integer> e : cards.entrySet()) {
cardsListOrdered.add(e);
}
}
isListInSync = true;
}
return cardsListOrdered;
}
public final List<CardPrinted> toFlatList() {
List<CardPrinted> result = new ArrayList<CardPrinted>();
for (Entry<CardPrinted, Integer> 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<CardPrinted, Integer> e : this) {
for (int i = 0; i < e.getValue(); i++) {
result.add(e.getKey().toForgeCard());
}
}
return result;
}
}

View File

@@ -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;
/**
* <p>CardReference class.</p>
*
* @author Forge
* @version $Id: CardReference.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardPrinted implements Comparable<CardPrinted> {
// 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<CardRules, CardPrinted> fnGetRules = new Lambda1<CardRules, CardPrinted>() {
@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<CardPrinted> rarity(final boolean isEqual, final CardRarity value)
{
return new PredicateRarity(value, isEqual);
}
public static Predicate<CardPrinted> printedInSets(final String[] value)
{
return new PredicateSets(value);
}
private static class PredicateRarity extends Predicate<CardPrinted> {
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<CardPrinted> {
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<CardPrinted> isCommon = rarity(true, CardRarity.Common);
public static final Predicate<CardPrinted> isUncommon = rarity(true, CardRarity.Uncommon);
public static final Predicate<CardPrinted> isRare = rarity(true, CardRarity.Rare);
public static final Predicate<CardPrinted> isMythicRare = rarity(true, CardRarity.MythicRare);
public static final Predicate<CardPrinted> isRareOrMythic = Predicate.or(isRare, isMythicRare);
public static final Predicate<CardPrinted> isSpecial = rarity(true, CardRarity.Special);
public static final Predicate<CardPrinted> exceptLands = rarity(false, CardRarity.BasicLand);
// TODO: Update this code on each rotation (or move this list to a file)
public static final Predicate<CardPrinted> isStandard = printedInSets(
new String[] {"M12", "NPH", "MBS", "SOM", "M11", "ROE", "WWK", "ZEN"});
}
}
}

View File

@@ -0,0 +1,28 @@
package forge.card;
/**
* <p>CardRarity class.</p>
*
* @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; }
}

View File

@@ -1,89 +0,0 @@
package forge.card;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import forge.AllZone;
import forge.Card;
import forge.SetInfo;
/**
* <p>CardReference class.</p>
*
* @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<SetInfo> 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;
}
}

View File

@@ -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;
/**
* <p>CardOracle class.</p>
*
* @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<String, CardInSet> 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<Entry<String, CardInSet>> 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<String, CardInSet>();
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<CardRules> cmc(final ComparableOp op, final int what)
{
return new LeafNumber(LeafNumber.CardField.CMC, op, what);
}
// Power
// Toughness
public static Predicate<CardRules> rules(final StringOp op, final String what)
{
return new LeafString(LeafString.CardField.RULES, op, what);
}
public static Predicate<CardRules> name(final StringOp op, final String what)
{
return new LeafString(LeafString.CardField.NAME, op, what);
}
public static Predicate<CardRules> subType(final StringOp op, final String what)
{
return new LeafString(LeafString.CardField.SUBTYPE, op, what);
}
public static Predicate<CardRules> 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<CardRules> coreType(final boolean isEqual, final CardCoreType type)
{
return new PredicateCoreType(type, isEqual);
}
public static Predicate<CardRules> 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<CardRules> superType(final boolean isEqual, final CardSuperType type)
{
return new PredicateSuperType(type, isEqual);
}
public static Predicate<CardRules> rarityInCardsLatestSet(final boolean isEqual, final CardRarity value)
{
return new PredicateLastesSetRarity(value, isEqual);
}
public static Predicate<CardRules> hasColor(final byte thatColor) {
return new LeafColor(LeafColor.ColorOperator.HasAllOf, thatColor);
}
public static Predicate<CardRules> isColor(final byte thatColor) {
return new LeafColor(LeafColor.ColorOperator.HasAnyOf, thatColor);
}
public static Predicate<CardRules> hasCntColors(final byte cntColors) {
return new LeafColor(LeafColor.ColorOperator.Equals, cntColors);
}
public static Predicate<CardRules> hasAtLeastCntColors(final byte cntColors) {
return new LeafColor(LeafColor.ColorOperator.CountColorsGreaterOrEqual, cntColors);
}
private static class LeafString extends Predicate<CardRules> {
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<CardRules> {
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<CardRules> {
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<CardRules> {
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<CardRules> {
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<CardRules> {
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<CardRules> isCreature = coreType(true, CardCoreType.Creature);
public static final Predicate<CardRules> isArtifact = coreType(true, CardCoreType.Artifact);
public static final Predicate<CardRules> isLand = coreType(true, CardCoreType.Land);
public static final Predicate<CardRules> isPlaneswalker = coreType(true, CardCoreType.Planeswalker);
public static final Predicate<CardRules> isInstant = coreType(true, CardCoreType.Instant);
public static final Predicate<CardRules> isSorcery = coreType(true, CardCoreType.Sorcery);
public static final Predicate<CardRules> isEnchantment = coreType(true, CardCoreType.Enchantment);
public static final Predicate<CardRules> isNonLand = coreType(false, CardCoreType.Land);
public static final Predicate<CardRules> isNonCreatureSpell = Predicate.compose(isCreature, PredicatesOp.NOR, isLand);
public static final Predicate<CardRules> isWhite = isColor(CardColor.WHITE);
public static final Predicate<CardRules> isBlue = isColor(CardColor.BLUE);
public static final Predicate<CardRules> isBlack = isColor(CardColor.BLACK);
public static final Predicate<CardRules> isRed = isColor(CardColor.RED);
public static final Predicate<CardRules> isGreen = isColor(CardColor.GREEN);
public static final Predicate<CardRules> isColorless = hasCntColors((byte) 0);
public static final Predicate<CardRules> isMulticolor = hasAtLeastCntColors((byte) 2);
// Think twice before using these, since rarity is a prop of printed card.
public static final Predicate<CardRules> isInLatestSetCommon = rarityInCardsLatestSet(true, CardRarity.Common);
public static final Predicate<CardRules> isInLatestSetUncommon = rarityInCardsLatestSet(true, CardRarity.Uncommon);
public static final Predicate<CardRules> isInLatestSetRare = rarityInCardsLatestSet(true, CardRarity.Rare);
public static final Predicate<CardRules> isInLatestSetMythicRare = rarityInCardsLatestSet(true, CardRarity.MythicRare);
public static final Predicate<CardRules> isInLatestSetSpecial = rarityInCardsLatestSet(true, CardRarity.Special);
}
}
}

View File

@@ -0,0 +1,47 @@
package forge.card;
/**
* <p>CardSet class.</p>
*
* @author Forge
* @version $Id: CardSet.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardSet implements Comparable<CardSet> { // 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);
}
}

View File

@@ -0,0 +1,6 @@
package forge.card;
public enum CardSuperType
{
Basic, Legendary, Show, Ongoing, World
}

View File

@@ -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;
/**
* <p>Immutable Card type. Can be build only from parsing a string.</p>
*
* @author Forge
* @version $Id: CardType.java 9708 2011-08-09 19:34:12Z jendave $
*/
public final class CardType implements Comparable<CardType> {
private List<String> subType = new ArrayList<String>();
private EnumSet<CardCoreType> coreType = EnumSet.noneOf(CardCoreType.class);
private EnumSet<CardSuperType> 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<String, CardCoreType> stringToCoreType = new HashMap<String, CardCoreType>();
private static HashMap<String, CardSuperType> stringToSuperType = new HashMap<String, CardSuperType>();
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<72>s Realm" and "Bolas<61>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<String> types = new ArrayList<String>();
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());
}
}

View File

@@ -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<CardRules> {
private Iterator<String> it;
public MtgDataParser(final Iterable<String> 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<String> strs = new ArrayList<String>();
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() { }
}

View File

@@ -101,6 +101,8 @@ public interface NewConstants {
/** Constant <code>BOOSTERDATA="boosterdata"</code>. */ /** Constant <code>BOOSTERDATA="boosterdata"</code>. */
String BOOSTERDATA = "boosterdata"; String BOOSTERDATA = "boosterdata";
String MTG_DATA = "mtg-data";
/** Constant <code>IMAGE_BASE="image/base"</code>. */ /** Constant <code>IMAGE_BASE="image/base"</code>. */
String IMAGE_BASE = "image/base"; String IMAGE_BASE = "image/base";
/** Constant <code>IMAGE_TOKEN="image/token"</code>. */ /** Constant <code>IMAGE_TOKEN="image/token"</code>. */