From bf29c3d96160eeaa64f60a512a0a9f4d7c85167a Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Wed, 24 Jun 2015 18:58:15 +0200 Subject: Mulling over the generic comparator issue --- README.md | 3 +- src/eu/equalparts/cardbase/Cardbase.java | 19 ++- src/eu/equalparts/cardbase/cli/CardbaseCLI.java | 20 +-- .../cardbase/comparator/CardComparator.java | 126 +++++++++++++++ .../cardbase/comparator/ComparatorDelegates.java | 39 +++++ .../cardbase/comparator/SpecialFields.java | 8 + .../cardbase/comparators/CardComparators.java | 76 --------- src/eu/equalparts/cardbase/data/Card.java | 3 + .../equalparts/test/cardbase/CardbaseSortTest.java | 169 +++++++++++++++++++++ test/eu/equalparts/test/cardbase/testbase.cb | 1 + test/eu/equalparts/test/cardbase/testcards.json | 1 + test/eu/equalparts/test/ui/CLITest.java | 34 ----- test/eu/equalparts/test/ui/CardbaseCLITest.java | 34 +++++ 13 files changed, 407 insertions(+), 126 deletions(-) create mode 100644 src/eu/equalparts/cardbase/comparator/CardComparator.java create mode 100644 src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java create mode 100644 src/eu/equalparts/cardbase/comparator/SpecialFields.java delete mode 100644 src/eu/equalparts/cardbase/comparators/CardComparators.java create mode 100644 test/eu/equalparts/test/cardbase/CardbaseSortTest.java create mode 100644 test/eu/equalparts/test/cardbase/testbase.cb create mode 100644 test/eu/equalparts/test/cardbase/testcards.json delete mode 100644 test/eu/equalparts/test/ui/CLITest.java create mode 100644 test/eu/equalparts/test/ui/CardbaseCLITest.java diff --git a/README.md b/README.md index 629718f..18f1a05 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # cardbase -The primary purpose of cardbase is to create a digital representation of your own MTG collection. A cardbase file (.cb) is simply a JSON file containing an array of card objects. The card data structure used is a superset of that used in [MTG JSON](http://mtgjson.com/), and in fact that is where most of the data comes from. +The primary purpose of cardbase is to create a digital representation of your own MTG collection. A cardbase file (.cb) is simply a JSON file containing a list of card objects and associated unique keys. The card data structure used is derived from that used in [MTG JSON](http://mtgjson.com/), and in fact that is where most of the data comes from right now. To-do list: * Build basic GUI to visualise the cards -* Allow sorting by field * Basic deck building - decks can be built and stored * Statistical deck analysis - cost distribution, etc diff --git a/src/eu/equalparts/cardbase/Cardbase.java b/src/eu/equalparts/cardbase/Cardbase.java index 211f865..9245870 100644 --- a/src/eu/equalparts/cardbase/Cardbase.java +++ b/src/eu/equalparts/cardbase/Cardbase.java @@ -2,9 +2,11 @@ package eu.equalparts.cardbase; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonGenerationException; @@ -12,11 +14,12 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; +import eu.equalparts.cardbase.comparator.CardComparator; import eu.equalparts.cardbase.data.Card; import eu.equalparts.cardbase.utils.JSON; /** - * Provides a variety of utility methods to interact with the loaded cardbase. + * Provides a variety of utility methods to interact with an optionally loaded cardbase. * * @author Eduardo Pedroni */ @@ -32,7 +35,6 @@ public class Cardbase { * information to be printed to the console. */ public static final boolean DEBUG = System.getenv("CB_DEBUG") != null; - /** * Used in the hash generation. */ @@ -133,13 +135,23 @@ public class Cardbase { return Collections.unmodifiableCollection(cards.values()); } + /** + * @param field the name of the field by which to sort. + * @return an unmodifiable collection representing the cardbase sorted in the required order. + * @throws NoSuchFieldException if the field provided is invalid. + */ + public Collection sort(String field) throws NoSuchFieldException { + List sortedCards = new ArrayList(cards.values()); + sortedCards.sort(new CardComparator(Card.class.getDeclaredField(field))); + return Collections.unmodifiableCollection(sortedCards); + } + /** * Returns a card from the cardbase by set code and number. * If no such card is in the cardbase, returns null. * * @param setCode the set to which the requested card belongs. * @param number the requested card's set number. - * key * @return the requested {@code Card} or null if no card is found. */ public Card getCard(String setCode, String number) { @@ -152,7 +164,6 @@ public class Cardbase { * If no such card is in the cardbase, returns null. * * @param hash the Cardbase hash of the requested card. - * * @return the requested {@code Card} or null if no card is found. */ public Card getCardFromHash(String hash) { diff --git a/src/eu/equalparts/cardbase/cli/CardbaseCLI.java b/src/eu/equalparts/cardbase/cli/CardbaseCLI.java index 7a2e581..6a7f013 100644 --- a/src/eu/equalparts/cardbase/cli/CardbaseCLI.java +++ b/src/eu/equalparts/cardbase/cli/CardbaseCLI.java @@ -96,15 +96,15 @@ public class CardbaseCLI { * * @param args a list of arguments. Only the first argument is used, as a cardbase JSON. */ - private CardbaseCLI(String... args) { + public CardbaseCLI(String... args) { System.out.println("Welcome to Cardbase CLI!"); // set debug flag if we are debugging if (Cardbase.DEBUG) System.out.println("Debug mode is on."); - // make the CardbaseManager - if (args.length > 0) { - File cardbaseFile = new File(args[0]); + // make the Cardbase + if (args != null && args.length > 0) { + cardbaseFile = new File(args[0]); if (cardbaseFile.exists() && cardbaseFile.isFile() && cardbaseFile.canRead()) { System.out.println("Loading cardbase from \"" + args[0] + "\"."); try { @@ -124,7 +124,7 @@ public class CardbaseCLI { System.exit(1); } } else { - System.out.println(args[0] + " appears to be invalid."); + System.out.println(args[0] + " appears to be an invalid path."); System.exit(0); } } else { @@ -202,7 +202,7 @@ public class CardbaseCLI { public void write(String[] args) { File outputFile; // user-provided file overrides everything else - if (args.length > 0) { + if (args != null && args.length > 0) { outputFile = new File(sanitiseFileName(args[0])); } else { outputFile = cardbaseFile; @@ -261,7 +261,7 @@ public class CardbaseCLI { * @param args the code of the chosen set. */ public void set(String[] args) { - if (args.length > 0) { + if (args != null && args.length > 0) { try { selectedSet = MTGUniverse.getFullCardSet(args[0]); // if the set code is invalid, null is returned @@ -306,7 +306,7 @@ public class CardbaseCLI { */ public void peruse(String[] args) { // if a card is specified, peruse only that - if (args.length > 0) { + if (args != null && args.length > 0) { if (selectedSet != null) { Card card = cardbase.getCard(selectedSet.code, args[0]); if (card != null) { @@ -352,7 +352,7 @@ public class CardbaseCLI { */ public void remove(String[] args) { if (selectedSet != null) { - if (args.length > 0) { + if (args != null && args.length > 0) { Card cardToRemove = selectedSet.getCardByNumber(args[0]); if (cardToRemove != null) { Integer count = 1; @@ -391,7 +391,7 @@ public class CardbaseCLI { Card cardToAdd = selectedSet.getCardByNumber(number); if (cardToAdd != null) { Integer count = 1; - if (args.length > 0 && args[0].matches("[0-9]+")) { + if (args != null && args.length > 0 && args[0].matches("[0-9]+")) { count = Integer.valueOf(args[0]); if (count <= 0) { System.out.println("Can't add " + count + " cards."); diff --git a/src/eu/equalparts/cardbase/comparator/CardComparator.java b/src/eu/equalparts/cardbase/comparator/CardComparator.java new file mode 100644 index 0000000..78200b8 --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/CardComparator.java @@ -0,0 +1,126 @@ +package eu.equalparts.cardbase.comparator; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Comparator; +import java.util.function.BiFunction; + +import eu.equalparts.cardbase.Cardbase; +import eu.equalparts.cardbase.data.Card; + +/** + * I'm new to this reflection business, so bear with me. + *

+ * The idea here is to avoid having to write one class + * for each comparable field in {@code Card}. The program + * can dynamically instantiate them as cards are compared + * by different fields. + *

+ * This class uses reflection to determine if the specified + * field is comparable with itself upon construction, and throws + * an {@code IllegalArgumentException} if that is not the case. + * + * @author Eduardo Pedroni + * + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class CardComparator implements Comparator { + + public enum Order { + NATURAL, REVERSE; + } + + /** + * The field being compared. + */ + private Field fieldToCompare; + + private BiFunction comparisonDelegate = this::defaultComparison; + + /** + * Creates a new comparator for the specified field only. This class + * will only be constructed successfully if the field comes from + * {@code Card} and can be compared to itself (i.e. implements + * {@code Comparable} where T is its own type. + *
+ * For reference, {@code String} and {@code Integer} are both self comparable. + * + * @param fieldToCompare the field this comparator will use to compare cards, as declared in {@code Card}. + */ + public CardComparator(Field fieldToCompare) { + if (fieldToCompare.getDeclaringClass().equals(Card.class) && + isSelfComparable(fieldToCompare.getType())) { + + this.fieldToCompare = fieldToCompare; + + // if annotated with a special comparator, set the comparison delegate here + this.comparisonDelegate = ComparatorDelegates::compareManaCost; + + } else { + System.out.println(fieldToCompare.isAccessible()); + System.out.println(fieldToCompare.getDeclaringClass().equals(Card.class)); + System.out.println(isSelfComparable(fieldToCompare.getType())); + throw new IllegalArgumentException("The field provided is not valid."); + } + } + + @Override + public int compare(Card o1, Card o2) { + /* + * we've already checked that the field is self comparable, + * so we are now free to cast to whatever type it is and compare. + */ + try { + Comparable field1 = (Comparable) fieldToCompare.get(o1); + Comparable field2 = (Comparable) fieldToCompare.get(o2); + + return comparisonDelegate.apply(field1, field2); + } catch (IllegalArgumentException e) { + System.out.println("Error: class Card does not define field" + fieldToCompare.getName() + "."); + if (Cardbase.DEBUG) e.printStackTrace(); + } catch (IllegalAccessException e) { + System.out.println("Error: field " + fieldToCompare.getName() + " in Card is not visible."); + if (Cardbase.DEBUG) e.printStackTrace(); + } + + // fallback, this shouldn't happen + return 0; + } + + /** + * The standard comparison operation, which uses the field's own {@code compareTo()} + * method. + * + * @param field1 the first object to be compared. + * @param field2 the second object to be compared. + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + private int defaultComparison(Comparable field1, Comparable field2) { + return field1.compareTo(field2); + } + + /** + * Use reflection to determine if the specified class can be compared with itself. + * + * @param type the type to analyse. + * @return true if the type can be compared to itself using {@code compareTo()}, false otherwise. + */ + private boolean isSelfComparable(Class type) { + + // go through all interfaces implemented by this class + for (Type implementedInterface : type.getGenericInterfaces()) { + // check if any parameterised interface found is "Comparable" + if (implementedInterface instanceof ParameterizedType) { + ParameterizedType genericInterface = (ParameterizedType) implementedInterface; + if (genericInterface.getRawType().equals(Comparable.class)) { + // check that the type argument of comparable is the same as the field type itself + return genericInterface.getActualTypeArguments()[0].equals(type); + } + } + } + return false; + } +} diff --git a/src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java b/src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java new file mode 100644 index 0000000..5736381 --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/ComparatorDelegates.java @@ -0,0 +1,39 @@ +package eu.equalparts.cardbase.comparator; + + +abstract class ComparatorDelegates { + + private ComparatorDelegates() {} + + public static Integer compareManaCost(Comparable field1, Comparable field2) { + // avoid casting syntax nightmare + String mc1 = (String) field1, mc2 = (String) field2; + + // first by number of colours + int mc1count = 0, mc2count = 0; + if (mc1.contains("W")) mc1count++; + if (mc1.contains("U")) mc1count++; + if (mc1.contains("B")) mc1count++; + if (mc1.contains("R")) mc1count++; + if (mc1.contains("G")) mc1count++; + + if (mc2.contains("W")) mc2count++; + if (mc2.contains("U")) mc2count++; + if (mc2.contains("B")) mc2count++; + if (mc2.contains("R")) mc2count++; + if (mc2.contains("G")) mc2count++; + + if (mc1count != mc2count) + return (mc1count < mc2count) ? -1 : ((mc1count == mc2count) ? 0 : 1); + + // next by colour wheel + + return 0; + } + +} +/* + * first by number of colours + * next by colour wheel: white > blue > black > red > green + * next by cmc + */ \ No newline at end of file diff --git a/src/eu/equalparts/cardbase/comparator/SpecialFields.java b/src/eu/equalparts/cardbase/comparator/SpecialFields.java new file mode 100644 index 0000000..8ecc82c --- /dev/null +++ b/src/eu/equalparts/cardbase/comparator/SpecialFields.java @@ -0,0 +1,8 @@ +package eu.equalparts.cardbase.comparator; + + +public class SpecialFields { + + public @interface ManaCost {} + +} diff --git a/src/eu/equalparts/cardbase/comparators/CardComparators.java b/src/eu/equalparts/cardbase/comparators/CardComparators.java deleted file mode 100644 index 99f7ded..0000000 --- a/src/eu/equalparts/cardbase/comparators/CardComparators.java +++ /dev/null @@ -1,76 +0,0 @@ -package eu.equalparts.cardbase.comparators; - -import java.util.Comparator; - -import eu.equalparts.cardbase.data.Card; - -public final class CardComparators { - - private CardComparators() {} - - public enum Order { - NATURAL, REVERSE; - } - - public static class NameComparator implements Comparator { - - private Order order = Order.NATURAL; - - public NameComparator() {} - - public NameComparator(Order order) { - this.order = order; - } - - @Override - public int compare(Card o1, Card o2) { - if (order == Order.NATURAL) { - return o1.name.compareTo(o2.name); - } else { - return o2.name.compareTo(o1.name); - } - - } - } - - public static class LayoutComparator implements Comparator { - - private Order order = Order.NATURAL; - - public LayoutComparator() {} - - public LayoutComparator(Order order) { - this.order = order; - } - - @Override - public int compare(Card o1, Card o2) { - if (order == Order.NATURAL) { - return o1.layout.compareTo(o2.layout); - } else { - return o2.layout.compareTo(o1.layout); - } - } - } - - public static class ManaCostComparator implements Comparator { - - private Order order = Order.NATURAL; - - public ManaCostComparator() {} - - public ManaCostComparator(Order order) { - this.order = order; - } - - @Override - public int compare(Card o1, Card o2) { - if (order == Order.NATURAL) { - return o1.manaCost.compareTo(o2.manaCost); - } else { - return o2.manaCost.compareTo(o1.manaCost); - } - } - } - -} diff --git a/src/eu/equalparts/cardbase/data/Card.java b/src/eu/equalparts/cardbase/data/Card.java index 2810564..7202660 100644 --- a/src/eu/equalparts/cardbase/data/Card.java +++ b/src/eu/equalparts/cardbase/data/Card.java @@ -1,9 +1,12 @@ package eu.equalparts.cardbase.data; +import eu.equalparts.cardbase.comparator.SpecialFields.ManaCost; + public class Card { public String name; public String layout; + @ManaCost public String manaCost; public Integer cmc; public String type; diff --git a/test/eu/equalparts/test/cardbase/CardbaseSortTest.java b/test/eu/equalparts/test/cardbase/CardbaseSortTest.java new file mode 100644 index 0000000..261451a --- /dev/null +++ b/test/eu/equalparts/test/cardbase/CardbaseSortTest.java @@ -0,0 +1,169 @@ +package eu.equalparts.test.cardbase; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collection; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; + +import eu.equalparts.cardbase.Cardbase; +import eu.equalparts.cardbase.data.Card; +import eu.equalparts.cardbase.utils.JSON; + +/** + * Tests the sorting functionality. + * + * @author Eduardo Pedroni + * + */ +public class CardbaseSortTest { + + private Cardbase cardbase; + private static List testCards; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + testCards = JSON.mapper.readValue(CardbaseSortTest.class.getResourceAsStream("testcards.json"), new TypeReference>() {}); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + cardbase = new Cardbase(); + for (Card card : testCards) { + cardbase.addCard(card, 1); + } + } + + @Test + public void test_sortByName() throws Exception { + + Collection sortedCards = cardbase.sort("name"); + + int i = 0; + String[] names = {"Callow Jushi", + "Coerced Confession", + "Disrupting Shoal", + "Khalni Hydra", + "Nightmare", + "Shivan Dragon", + "Sorin Markov", + "Ugin's Construct" }; + + for (Card card : sortedCards) { + assertTrue(card.name + " should have been " + names[i] + ", i = " + i, card.name.equals(names[i])); + i++; + } + } + + @Test + public void test_sortByLayout() throws Exception { + + Collection sortedCards = cardbase.sort("layout"); + + int i = 0; + String[] layouts = {"flip", + "normal", + "normal", + "normal", + "normal", + "normal", + "normal", + "normal" }; + + for (Card card : sortedCards) { + assertTrue(card.layout + " should have been " + layouts[i] + ", i = " + i, card.layout.equals(layouts[i])); + i++; + } + } + + @Test + public void test_sortByManaCost() throws Exception { + + + +// Collection sortedCards = cardbase.sort("name"); +// +// int i = 0; +// String[] layouts = {"flip", +// "normal", +// "normal", +// "normal", +// "normal", +// "normal", +// "normal", +// "normal" }; +// +// for (Card card : sortedCards) { +// assertTrue(card.layout + " should have been " + layouts[i] + ", i = " + i, card.layout.equals(layouts[i])); +// i++; +// } + fail("todo"); + } + + @Test + public void test_sortByCMC() throws Exception { + + Collection sortedCards = cardbase.sort("cmc"); + + int i = 0; + Integer[] cmcs = {2, 3, 4, 5, 6, 6, 6, 8}; + + for (Card card : sortedCards) { + assertTrue(card.cmc + " should have been " + cmcs[i] + ", i = " + i, card.cmc.equals(cmcs[i])); + i++; + } + } + + @Test + public void test_sortByType() throws Exception { + + Collection sortedCards = cardbase.sort("type"); + + int i = 0; + String[] types = {"Artifact Creature — Construct", + "Creature — Dragon", + "Creature — Human Wizard", + "Creature — Hydra", + "Creature — Nightmare Horse", + "Instant — Arcane", + "Planeswalker — Sorin", + "Sorcery"}; + + for (Card card : sortedCards) { + assertTrue(card.type + " should have been " + types[i] + ", i = " + i, card.type.equals(types[i])); + i++; + } + } + + @Test + public void test_sortByRarity() throws Exception { + + Collection sortedCards = cardbase.sort("rarity"); + + int i = 0; + String[] rarities = {"Uncommon", + "Uncommon", + "Uncommon", + "Rare", + "Rare", + "Rare", + "Mythic Rare", + "Mythic Rare"}; + + for (Card card : sortedCards) { + assertTrue(card.rarity + " should have been " + rarities[i] + ", i = " + i, card.rarity.equals(rarities[i])); + i++; + } + } +} diff --git a/test/eu/equalparts/test/cardbase/testbase.cb b/test/eu/equalparts/test/cardbase/testbase.cb new file mode 100644 index 0000000..89b3bfb --- /dev/null +++ b/test/eu/equalparts/test/cardbase/testbase.cb @@ -0,0 +1 @@ +{"M15276":{"name":"Nightmare","layout":"normal","manaCost":"{5}{B}","cmc":6,"type":"Creature — Nightmare Horse","rarity":"Rare","text":"Flying (This creature can't be blocked except by creatures with flying or reach.)\nNightmare's power and toughness are each equal to the number of Swamps you control.","flavor":"The thunder of its hooves beats dreams into despair.","artist":"Vance Kovacs","number":"276","power":"*","toughness":"*","loyalty":null,"multiverseid":383168,"imageName":"nightmare","border":null,"watermark":null,"setCode":"M15","imageCode":"m15","count":1},"FRF164":{"name":"Ugin's Construct","layout":"normal","manaCost":"{4}","cmc":4,"type":"Artifact Creature — Construct","rarity":"Uncommon","text":"When Ugin's Construct enters the battlefield, sacrifice a permanent that's one or more colors.","flavor":"While trapping the Eldrazi on Zendikar, Ugin learned little from Sorin, but he gleaned the rudiments of lithomancy from Nahiri.","artist":"Peter Mohrbacher","number":"164","power":"4","toughness":"5","loyalty":null,"multiverseid":391949,"imageName":"ugin's construct","border":null,"watermark":null,"setCode":"FRF","imageCode":"frf","count":1},"M12109":{"name":"Sorin Markov","layout":"normal","manaCost":"{3}{B}{B}{B}","cmc":6,"type":"Planeswalker — Sorin","rarity":"Mythic Rare","text":"+2: Sorin Markov deals 2 damage to target creature or player and you gain 2 life.\n−3: Target opponent's life total becomes 10.\n−7: You control target player during that player's next turn.","flavor":null,"artist":"Michael Komarck","number":"109","power":null,"toughness":null,"loyalty":4,"multiverseid":238330,"imageName":"sorin markov","border":null,"watermark":null,"setCode":"M12","imageCode":"m12","count":1},"ROE192":{"name":"Khalni Hydra","layout":"normal","manaCost":"{G}{G}{G}{G}{G}{G}{G}{G}","cmc":8,"type":"Creature — Hydra","rarity":"Mythic Rare","text":"Khalni Hydra costs {G} less to cast for each green creature you control.\nTrample","flavor":"\"In ages past, bargains were struck and promises were made. Now we must collect on our debt. Begin the hymns.\"\n—Moruul, Khalni druid","artist":"Todd Lockwood","number":"192","power":"8","toughness":"8","loyalty":null,"multiverseid":193551,"imageName":"khalni hydra","border":null,"watermark":null,"setCode":"ROE","imageCode":"roe","count":1},"BOK33":{"name":"Disrupting Shoal","layout":"normal","manaCost":"{X}{U}{U}","cmc":2,"type":"Instant — Arcane","rarity":"Rare","text":"You may exile a blue card with converted mana cost X from your hand rather than pay Disrupting Shoal's mana cost.\nCounter target spell if its converted mana cost is X.","flavor":null,"artist":"Scott M. Fischer","number":"33","power":null,"toughness":null,"loyalty":null,"multiverseid":74128,"imageName":"disrupting shoal","border":null,"watermark":null,"setCode":"BOK","imageCode":"bok","count":1},"M15281":{"name":"Shivan Dragon","layout":"normal","manaCost":"{4}{R}{R}","cmc":6,"type":"Creature — Dragon","rarity":"Rare","text":"Flying (This creature can't be blocked except by creatures with flying or reach.)\n{R}: Shivan Dragon gets +1/+0 until end of turn.","flavor":"The undisputed master of the mountains of Shiv.","artist":"Donato Giancola","number":"281","power":"5","toughness":"5","loyalty":null,"multiverseid":383172,"imageName":"shivan dragon","border":null,"watermark":null,"setCode":"M15","imageCode":"m15","count":1},"BOK31a":{"name":"Callow Jushi","layout":"flip","manaCost":"{1}{U}{U}","cmc":3,"type":"Creature — Human Wizard","rarity":"Uncommon","text":"Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Callow Jushi.\nAt the beginning of the end step, if there are two or more ki counters on Callow Jushi, you may flip it.","flavor":null,"artist":"Tsutomu Kawade","number":"31a","power":"2","toughness":"2","loyalty":null,"multiverseid":74489,"imageName":"callow jushi","border":null,"watermark":null,"setCode":"BOK","imageCode":"bok","count":1},"GTC217":{"name":"Coerced Confession","layout":"normal","manaCost":"{4}{U/B}","cmc":5,"type":"Sorcery","rarity":"Uncommon","text":"Target player puts the top four cards of his or her library into his or her graveyard. You draw a card for each creature card put into that graveyard this way.","flavor":"\"Ask the right questions in the right way and truth is inevitable.\"\n—Lazav","artist":"Mathias Kollros","number":"217","power":null,"toughness":null,"loyalty":null,"multiverseid":366408,"imageName":"coerced confession","border":null,"watermark":"Dimir","setCode":"GTC","imageCode":"gtc","count":1}} \ No newline at end of file diff --git a/test/eu/equalparts/test/cardbase/testcards.json b/test/eu/equalparts/test/cardbase/testcards.json new file mode 100644 index 0000000..71c8683 --- /dev/null +++ b/test/eu/equalparts/test/cardbase/testcards.json @@ -0,0 +1 @@ +[{"name":"Callow Jushi","layout":"flip","manaCost":"{1}{U}{U}","cmc":3,"type":"Creature — Human Wizard","rarity":"Uncommon","text":"Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Callow Jushi.\nAt the beginning of the end step, if there are two or more ki counters on Callow Jushi, you may flip it.","flavor":null,"artist":"Tsutomu Kawade","number":"31a","power":"2","toughness":"2","loyalty":null,"multiverseid":74489,"imageName":"callow jushi","border":null,"watermark":null,"setCode":"BOK","imageCode":"bok","count":null},{"name":"Coerced Confession","layout":"normal","manaCost":"{4}{U/B}","cmc":5,"type":"Sorcery","rarity":"Uncommon","text":"Target player puts the top four cards of his or her library into his or her graveyard. You draw a card for each creature card put into that graveyard this way.","flavor":"\"Ask the right questions in the right way and truth is inevitable.\"\n—Lazav","artist":"Mathias Kollros","number":"217","power":null,"toughness":null,"loyalty":null,"multiverseid":366408,"imageName":"coerced confession","border":null,"watermark":"Dimir","setCode":"GTC","imageCode":"gtc","count":null},{"name":"Khalni Hydra","layout":"normal","manaCost":"{G}{G}{G}{G}{G}{G}{G}{G}","cmc":8,"type":"Creature — Hydra","rarity":"Mythic Rare","text":"Khalni Hydra costs {G} less to cast for each green creature you control.\nTrample","flavor":"\"In ages past, bargains were struck and promises were made. Now we must collect on our debt. Begin the hymns.\"\n—Moruul, Khalni druid","artist":"Todd Lockwood","number":"192","power":"8","toughness":"8","loyalty":null,"multiverseid":193551,"imageName":"khalni hydra","border":null,"watermark":null,"setCode":"ROE","imageCode":"roe","count":null},{"name":"Nightmare","layout":"normal","manaCost":"{5}{B}","cmc":6,"type":"Creature — Nightmare Horse","rarity":"Rare","text":"Flying (This creature can't be blocked except by creatures with flying or reach.)\nNightmare's power and toughness are each equal to the number of Swamps you control.","flavor":"The thunder of its hooves beats dreams into despair.","artist":"Vance Kovacs","number":"276","power":"*","toughness":"*","loyalty":null,"multiverseid":383168,"imageName":"nightmare","border":null,"watermark":null,"setCode":"M15","imageCode":"m15","count":null},{"name":"Shivan Dragon","layout":"normal","manaCost":"{4}{R}{R}","cmc":6,"type":"Creature — Dragon","rarity":"Rare","text":"Flying (This creature can't be blocked except by creatures with flying or reach.)\n{R}: Shivan Dragon gets +1/+0 until end of turn.","flavor":"The undisputed master of the mountains of Shiv.","artist":"Donato Giancola","number":"281","power":"5","toughness":"5","loyalty":null,"multiverseid":383172,"imageName":"shivan dragon","border":null,"watermark":null,"setCode":"M15","imageCode":"m15","count":null},{"name":"Disrupting Shoal","layout":"normal","manaCost":"{X}{U}{U}","cmc":2,"type":"Instant — Arcane","rarity":"Rare","text":"You may exile a blue card with converted mana cost X from your hand rather than pay Disrupting Shoal's mana cost.\nCounter target spell if its converted mana cost is X.","flavor":null,"artist":"Scott M. Fischer","number":"33","power":null,"toughness":null,"loyalty":null,"multiverseid":74128,"imageName":"disrupting shoal","border":null,"watermark":null,"setCode":"BOK","imageCode":"bok","count":null},{"name":"Sorin Markov","layout":"normal","manaCost":"{3}{B}{B}{B}","cmc":6,"type":"Planeswalker — Sorin","rarity":"Mythic Rare","text":"+2: Sorin Markov deals 2 damage to target creature or player and you gain 2 life.\n−3: Target opponent's life total becomes 10.\n−7: You control target player during that player's next turn.","flavor":null,"artist":"Michael Komarck","number":"109","power":null,"toughness":null,"loyalty":4,"multiverseid":238330,"imageName":"sorin markov","border":null,"watermark":null,"setCode":"M12","imageCode":"m12","count":null},{"name":"Ugin's Construct","layout":"normal","manaCost":"{4}","cmc":4,"type":"Artifact Creature — Construct","rarity":"Uncommon","text":"When Ugin's Construct enters the battlefield, sacrifice a permanent that's one or more colors.","flavor":"While trapping the Eldrazi on Zendikar, Ugin learned little from Sorin, but he gleaned the rudiments of lithomancy from Nahiri.","artist":"Peter Mohrbacher","number":"164","power":"4","toughness":"5","loyalty":null,"multiverseid":391949,"imageName":"ugin's construct","border":null,"watermark":null,"setCode":"FRF","imageCode":"frf","count":null}] \ No newline at end of file diff --git a/test/eu/equalparts/test/ui/CLITest.java b/test/eu/equalparts/test/ui/CLITest.java deleted file mode 100644 index 4a7ebac..0000000 --- a/test/eu/equalparts/test/ui/CLITest.java +++ /dev/null @@ -1,34 +0,0 @@ -package eu.equalparts.test.ui; - -import static org.junit.Assert.*; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -public class CLITest { - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - } - - @Before - public void setUp() throws Exception { - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void test() { - fail("Not yet implemented"); - } - -} diff --git a/test/eu/equalparts/test/ui/CardbaseCLITest.java b/test/eu/equalparts/test/ui/CardbaseCLITest.java new file mode 100644 index 0000000..ad81fb9 --- /dev/null +++ b/test/eu/equalparts/test/ui/CardbaseCLITest.java @@ -0,0 +1,34 @@ +package eu.equalparts.test.ui; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CardbaseCLITest { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void test() { + fail("Not yet implemented"); + } + +} -- cgit v1.2.3