Made sorting on CN more reliable with zero-padded strings

This commit is contained in:
leriomaggio
2021-05-24 10:14:22 +01:00
parent 189b0ea122
commit 2a304bd413

View File

@@ -50,12 +50,14 @@ import forge.util.CardTranslation;
import forge.util.Localizer; import forge.util.Localizer;
public enum ColumnDef { public enum ColumnDef {
/**The column containing the inventory item name.*/ /**
* The column containing the inventory item name.
*/
STRING("", "", 0, false, SortState.ASC, STRING("", "", 0, false, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
public Comparable<?> apply(final Entry<InventoryItem, Integer> from) { public Comparable<?> apply(final Entry<InventoryItem, Integer> from) {
return from.getKey() instanceof Comparable<?> ? (Comparable<?>)from.getKey() : from.getKey().getName(); return from.getKey() instanceof Comparable<?> ? (Comparable<?>) from.getKey() : from.getKey().getName();
} }
}, },
new Function<Entry<? extends InventoryItem, Integer>, Object>() { new Function<Entry<? extends InventoryItem, Integer>, Object>() {
@@ -64,7 +66,9 @@ public enum ColumnDef {
return from.getKey().toString(); return from.getKey().toString();
} }
}), }),
/**The name column.*/ /**
* The name column.
*/
NAME("lblName", "lblName", 180, false, SortState.ASC, NAME("lblName", "lblName", 180, false, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -82,8 +86,10 @@ public enum ColumnDef {
return from.getKey().getName(); return from.getKey().getName();
} }
}), }),
/**The column for sorting cards in collector order.*/ /**
* The column for sorting cards in collector order.
*/
COLLECTOR_ORDER("lblCN", "ttCN", 20, false, SortState.ASC, COLLECTOR_ORDER("lblCN", "ttCN", 20, false, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -99,21 +105,25 @@ public enum ColumnDef {
((PaperCard) item).getCollectorNumber() : IPaperCard.NO_COLLECTOR_NUMBER; ((PaperCard) item).getCollectorNumber() : IPaperCard.NO_COLLECTOR_NUMBER;
} }
}), }),
/**The type column.*/ /**
* The type column.
*/
TYPE("lblType", "ttType", 100, false, SortState.ASC, TYPE("lblType", "ttType", 100, false, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
public Comparable<?> apply(final Entry<InventoryItem, Integer> from) { public Comparable<?> apply(final Entry<InventoryItem, Integer> from) {
return CardTranslation.getTranslatedType(from.getKey().getName(),toType(from.getKey())); return CardTranslation.getTranslatedType(from.getKey().getName(), toType(from.getKey()));
} }
}, },
new Function<Entry<? extends InventoryItem, Integer>, Object>() { new Function<Entry<? extends InventoryItem, Integer>, Object>() {
@Override @Override
public Object apply(final Entry<? extends InventoryItem, Integer> from) { public Object apply(final Entry<? extends InventoryItem, Integer> from) {
return CardTranslation.getTranslatedType(from.getKey().getName(),toType(from.getKey())); return CardTranslation.getTranslatedType(from.getKey().getName(), toType(from.getKey()));
} }
}), }),
/**The mana cost column.*/ /**
* The mana cost column.
*/
COST("lblCost", "ttCost", 70, true, SortState.ASC, COST("lblCost", "ttCost", 70, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -127,7 +137,9 @@ public enum ColumnDef {
return toCardRules(from.getKey()); return toCardRules(from.getKey());
} }
}), }),
/**The color column.*/ /**
* The color column.
*/
COLOR("lblColor", "ttColor", 46, true, SortState.ASC, COLOR("lblColor", "ttColor", 46, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -141,7 +153,9 @@ public enum ColumnDef {
return toColor(from.getKey()); return toColor(from.getKey());
} }
}), }),
/**The power column.*/ /**
* The power column.
*/
POWER("lblPower", "ttPower", 20, true, SortState.DESC, POWER("lblPower", "ttPower", 20, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -155,7 +169,9 @@ public enum ColumnDef {
return toPower(from.getKey()); return toPower(from.getKey());
} }
}), }),
/**The toughness column.*/ /**
* The toughness column.
*/
TOUGHNESS("lblToughness", "ttToughness", 20, true, SortState.DESC, TOUGHNESS("lblToughness", "ttToughness", 20, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -169,7 +185,9 @@ public enum ColumnDef {
return toToughness(from.getKey()); return toToughness(from.getKey());
} }
}), }),
/**The converted mana cost column.*/ /**
* The converted mana cost column.
*/
CMC("lblCMC", "ttCMC", 20, true, SortState.ASC, CMC("lblCMC", "ttCMC", 20, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -183,7 +201,9 @@ public enum ColumnDef {
return toCMC(from.getKey()); return toCMC(from.getKey());
} }
}), }),
/**The rarity column.*/ /**
* The rarity column.
*/
RARITY("lblRarity", "lblRarity", 20, true, SortState.DESC, RARITY("lblRarity", "lblRarity", 20, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -197,7 +217,9 @@ public enum ColumnDef {
return toRarity(from.getKey()); return toRarity(from.getKey());
} }
}), }),
/**The set code column.*/ /**
* The set code column.
*/
SET("lblSet", "lblSet", 38, true, SortState.DESC, SET("lblSet", "lblSet", 38, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -214,7 +236,9 @@ public enum ColumnDef {
return i instanceof InventoryItemFromSet ? ((InventoryItemFromSet) i).getEdition() : "n/a"; return i instanceof InventoryItemFromSet ? ((InventoryItemFromSet) i).getEdition() : "n/a";
} }
}), }),
/**The AI compatibility flag column*/ /**
* The AI compatibility flag column
*/
AI("lblAI", "lblAIStatus", 30, true, SortState.ASC, AI("lblAI", "lblAIStatus", 30, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -237,7 +261,9 @@ public enum ColumnDef {
: (ai.getRemRandomDecks() ? "?" : ""); : (ai.getRemRandomDecks() ? "?" : "");
} }
}), }),
/**The Draft ranking column.*/ /**
* The Draft ranking column.
*/
RANKING("lblRanking", "lblDraftRanking", 50, true, SortState.ASC, RANKING("lblRanking", "lblDraftRanking", 50, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -251,7 +277,9 @@ public enum ColumnDef {
return toRanking(from.getKey(), true); return toRanking(from.getKey(), true);
} }
}), }),
/**The quantity column.*/ /**
* The quantity column.
*/
QUANTITY("lblQty", "lblQuantity", 25, true, SortState.ASC, QUANTITY("lblQty", "lblQuantity", 25, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -265,7 +293,9 @@ public enum ColumnDef {
return from.getValue(); return from.getValue();
} }
}), }),
/**The quantity in deck column.*/ /**
* The quantity in deck column.
*/
DECK_QUANTITY("lblQuantity", "lblQuantity", 50, true, SortState.ASC, DECK_QUANTITY("lblQuantity", "lblQuantity", 50, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -279,19 +309,29 @@ public enum ColumnDef {
return from.getValue(); return from.getValue();
} }
}), }),
/**The new inventory flag column.*/ /**
* The new inventory flag column.
*/
NEW("lblNew", "lblNew", 30, true, SortState.DESC, NEW("lblNew", "lblNew", 30, true, SortState.DESC,
null, null), //functions will be set later null, null), //functions will be set later
/**The price column.*/ /**
* The price column.
*/
PRICE("lblPrice", "ttPrice", 35, true, SortState.DESC, PRICE("lblPrice", "ttPrice", 35, true, SortState.DESC,
null, null), null, null),
/**The quantity owned column.*/ /**
* The quantity owned column.
*/
OWNED("lblOwned", "lblOwned", 20, true, SortState.ASC, OWNED("lblOwned", "lblOwned", 20, true, SortState.ASC,
null, null), null, null),
/**The deck name column.*/ /**
* The deck name column.
*/
DECKS("lblDecks", "lblDecks", 20, true, SortState.ASC, DECKS("lblDecks", "lblDecks", 20, true, SortState.ASC,
null, null), null, null),
/**The favorite flag column.*/ /**
* The favorite flag column.
*/
FAVORITE("", "ttFavorite", 18, true, SortState.DESC, FAVORITE("", "ttFavorite", 18, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -309,7 +349,9 @@ public enum ColumnDef {
return toCard(from.getKey()); return toCard(from.getKey());
} }
}), }),
/**The favorite deck flag column.*/ /**
* The favorite deck flag column.
*/
DECK_FAVORITE("", "ttFavorite", 18, true, SortState.DESC, DECK_FAVORITE("", "ttFavorite", 18, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -327,7 +369,9 @@ public enum ColumnDef {
return toDeck(from.getKey()); return toDeck(from.getKey());
} }
}), }),
/**The edit/delete deck column.*/ /**
* The edit/delete deck column.
*/
DECK_ACTIONS("", "lblDeleteEdit", 40, true, SortState.DESC, DECK_ACTIONS("", "lblDeleteEdit", 40, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -341,7 +385,9 @@ public enum ColumnDef {
return toDeck(from.getKey()); return toDeck(from.getKey());
} }
}), }),
/**The deck folder column.*/ /**
* The deck folder column.
*/
DECK_FOLDER("lblFolder", "lblFolder", 80, false, SortState.ASC, DECK_FOLDER("lblFolder", "lblFolder", 80, false, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -355,7 +401,9 @@ public enum ColumnDef {
return toDeckFolder(from.getKey()); return toDeckFolder(from.getKey());
} }
}), }),
/**The deck color column.*/ /**
* The deck color column.
*/
DECK_COLOR("lblColor", "ttColor", 70, true, SortState.ASC, DECK_COLOR("lblColor", "ttColor", 70, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -369,7 +417,9 @@ public enum ColumnDef {
return toDeckColor(from.getKey()); return toDeckColor(from.getKey());
} }
}), }),
/**The deck format column.*/ /**
* The deck format column.
*/
DECK_FORMAT("lblFormat", "ttFormats", 60, false, SortState.DESC, DECK_FORMAT("lblFormat", "ttFormats", 60, false, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -380,9 +430,9 @@ public enum ColumnDef {
} }
Iterable<GameFormat> all = deck.getExhaustiveFormats(); Iterable<GameFormat> all = deck.getExhaustiveFormats();
int acc = 0; int acc = 0;
for(GameFormat gf : all) { for (GameFormat gf : all) {
int ix = gf.getIndex(); int ix = gf.getIndex();
if( ix < 30 && ix > 0) if (ix < 30 && ix > 0)
acc |= 0x40000000 >> (ix - 1); acc |= 0x40000000 >> (ix - 1);
} }
return acc; return acc;
@@ -398,7 +448,9 @@ public enum ColumnDef {
return deck.getFormatsString(); return deck.getFormatsString();
} }
}), }),
/**The deck edition column, a mystery to us all.*/ /**
* The deck edition column, a mystery to us all.
*/
DECK_EDITION("lblSet", "lblSetEdition", 38, true, SortState.DESC, DECK_EDITION("lblSet", "lblSetEdition", 38, true, SortState.DESC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -412,7 +464,9 @@ public enum ColumnDef {
return toDeck(from.getKey()).getEdition().getCode(); return toDeck(from.getKey()).getEdition().getCode();
} }
}), }),
/**The main library size column.*/ /**
* The main library size column.
*/
DECK_MAIN("lblMain", "ttMain", 30, true, SortState.ASC, DECK_MAIN("lblMain", "ttMain", 30, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -426,7 +480,9 @@ public enum ColumnDef {
return toDeck(from.getKey()).getMainSize(); return toDeck(from.getKey()).getMainSize();
} }
}), }),
/**The sideboard size column.*/ /**
* The sideboard size column.
*/
DECK_SIDE("lblSide", "lblSideboard", 30, true, SortState.ASC, DECK_SIDE("lblSide", "lblSideboard", 30, true, SortState.ASC,
new Function<Entry<InventoryItem, Integer>, Comparable<?>>() { new Function<Entry<InventoryItem, Integer>, Comparable<?>>() {
@Override @Override
@@ -442,12 +498,20 @@ public enum ColumnDef {
}); });
ColumnDef(String shortName0, String longName0, int preferredWidth0, boolean isWidthFixed0, SortState sortState0, ColumnDef(String shortName0, String longName0, int preferredWidth0, boolean isWidthFixed0, SortState sortState0,
Function<Entry<InventoryItem, Integer>, Comparable<?>> fnSort0, Function<Entry<InventoryItem, Integer>, Comparable<?>> fnSort0,
Function<Entry<? extends InventoryItem, Integer>, Object> fnDisplay0) { Function<Entry<? extends InventoryItem, Integer>, Object> fnDisplay0) {
final Localizer localizer = Localizer.getInstance(); final Localizer localizer = Localizer.getInstance();
if (shortName0 != null && !shortName0.isEmpty()) { this.shortName = localizer.getMessage(shortName0);} else {this.shortName = shortName0;} if (shortName0 != null && !shortName0.isEmpty()) {
if (longName0 != null && !longName0.isEmpty()) { this.longName = localizer.getMessage(longName0);} else {this.longName = longName0;} this.shortName = localizer.getMessage(shortName0);
} else {
this.shortName = shortName0;
}
if (longName0 != null && !longName0.isEmpty()) {
this.longName = localizer.getMessage(longName0);
} else {
this.longName = longName0;
}
this.preferredWidth = preferredWidth0; this.preferredWidth = preferredWidth0;
this.isWidthFixed = isWidthFixed0; this.isWidthFixed = isWidthFixed0;
@@ -467,57 +531,66 @@ public enum ColumnDef {
public String toString() { public String toString() {
return this.longName; return this.longName;
} }
/**
*Converts a card name to a sortable name. /**
* Trim leading quotes, then move article last, then replace characters. * Converts a card name to a sortable name.
* Because An-Havva Constable. * Trim leading quotes, then move article last, then replace characters.
* Capitals and lowercase sorted as one: "my deck" before "Myr Retribution" * Because An-Havva Constable.
* Apostrophes matter, though: "D'Avenant" before "Danitha" * Capitals and lowercase sorted as one: "my deck" before "Myr Retribution"
* TO DO: Commas before apostrophes: "Rakdos, Lord of Riots" before "Rakdos's Return" * Apostrophes matter, though: "D'Avenant" before "Danitha"
* @param printedName The name of the card. * TO DO: Commas before apostrophes: "Rakdos, Lord of Riots" before "Rakdos's Return"
* @return A sortable name. *
*/ * @param printedName The name of the card.
* @return A sortable name.
*/
private static String toSortableName(String printedName) { private static String toSortableName(String printedName) {
if (printedName.startsWith("\"")) printedName = printedName.substring(1); if (printedName.startsWith("\"")) printedName = printedName.substring(1);
return moveArticleToEnd(printedName).toLowerCase().replaceAll("[^\\s'0-9a-z]",""); return moveArticleToEnd(printedName).toLowerCase().replaceAll("[^\\s'0-9a-z]", "");
} }
/**Article words. These words get kicked to the end of a sortable name. /**
For localization, simply overwrite this array with appropriate words. * Article words. These words get kicked to the end of a sortable name.
Words in this list are used by the method String moveArticleToEnd(String), useful * For localization, simply overwrite this array with appropriate words.
for alphabetizing phrases, in particular card or other inventory object names.*/ * Words in this list are used by the method String moveArticleToEnd(String), useful
* for alphabetizing phrases, in particular card or other inventory object names.
*/
private static final String[] ARTICLE_WORDS = { private static final String[] ARTICLE_WORDS = {
"A", "A",
"An", "An",
"The" "The"
}; };
/**Detects whether a string begins with an article word /**
@param str The name of the card. * Detects whether a string begins with an article word
@return The sort-friendly name of the card. Example: "The Hive" becomes "Hive The".*/ *
private static String moveArticleToEnd(String str){ * @param str The name of the card.
String articleWord; * @return The sort-friendly name of the card. Example: "The Hive" becomes "Hive The".
for (int i = 0; i < ARTICLE_WORDS.length; i++){ */
articleWord = ARTICLE_WORDS[i]; private static String moveArticleToEnd(String str) {
if (str.startsWith(articleWord + " ")){ String articleWord;
str = str.substring(articleWord.length()+1) + " " + articleWord; for (int i = 0; i < ARTICLE_WORDS.length; i++) {
return str; articleWord = ARTICLE_WORDS[i];
} if (str.startsWith(articleWord + " ")) {
} str = str.substring(articleWord.length() + 1) + " " + articleWord;
return str; return str;
}
}
return str;
} }
private static String toType(final InventoryItem i) { private static String toType(final InventoryItem i) {
return i instanceof IPaperCard ? ((IPaperCard)i).getRules().getType().toString() : i.getItemType(); return i instanceof IPaperCard ? ((IPaperCard) i).getRules().getType().toString() : i.getItemType();
} }
private static IPaperCard toCard(final InventoryItem i) { private static IPaperCard toCard(final InventoryItem i) {
return i instanceof IPaperCard ? ((IPaperCard) i) : null; return i instanceof IPaperCard ? ((IPaperCard) i) : null;
} }
private static ManaCost toManaCost(final InventoryItem i) { private static ManaCost toManaCost(final InventoryItem i) {
return i instanceof IPaperCard ? ((IPaperCard) i).getRules().getManaCost() : ManaCost.NO_COST; return i instanceof IPaperCard ? ((IPaperCard) i).getRules().getManaCost() : ManaCost.NO_COST;
} }
private static CardRules toCardRules(final InventoryItem i) { private static CardRules toCardRules(final InventoryItem i) {
return i instanceof IPaperCard ? ((IPaperCard) i).getRules() : null; return i instanceof IPaperCard ? ((IPaperCard) i).getRules() : null;
} }
@@ -531,7 +604,7 @@ public enum ColumnDef {
if (i instanceof PaperCard) { if (i instanceof PaperCard) {
result = ((IPaperCard) i).getRules().getIntPower(); result = ((IPaperCard) i).getRules().getIntPower();
if (result == Integer.MAX_VALUE) { if (result == Integer.MAX_VALUE) {
if (((IPaperCard)i).getRules().getType().isPlaneswalker()) { if (((IPaperCard) i).getRules().getType().isPlaneswalker()) {
String loy = ((IPaperCard) i).getRules().getInitialLoyalty(); String loy = ((IPaperCard) i).getRules().getInitialLoyalty();
result = StringUtils.isNumeric(loy) ? Integer.valueOf(loy) : 0; result = StringUtils.isNumeric(loy) ? Integer.valueOf(loy) : 0;
} }
@@ -547,13 +620,13 @@ public enum ColumnDef {
private static Integer toCMC(final InventoryItem i) { private static Integer toCMC(final InventoryItem i) {
return i instanceof PaperCard ? ((IPaperCard) i).getRules().getManaCost().getCMC() : -1; return i instanceof PaperCard ? ((IPaperCard) i).getRules().getManaCost().getCMC() : -1;
} }
private static CardRarity toRarity(final InventoryItem i) { private static CardRarity toRarity(final InventoryItem i) {
return i instanceof PaperCard ? ((IPaperCard) i).getRarity() : CardRarity.Unknown; return i instanceof PaperCard ? ((IPaperCard) i).getRarity() : CardRarity.Unknown;
} }
private static Double toRanking(final InventoryItem i, boolean truncate) { private static Double toRanking(final InventoryItem i, boolean truncate) {
if (i instanceof PaperCard){ if (i instanceof PaperCard) {
PaperCard cp = (PaperCard) i; PaperCard cp = (PaperCard) i;
double ranking = CardRanker.getRawScore(cp); double ranking = CardRanker.getRawScore(cp);
if (truncate) { if (truncate) {
@@ -567,222 +640,281 @@ public enum ColumnDef {
private static DeckProxy toDeck(final InventoryItem i) { private static DeckProxy toDeck(final InventoryItem i) {
return i instanceof DeckProxy ? ((DeckProxy) i) : null; return i instanceof DeckProxy ? ((DeckProxy) i) : null;
} }
private static ColorSet toDeckColor(final InventoryItem i) { private static ColorSet toDeckColor(final InventoryItem i) {
return i instanceof DeckProxy ? ((DeckProxy) i).getColor() : null; return i instanceof DeckProxy ? ((DeckProxy) i).getColor() : null;
} }
private static String toDeckFolder(final InventoryItem i) { private static String toDeckFolder(final InventoryItem i) {
return i instanceof DeckProxy ? ((DeckProxy) i).getPath() + "/" : null; return i instanceof DeckProxy ? ((DeckProxy) i).getPath() + "/" : null;
} }
/**Generates a sortable numeric string based on a card's attributes. /**
This is a multi-layer sort. It is coded in layers to make it easier to manipulate. * Generates a sortable numeric string based on a card's attributes.
This method can be fed any inventory item, but is only useful for paper cards. * This is a multi-layer sort. It is coded in layers to make it easier to manipulate.
@param i An inventory item. * This method can be fed any inventory item, but is only useful for paper cards.
@return A sortable numeric string based on the item's attributes.*/ *
* @param i An inventory item.
* @return A sortable numeric string based on the item's attributes.
*/
private static String toCollectorPrefix(final InventoryItem i) { private static String toCollectorPrefix(final InventoryItem i) {
//make sure it's a card. if not, pointless to proceed. //make sure it's a card. if not, pointless to proceed.
return (i instanceof PaperCard ? ((PaperCard) i).getCollectorNumber() : IPaperCard.NO_COLLECTOR_NUMBER) + toSortableName(i.getName()); String collectorNumber;
if (i instanceof PaperCard) {
collectorNumber = ((PaperCard) i).getCollectorNumber();
// Now, for proper sorting, let's zero-pad the collector number (if integer)
try {
int collNr = Integer.parseInt(collectorNumber);
collectorNumber = String.format("%05d", collNr);
} catch (NumberFormatException ex) {
} // NOOP, leave it as it is - NaN (may contains letters)
} else {
collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER;
}
return collectorNumber + toSortableName(i.getName());
} }
/**Returns 1 for land, otherwise 0 and continues sorting. /**
@param i A paper card. * Returns 1 for land, otherwise 0 and continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toLandsLast(final InventoryItem i) { private static String toLandsLast(final InventoryItem i) {
//nonland? //nonland?
return !(((IPaperCard) i).getRules().getType().isLand()) ? return !(((IPaperCard) i).getRules().getType().isLand()) ?
"0" + toArtifactsWithColorlessCostsLast(i) "0" + toArtifactsWithColorlessCostsLast(i)
//land //land
: "1"; : "1";
} }
/**Returns 1 for artifacts without color shards in their mana cost, otherwise 0 and continues sorting. /**
As of 2019, colored artifacts appear here if there are no colored shards in their casting cost. * Returns 1 for artifacts without color shards in their mana cost, otherwise 0 and continues sorting.
@param i A paper card. * As of 2019, colored artifacts appear here if there are no colored shards in their casting cost.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toArtifactsWithColorlessCostsLast(final InventoryItem i) { private static String toArtifactsWithColorlessCostsLast(final InventoryItem i) {
forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost(); forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost();
return !(((IPaperCard) i).getRules().getType().isArtifact() && (toColor(i).isColorless() || return !(((IPaperCard) i).getRules().getType().isArtifact() && (toColor(i).isColorless() ||
//If it isn't colorless, see if it can be paid with only white, only blue, only black. //If it isn't colorless, see if it can be paid with only white, only blue, only black.
//No need to check others since three-color hybrid shards don't exist. //No need to check others since three-color hybrid shards don't exist.
manaCost.canBePaidWithAvaliable(MagicColor.WHITE) && manaCost.canBePaidWithAvaliable(MagicColor.WHITE) &&
manaCost.canBePaidWithAvaliable(MagicColor.BLUE) && manaCost.canBePaidWithAvaliable(MagicColor.BLUE) &&
manaCost.canBePaidWithAvaliable(MagicColor.BLACK))) manaCost.canBePaidWithAvaliable(MagicColor.BLACK)))
? "0" + toSplitLast(i): "1"; ? "0" + toSplitLast(i) : "1";
} }
/**Returns 1 for split cards or 0 for other cards; continues sorting. /**
@param i A paper card. * Returns 1 for split cards or 0 for other cards; continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toSplitLast(final InventoryItem i) { private static String toSplitLast(final InventoryItem i) {
return ((IPaperCard) i).getRules().getSplitType() != CardSplitType.Split ? return ((IPaperCard) i).getRules().getSplitType() != CardSplitType.Split ?
"0" + toConspiracyFirst(i) : "1" + toSplitCardSort(i); "0" + toConspiracyFirst(i) : "1" + toSplitCardSort(i);
} }
/**Returns 0 for Conspiracy cards, otherwise 1 and continues sorting. /**
@param i A paper card. * Returns 0 for Conspiracy cards, otherwise 1 and continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toConspiracyFirst(final InventoryItem i) { private static String toConspiracyFirst(final InventoryItem i) {
return ((IPaperCard) i).getRules().getType().isConspiracy() return ((IPaperCard) i).getRules().getType().isConspiracy()
? "0" //is a Conspiracy ? "0" //is a Conspiracy
: "1" + toColorlessFirst(i); //isn't a Conspiracy : "1" + toColorlessFirst(i); //isn't a Conspiracy
} }
/**Returns 0 for colorless cards, otherwise 1 and continues sorting. /**
@param i A paper card. * Returns 0 for colorless cards, otherwise 1 and continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toColorlessFirst(final InventoryItem i) { private static String toColorlessFirst(final InventoryItem i) {
return toColor(i).isColorless() ? return toColor(i).isColorless() ?
"0" : "1" + toMonocolorFirst(i); "0" : "1" + toMonocolorFirst(i);
} }
/**Returns 0 for monocolor cards, 1 for multicolor cards; continues sorting. /**
@param i A paper card. * Returns 0 for monocolor cards, 1 for multicolor cards; continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toMonocolorFirst(final InventoryItem i) { private static String toMonocolorFirst(final InventoryItem i) {
return toColor(i).isMonoColor() ? return toColor(i).isMonoColor() ?
"0" + toWubrgOrder(i): "1" + toGoldFirst(i); "0" + toWubrgOrder(i) : "1" + toGoldFirst(i);
} }
/**Returns 0 for gold cards and continues sorting, 1 otherwise. /**
@param i A paper card. * Returns 0 for gold cards and continues sorting, 1 otherwise.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toGoldFirst(final InventoryItem i) { private static String toGoldFirst(final InventoryItem i) {
forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost(); forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost();
return !(manaCost.canBePaidWithAvaliable(MagicColor.WHITE) | manaCost.canBePaidWithAvaliable(MagicColor.BLUE) | return !(manaCost.canBePaidWithAvaliable(MagicColor.WHITE) | manaCost.canBePaidWithAvaliable(MagicColor.BLUE) |
manaCost.canBePaidWithAvaliable(MagicColor.BLACK) | manaCost.canBePaidWithAvaliable(MagicColor.RED) | manaCost.canBePaidWithAvaliable(MagicColor.BLACK) | manaCost.canBePaidWithAvaliable(MagicColor.RED) |
manaCost.canBePaidWithAvaliable(MagicColor.GREEN)) ? "0" : "1"; manaCost.canBePaidWithAvaliable(MagicColor.GREEN)) ? "0" : "1";
} }
/**Entry point for generating split card sortable strings. /**
Splits the card into two card faces, then sends it to the next * Entry point for generating split card sortable strings.
sorting method. * Splits the card into two card faces, then sends it to the next
@param i A paper card. * sorting method.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
//Split card sorting is probably as complex as sorting gets. //Split card sorting is probably as complex as sorting gets.
//This method serves as an entry point only, separating the two card parts for convenience. //This method serves as an entry point only, separating the two card parts for convenience.
private static String toSplitCardSort(final InventoryItem i) { private static String toSplitCardSort(final InventoryItem i) {
CardRules rules = ((IPaperCard) i).getRules(); CardRules rules = ((IPaperCard) i).getRules();
forge.card.ICardFace mainPart = rules.getMainPart(); forge.card.ICardFace mainPart = rules.getMainPart();
forge.card.ICardFace otherPart = rules.getOtherPart(); forge.card.ICardFace otherPart = rules.getOtherPart();
return toSplitSort(mainPart, otherPart); return toSplitSort(mainPart, otherPart);
} }
/**Generates a sortable numeric string for split cards. /**
Split cards are sorted by color on both halves. * Generates a sortable numeric string for split cards.
Sort order is C//C, W//W, U//U, B//B, R//R, G//G, * Split cards are sorted by color on both halves.
Gold/Gold, * Sort order is C//C, W//W, U//U, B//B, R//R, G//G,
W//U, U//B, B//R, R//G, G//W, * Gold/Gold,
W//B, U//R, B//G, R//W, G//U, * W//U, U//B, B//R, R//G, G//W,
W//R, U//G, B//W, R//U, G//B, * W//B, U//R, B//G, R//W, G//U,
W//G, U//W, B//U, R//B, G//R. * W//R, U//G, B//W, R//U, G//B,
Any that do not conform will sort at the end. * W//G, U//W, B//U, R//B, G//R.
@param mainPart The first half of the card. * Any that do not conform will sort at the end.
@param otherPart The other half of the card. *
@return Part of a sortable numeric string.*/ * @param mainPart The first half of the card.
private static String toSplitSort(final ICardFace mainPart, final ICardFace otherPart) { * @param otherPart The other half of the card.
ColorSet mainPartColor = mainPart.getColor(); * @return Part of a sortable numeric string.
ColorSet otherPartColor = otherPart.getColor(); */
private static String toSplitSort(final ICardFace mainPart, final ICardFace otherPart) {
return mainPartColor.isEqual(otherPartColor.getColor()) ColorSet mainPartColor = mainPart.getColor();
ColorSet otherPartColor = otherPart.getColor();
? //both halves match
return mainPartColor.isEqual(otherPartColor.getColor())
(mainPartColor.isEqual(MagicColor.WHITE) ? "01" :
mainPartColor.isEqual(MagicColor.BLUE) ? "02" : ? //both halves match
mainPartColor.isEqual(MagicColor.BLACK) ? "03" :
mainPartColor.isEqual(MagicColor.RED) ? "04" : (mainPartColor.isEqual(MagicColor.WHITE) ? "01" :
mainPartColor.isEqual(MagicColor.GREEN) ? "05" : "00") mainPartColor.isEqual(MagicColor.BLUE) ? "02" :
mainPartColor.isEqual(MagicColor.BLACK) ? "03" :
: //halves don't match mainPartColor.isEqual(MagicColor.RED) ? "04" :
mainPartColor.isEqual(MagicColor.GREEN) ? "05" : "00")
//both halves gold
mainPartColor.isMulticolor() && otherPartColor.isMulticolor() ? "06" : : //halves don't match
//second color is << 1 //both halves gold
mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.BLUE) ? "11" : mainPartColor.isMulticolor() && otherPartColor.isMulticolor() ? "06" :
mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.BLACK) ? "12" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.RED) ? "13" : //second color is << 1
mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.GREEN) ? "14" : mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.BLUE) ? "11" :
mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.WHITE) ? "15" : mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.BLACK) ? "12" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.RED) ? "13" :
//second color is << 2 mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.GREEN) ? "14" :
mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.BLACK) ? "21" : mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.WHITE) ? "15" :
mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.RED) ? "22" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.GREEN) ? "23" : //second color is << 2
mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.WHITE) ? "24" : mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.BLACK) ? "21" :
mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.BLUE) ? "25" : mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.RED) ? "22" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.GREEN) ? "23" :
//second color is << 3 mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.WHITE) ? "24" :
mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.RED) ? "31" : mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.BLUE) ? "25" :
mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.GREEN) ? "32" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.WHITE) ? "33" : //second color is << 3
mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.BLUE) ? "34" : mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.RED) ? "31" :
mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.BLACK) ? "35" : mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.GREEN) ? "32" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.WHITE) ? "33" :
//second color is << 4 mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.BLUE) ? "34" :
mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.GREEN) ? "41" : mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.BLACK) ? "35" :
mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.WHITE) ? "42" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.BLUE) ? "43" : //second color is << 4
mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.BLACK) ? "44" : mainPartColor.isEqual(MagicColor.WHITE) && otherPartColor.isEqual(MagicColor.GREEN) ? "41" :
mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.RED) ? "45" mainPartColor.isEqual(MagicColor.BLUE) && otherPartColor.isEqual(MagicColor.WHITE) ? "42" :
mainPartColor.isEqual(MagicColor.BLACK) && otherPartColor.isEqual(MagicColor.BLUE) ? "43" :
://No split cards have been printed that don't fall into one of these groups. mainPartColor.isEqual(MagicColor.RED) && otherPartColor.isEqual(MagicColor.BLACK) ? "44" :
mainPartColor.isEqual(MagicColor.GREEN) && otherPartColor.isEqual(MagicColor.RED) ? "45"
"99";
://No split cards have been printed that don't fall into one of these groups.
"99";
} }
/**Returns 0 for white, 1 for blue, 2 for black, 3 for red, or 4 for green. /**
@param i A paper card. * Returns 0 for white, 1 for blue, 2 for black, 3 for red, or 4 for green.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toWubrgOrder(final InventoryItem i) { private static String toWubrgOrder(final InventoryItem i) {
ColorSet color = toColor(i); ColorSet color = toColor(i);
return color.hasWhite() ? "0" : color.hasBlue() ? "1" : color.hasBlack() ? "2" : return color.hasWhite() ? "0" : color.hasBlue() ? "1" : color.hasBlack() ? "2" :
color.hasRed() ? "3" : "4"; color.hasRed() ? "3" : "4";
} }
/**Returns 1 for Contraptions, otherwise 0 and continues sorting. /**
@param i A paper card. * Returns 1 for Contraptions, otherwise 0 and continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toContraptionsLast(final InventoryItem i) { private static String toContraptionsLast(final InventoryItem i) {
return !(((IPaperCard) i).getRules().getType().hasSubtype("Contraption")) ? return !(((IPaperCard) i).getRules().getType().hasSubtype("Contraption")) ?
"0" + toLandsLast(i) : "1"; "0" + toLandsLast(i) : "1";
} }
/**Returns 1 for basic lands, 0 otherwise, and continues sorting. /**
@param i A paper card. * Returns 1 for basic lands, 0 otherwise, and continues sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toBasicLandsLast(final InventoryItem i) { private static String toBasicLandsLast(final InventoryItem i) {
return !(((IPaperCard) i).getRules().getType().isBasicLand()) return !(((IPaperCard) i).getRules().getType().isBasicLand())
? "0" + toContraptionsLast(i) ? "0" + toContraptionsLast(i)
: "1" + toFullArtFirst(i); : "1" + toFullArtFirst(i);
} }
/**Currently only continues sorting. If Forge is updated to /**
use a flag for full-art lands, this method should be updated * Currently only continues sorting. If Forge is updated to
to assign those 0 and regular lands 1, then continue sorting. * use a flag for full-art lands, this method should be updated
@param i A paper card. * to assign those 0 and regular lands 1, then continue sorting.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toFullArtFirst(final InventoryItem i) { private static String toFullArtFirst(final InventoryItem i) {
return toBasicLandSort(i); return toBasicLandSort(i);
} }
/**Returns 0 for wastes, 1 for plains, 2 for island, /**
3 for swamp, 4 for mountain, 5 for forest. Snow * Returns 0 for wastes, 1 for plains, 2 for island,
lands are treated like nonsnow. * 3 for swamp, 4 for mountain, 5 for forest. Snow
@param i A paper card. * lands are treated like nonsnow.
@return Part of a sortable numeric string.*/ *
* @param i A paper card.
* @return Part of a sortable numeric string.
*/
private static String toBasicLandSort(final InventoryItem i) { private static String toBasicLandSort(final InventoryItem i) {
CardType basicLandType = ((IPaperCard) i).getRules().getType(); CardType basicLandType = ((IPaperCard) i).getRules().getType();
return basicLandType.hasStringType("Plains") ? "1" : ( return basicLandType.hasStringType("Plains") ? "1" : (
basicLandType.hasStringType("Island") ? "2" : ( basicLandType.hasStringType("Island") ? "2" : (
basicLandType.hasStringType("Swamp") ? "3" : ( basicLandType.hasStringType("Swamp") ? "3" : (
basicLandType.hasStringType("Mountain") ? "4" : ( basicLandType.hasStringType("Mountain") ? "4" : (
basicLandType.hasStringType("Forest") ? "5" : "0" basicLandType.hasStringType("Forest") ? "5" : "0"
) )
) )
) )
); );
} }
} }