cards = new ArrayList<>();
for (Player player : game.getPlayers().values()) {
@@ -1423,11 +1423,10 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
/**
- *
* returns a list of Permanents that produce mana sorted by the number of
* mana the Permanent produces that match the unpaid costs in ascending
* order
- *
+ *
* the idea is that we should pay costs first from mana producers that
* produce only one type of mana and save the multi-mana producers for those
* costs that can't be paid by any other producers
@@ -2089,13 +2088,13 @@ public class ComputerPlayer extends PlayerImpl implements Player {
try {
Card bestCard = pickBestCard(cards, chosenColors);
int maxScore = RateCard.rateCard(bestCard, chosenColors);
- int pickedCardRate = RateCard.getCardRating(bestCard);
+ int pickedCardRate = RateCard.getBaseCardScore(bestCard);
if (pickedCardRate <= 30) {
// if card is bad
// try to counter pick without any color restriction
Card counterPick = pickBestCard(cards, null);
- int counterPickScore = RateCard.getCardRating(counterPick);
+ int counterPickScore = RateCard.getBaseCardScore(counterPick);
// card is really good
// take it!
if (counterPickScore >= 80) {
@@ -2441,7 +2440,6 @@ public class ComputerPlayer extends PlayerImpl implements Player {
/**
* Sets a possible target player
- *
*/
private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game) {
if (target.getOriginalTarget() instanceof TargetOpponent) {
diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java
index 531d2736c9f..10cb4c21bf5 100644
--- a/Mage.Server/src/main/java/mage/server/Main.java
+++ b/Mage.Server/src/main/java/mage/server/Main.java
@@ -5,6 +5,7 @@ import mage.cards.Sets;
import mage.cards.repository.CardScanner;
import mage.cards.repository.PluginClassloaderRegistery;
import mage.cards.repository.RepositoryUtil;
+import mage.game.draft.RateCard;
import mage.game.match.MatchType;
import mage.game.tournament.TournamentType;
import mage.interfaces.MageServer;
@@ -135,6 +136,10 @@ public final class Main {
}
logger.info("Done.");
+ // cards preload with ratings
+ RateCard.bootstrapCardsAndRatings();
+ logger.info("Done.");
+
logger.info("Updating user stats DB...");
UserStatsRepository.instance.updateUserStats();
logger.info("Done.");
diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
index 6dc375d3b27..08255079604 100644
--- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
+++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
@@ -4,11 +4,14 @@ import mage.ObjectColor;
import mage.abilities.keyword.MultikickerAbility;
import mage.cards.*;
import mage.cards.basiclands.BasicLand;
-import mage.cards.repository.*;
+import mage.cards.repository.CardInfo;
+import mage.cards.repository.CardRepository;
+import mage.cards.repository.CardScanner;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.SubType;
import mage.constants.SuperType;
+import mage.game.draft.RateCard;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.TokenImpl;
import org.junit.Assert;
@@ -90,21 +93,6 @@ public class VerifyCardDataTest {
skipListCreate("MISSING_ABILITIES");
}
- public static List allCards() {
- Collection sets = Sets.getInstance().values();
- List cards = new ArrayList<>();
- for (ExpansionSet set : sets) {
- if (set.isCustomSet()) {
- continue;
- }
- for (ExpansionSet.SetCardInfo setInfo : set.getSetCardInfo()) {
- cards.add(CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(),
- setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo())));
- }
- }
- return cards;
- }
-
private void warn(Card card, String message) {
outputMessages.add("Warning: " + message + " for " + card.getExpansionSetCode() + " - " + card.getName() + " - " + card.getCardNumber());
}
@@ -119,7 +107,7 @@ public class VerifyCardDataTest {
@Test
public void verifyCards() throws IOException {
- for (Card card : allCards()) {
+ for (Card card : CardScanner.getAllCards()) {
Set tokens = findSourceTokens(card.getClass());
if (card.isSplitCard()) {
check(((SplitCard) card).getLeftHalfCard(), null);
@@ -658,6 +646,14 @@ public class VerifyCardDataTest {
}
}
+ private void checkLegalityFormats(Card card, JsonCard ref) {
+ if (skipListHaveName("LEGALITY", card.getExpansionSetCode(), card.getName())) {
+ return;
+ }
+
+ // TODO: add legality checks (by sets and cards, by banned)
+ }
+
private String prepareRule(String cardName, String rule) {
// remove and optimize rule text for analyze
String newRule = rule;
@@ -901,4 +897,22 @@ public class VerifyCardDataTest {
return result.toString();
}
+ @Test
+ public void testCardRatingConsistency() {
+ // all cards with same name must have same rating (see RateCard.rateCard)
+ // cards rating must be consistency (same) for card sorting
+ List cardsList = new ArrayList<>(CardScanner.getAllCards());
+ Map cardRates = new HashMap<>();
+ for (Card card : cardsList) {
+ int curRate = RateCard.rateCard(card, null, false);
+ int prevRate = cardRates.getOrDefault(card.getName(), 0);
+ if (prevRate == 0) {
+ cardRates.putIfAbsent(card.getName(), curRate);
+ } else {
+ if (curRate != prevRate) {
+ Assert.fail("Card with same name have different ratings: " + card.getName());
+ }
+ }
+ }
+ }
}
diff --git a/Mage/src/main/java/mage/cards/repository/CardScanner.java b/Mage/src/main/java/mage/cards/repository/CardScanner.java
index 9939dd49ace..2893f409df6 100644
--- a/Mage/src/main/java/mage/cards/repository/CardScanner.java
+++ b/Mage/src/main/java/mage/cards/repository/CardScanner.java
@@ -4,6 +4,7 @@ import mage.cards.*;
import org.apache.log4j.Logger;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -70,4 +71,23 @@ public final class CardScanner {
}
CardRepository.instance.saveCards(cardsToAdd, CardRepository.instance.getContentVersionConstant());
}
+
+ public static List getAllCards() {
+ return getAllCards(true);
+ }
+
+ public static List getAllCards(boolean ignoreCustomSets) {
+ Collection sets = Sets.getInstance().values();
+ List cards = new ArrayList<>();
+ for (ExpansionSet set : sets) {
+ if (ignoreCustomSets && set.isCustomSet()) {
+ continue;
+ }
+ for (ExpansionSet.SetCardInfo setInfo : set.getSetCardInfo()) {
+ cards.add(CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(),
+ setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo())));
+ }
+ }
+ return cards;
+ }
}
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java b/Mage/src/main/java/mage/game/draft/RateCard.java
similarity index 70%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java
rename to Mage/src/main/java/mage/game/draft/RateCard.java
index 56a980487a3..0de7de1886c 100644
--- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java
+++ b/Mage/src/main/java/mage/game/draft/RateCard.java
@@ -1,4 +1,4 @@
-package mage.player.ai.utils;
+package mage.game.draft;
import mage.abilities.Ability;
import mage.abilities.Mode;
@@ -7,9 +7,9 @@ import mage.abilities.effects.common.*;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.Card;
+import mage.cards.repository.CardScanner;
import mage.constants.ColoredManaSymbol;
import mage.constants.Outcome;
-import mage.constants.Rarity;
import mage.constants.SubType;
import mage.target.Target;
import mage.target.TargetPermanent;
@@ -30,9 +30,9 @@ import java.util.*;
*/
public final class RateCard {
- private static Map ratings;
- private static List setsWithRatingsToBeLoaded;
+ private static Map baseRatings = new HashMap<>();
private static final Map rated = new HashMap<>();
+ private static boolean isLoaded = false;
/**
* Rating that is given for new cards.
@@ -55,6 +55,15 @@ public final class RateCard {
private RateCard() {
}
+ public static void bootstrapCardsAndRatings() {
+ // preload cards and ratings
+ log.info("Loading cards and rating...");
+ List cards = CardScanner.getAllCards(false);
+ for (Card card : cards) {
+ RateCard.rateCard(card, null);
+ }
+ }
+
/**
* Get absolute score of the card.
* Depends on type, manacost, rating.
@@ -65,10 +74,15 @@ public final class RateCard {
* @return
*/
public static int rateCard(Card card, List allowedColors) {
- if (allowedColors == null && rated.containsKey(card.getName())) {
+ return rateCard(card, allowedColors, true);
+ }
+
+ public static int rateCard(Card card, List allowedColors, boolean useCache) {
+ if (useCache && allowedColors == null && rated.containsKey(card.getName())) {
int rate = rated.get(card.getName());
return rate;
}
+
int type;
if (card.isPlaneswalker()) {
type = 15;
@@ -83,10 +97,12 @@ public final class RateCard {
} else {
type = 6;
}
- int score = getCardRating(card) + 2 * type + getManaCostScore(card, allowedColors)
+ int score = getBaseCardScore(card) + 2 * type + getManaCostScore(card, allowedColors)
+ 40 * isRemoval(card);
- if (allowedColors == null)
+
+ if (useCache && allowedColors == null)
rated.put(card.getName(), score);
+
return score;
}
@@ -153,107 +169,127 @@ public final class RateCard {
return 0;
}
+
/**
* Return rating of the card.
*
* @param card Card to rate.
* @return Rating number from [1:100].
*/
- public static int getCardRating(Card card) {
- readRatingSetList();
- String exp = card.getExpansionSetCode().toLowerCase();
- readRatings(exp);
+ public static int getBaseCardScore(Card card) {
+ // same card name must have same rating
- if (ratings != null && ratings.containsKey(card.getName())) {
- return ratings.get(card.getName());
+ // ratings from files
+ // lazy loading on first request
+ prepareAndLoadRatings();
+
+ // ratings from card rarity
+ // some cards can have different rarity -- it's will be used from first set
+ int newRating;
+ switch (card.getRarity()) {
+ case COMMON:
+ newRating = DEFAULT_NOT_RATED_CARD_RATING;
+ break;
+ case UNCOMMON:
+ newRating = DEFAULT_NOT_RATED_UNCOMMON_RATING;
+ break;
+ case RARE:
+ newRating = DEFAULT_NOT_RATED_RARE_RATING;
+ break;
+ case MYTHIC:
+ newRating = DEFAULT_NOT_RATED_MYTHIC_RATING;
+ break;
+ default:
+ newRating = DEFAULT_NOT_RATED_CARD_RATING;
+ break;
}
- Rarity r = card.getRarity();
- if (Rarity.COMMON == r) {
- return DEFAULT_NOT_RATED_CARD_RATING;
- } else if (Rarity.UNCOMMON == r) {
- return DEFAULT_NOT_RATED_UNCOMMON_RATING;
- } else if (Rarity.RARE == r) {
- return DEFAULT_NOT_RATED_RARE_RATING;
- } else if (Rarity.MYTHIC == r) {
- return DEFAULT_NOT_RATED_MYTHIC_RATING;
+ int oldRating = baseRatings.getOrDefault(card.getName(), 0);
+ if (oldRating != 0 && oldRating != newRating) {
+ //log.info("card have different rating by sets: " + card.getName() + " (" + oldRating + " <> " + newRating + ")");
+ }
+
+ if (oldRating != 0) {
+ return oldRating;
+ } else {
+ baseRatings.put(card.getName(), newRating);
+ return newRating;
}
- return DEFAULT_NOT_RATED_CARD_RATING;
}
/**
- * reads the list of sets that have ratings csv files
- * populates the setsWithRatingsToBeLoaded
+ * reads the list of sets that have ratings csv files and read each file
*/
- private synchronized static void readRatingSetList() {
+ public synchronized static void prepareAndLoadRatings() {
+ if (isLoaded) {
+ return;
+ }
+
+ // load sets list
+ List setsToLoad = new LinkedList<>();
try {
- if (setsWithRatingsToBeLoaded == null) {
- setsWithRatingsToBeLoaded = new LinkedList<>();
- InputStream is = RateCard.class.getResourceAsStream(RATINGS_SET_LIST);
- Scanner scanner = new Scanner(is);
- while (scanner.hasNextLine()) {
- String line = scanner.nextLine();
- if (!line.substring(0, 1).equals("#")) {
- setsWithRatingsToBeLoaded.add(line);
- }
+ InputStream is = RateCard.class.getResourceAsStream(RATINGS_SET_LIST);
+ Scanner scanner = new Scanner(is);
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (!line.substring(0, 1).equals("#")) {
+ setsToLoad.add(line);
}
}
- } catch (Exception e) {
- log.info("failed to read ratings set list file: " + RATINGS_SET_LIST);
- e.printStackTrace();
+ } catch (Throwable e) {
+ log.error("Failed to read ratings sets list file: " + RATINGS_SET_LIST, e);
}
- }
- /**
- * Reads ratings from resources and loads them into ratings map
- */
- private synchronized static void readRatings(String expCode) {
- if (ratings == null) {
- ratings = new HashMap<>();
- }
- if (setsWithRatingsToBeLoaded.contains(expCode)) {
- log.info("reading draftbot ratings for the set " + expCode);
- readFromFile(RATINGS_DIR + expCode + ".csv");
- setsWithRatingsToBeLoaded.remove(expCode);
+ // load set data
+ String rateFile = "";
+ try {
+ for (String code : setsToLoad) {
+ //log.info("Reading ratings for the set " + code);
+ rateFile = RATINGS_DIR + code + ".csv";
+ readFromFile(rateFile);
+ }
+ } catch (Exception e) {
+ log.error("Failed to read ratings set file: " + rateFile, e);
}
+
+ isLoaded = true;
}
/**
* reads ratings from the file
*/
private synchronized static void readFromFile(String path) {
+ // card must get max rating from multiple cards
Integer min = Integer.MAX_VALUE, max = 0;
Map thisFileRatings = new HashMap<>();
- try {
- InputStream is = RateCard.class.getResourceAsStream(path);
- Scanner scanner = new Scanner(is);
- while (scanner.hasNextLine()) {
- String line = scanner.nextLine();
- String[] s = line.split(":");
- if (s.length == 2) {
- Integer rating = Integer.parseInt(s[1].trim());
- String name = s[0].trim();
- if (rating > max) {
- max = rating;
- }
- if (rating < min) {
- min = rating;
- }
- thisFileRatings.put(name, rating);
+
+ // load
+ InputStream is = RateCard.class.getResourceAsStream(path);
+ Scanner scanner = new Scanner(is);
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ String[] s = line.split(":");
+ if (s.length == 2) {
+ Integer rating = Integer.parseInt(s[1].trim());
+ String name = s[0].trim();
+ if (rating > max) {
+ max = rating;
}
- }
- // normalize for the file to [1..100]
- for (String name : thisFileRatings.keySet()) {
- int r = thisFileRatings.get(name);
- int newrating = (int) (100.0f * (r - min) / (max - min));
- if (!ratings.containsKey(name) ||
- (ratings.containsKey(name) && newrating > ratings.get(name))) {
- ratings.put(name, newrating);
+ if (rating < min) {
+ min = rating;
}
+ thisFileRatings.put(name, rating);
+ }
+ }
+
+ // normalize for the file to [1..100]
+ for (String name : thisFileRatings.keySet()) {
+ int r = thisFileRatings.get(name);
+ int newRating = (int) (100.0f * (r - min) / (max - min));
+ int oldRating = baseRatings.getOrDefault(name, 0);
+ if (newRating > oldRating) {
+ baseRatings.put(name, newRating);
}
- } catch (Exception e) {
- log.info("failed to read ratings file: " + path);
- e.printStackTrace();
}
}
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/aer.csv b/Mage/src/main/resources/ratings/aer.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/aer.csv
rename to Mage/src/main/resources/ratings/aer.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/akh.csv b/Mage/src/main/resources/ratings/akh.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/akh.csv
rename to Mage/src/main/resources/ratings/akh.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/dom.csv b/Mage/src/main/resources/ratings/dom.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/dom.csv
rename to Mage/src/main/resources/ratings/dom.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/grn.csv b/Mage/src/main/resources/ratings/grn.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/grn.csv
rename to Mage/src/main/resources/ratings/grn.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/hou.csv b/Mage/src/main/resources/ratings/hou.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/hou.csv
rename to Mage/src/main/resources/ratings/hou.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/ima.csv b/Mage/src/main/resources/ratings/ima.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/ima.csv
rename to Mage/src/main/resources/ratings/ima.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/kld.csv b/Mage/src/main/resources/ratings/kld.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/kld.csv
rename to Mage/src/main/resources/ratings/kld.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/m13.csv b/Mage/src/main/resources/ratings/m13.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/m13.csv
rename to Mage/src/main/resources/ratings/m13.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/m19.csv b/Mage/src/main/resources/ratings/m19.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/m19.csv
rename to Mage/src/main/resources/ratings/m19.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/mm3.csv b/Mage/src/main/resources/ratings/mm3.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/mm3.csv
rename to Mage/src/main/resources/ratings/mm3.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/rix.csv b/Mage/src/main/resources/ratings/rix.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/rix.csv
rename to Mage/src/main/resources/ratings/rix.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/rna.csv b/Mage/src/main/resources/ratings/rna.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/rna.csv
rename to Mage/src/main/resources/ratings/rna.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/setsWithRatings.csv b/Mage/src/main/resources/ratings/setsWithRatings.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/setsWithRatings.csv
rename to Mage/src/main/resources/ratings/setsWithRatings.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/uma.csv b/Mage/src/main/resources/ratings/uma.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/uma.csv
rename to Mage/src/main/resources/ratings/uma.csv
diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/xln.csv b/Mage/src/main/resources/ratings/xln.csv
similarity index 100%
rename from Mage.Server.Plugins/Mage.Player.AI/src/main/resources/ratings/xln.csv
rename to Mage/src/main/resources/ratings/xln.csv