Refactor to only rank cards once

This commit is contained in:
mcrawford620
2016-07-10 03:41:07 +00:00
parent 52458fd146
commit 8b5e00f93b
5 changed files with 154 additions and 210 deletions

View File

@@ -37,10 +37,7 @@ public class CardRankerTest {
PaperCard c1 = readCard("makindi_patrol.txt");
list.add(c1);
List<Pair<Double, PaperCard>> ranked = ranker.rankCards(list);
for (Pair<Double, PaperCard> pair : ranked) {
System.out.println(pair.getKey().toString() + " " + pair.getValue().getName());
}
List<PaperCard> ranked = ranker.rankCards(list);
}
/**

View File

@@ -5,6 +5,5 @@ PT:6/6
K:Vigilance
A:AB$ ChangeZone | Cost$Sac<2/Eldrazi.Scion> | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SorcerySpeed$ True | SpellDescription$ Return CARDNAME from your graveyard to your hand. Activate this ability only any time you could cast a sorcery.
SVar:DiscardMe:1
SVar:RemRandomDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/deathless_behemoth.jpg
Oracle:Vigilance\nSacrifice two Eldrazi Scions: Return Deathless Behemoth from your graveyard to your hand. Activate this ability only any time you could cast a sorcery.

View File

@@ -1,15 +1,12 @@
package forge.limited;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.CardRulesPredicates;
import forge.card.DeckHints;
import forge.item.PaperCard;
import org.apache.commons.lang3.tuple.Pair;
import java.awt.print.Paper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -23,16 +20,23 @@ public class CardRanker {
* @param cards PaperCards to rank
* @return List of beans with card rankings
*/
public List<Pair<Double, PaperCard>> rankCards(final Iterable<PaperCard> cards) {
final List<Pair<Double, PaperCard>> ranked = new ArrayList<Pair<Double, PaperCard>>();
public List<PaperCard> rankCards(final Iterable<PaperCard> cards) {
List<Pair<Double, PaperCard>> cardScores = getScores(cards);
getInitialRankings(cards, ranked);
Collections.sort(cardScores, Collections.reverseOrder(new CardRankingComparator()));
Collections.sort(ranked, Collections.reverseOrder(new CardRankingComparator()));
return ranked;
List<PaperCard> rankedCards = new ArrayList<>(cardScores.size());
for (Pair<Double, PaperCard> pair : cardScores) {
System.out.println(pair.getKey().toString() + " " + pair.getValue().getName());
rankedCards.add(pair.getValue());
}
private void getInitialRankings(Iterable<PaperCard> cards, List<Pair<Double, PaperCard>> ranked) {
return rankedCards;
}
private List<Pair<Double, PaperCard>> getScores(Iterable<PaperCard> cards) {
List<Pair<Double, PaperCard>> cardScores = new ArrayList<>();
List<PaperCard> cache = new ArrayList<>();
for (PaperCard card : cards) {
cache.add(card);
@@ -42,6 +46,19 @@ public class CardRanker {
for (int i = 0; i < cache.size(); i++) {
final PaperCard card = cache.get(i);
double score = getRawScore(card, customRankings);
List<PaperCard> otherCards = getCardsExcept(cache, i);
score += calculateSynergies(card, otherCards);
cardScores.add(Pair.of(score, card));
}
return cardScores;
}
private double getRawScore(PaperCard card, String customRankings) {
Double rkg;
if (customRankings != null) {
rkg = DraftRankCache.getCustomRanking(customRankings, card.getName());
@@ -59,19 +76,16 @@ public class CardRanker {
} else {
rawScore = 0.0;
}
return rawScore;
}
double score = rawScore;
private List<PaperCard> getCardsExcept(List<PaperCard> cache, int i) {
List<PaperCard> otherCards = new ArrayList<>();
otherCards.addAll(cache.subList(0, i));
if (i + 1 < cache.size()) {
otherCards.addAll(cache.subList(i + 1, cache.size()));
}
score += calculateSynergies(card, otherCards);
ranked.add(Pair.of(score, card));
}
return otherCards;
}
private double calculateSynergies(PaperCard card, Iterable<PaperCard> otherCards) {
@@ -83,6 +97,16 @@ public class CardRanker {
return synergyScore;
}
private double getScoreForDeckHints(PaperCard card, Iterable<PaperCard> otherCards) {
double score = 0.0;
final DeckHints hints = card.getRules().getAiHints().getDeckHints();
if (hints != null && hints.getType() != DeckHints.Type.NONE) {
final List<PaperCard> comboCards = hints.filter(otherCards);
score = comboCards.size() * 10;
}
return score;
}
private double getScoreForBuffedBy(PaperCard card, Iterable<PaperCard> otherCards) {
double matchBuffScore = 0.0;
Iterable<Map.Entry<String, String>> vars = card.getRules().getMainPart().getVariables();
@@ -91,20 +115,9 @@ public class CardRanker {
String buff = var.getValue();
final Iterable<PaperCard> buffers = Iterables.filter(otherCards,
Predicates.compose(CardRulesPredicates.subType(buff), PaperCard.FN_GET_RULES));
System.out.println(Iterables.size(buffers));
matchBuffScore = Iterables.size(buffers) * 5;
matchBuffScore = Iterables.size(buffers) * 3;
}
}
return matchBuffScore;
}
private double getScoreForDeckHints(PaperCard card, Iterable<PaperCard> otherCards) {
double score = 0.0;
final DeckHints hints = card.getRules().getAiHints().getDeckHints();
if (hints != null && hints.getType() != DeckHints.Type.NONE) {
final List<PaperCard> comboCards = hints.filter(otherCards);
score = comboCards.size() * 3;
}
return score;
}
}

View File

@@ -1,5 +1,6 @@
package forge.limited;
import java.awt.print.Paper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -63,11 +64,11 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
private final List<String> setsWithBasicLands = new ArrayList<String>();
// Views for aiPlayable
private Iterable<PaperCard> colorList;
protected CardRanker ranker = new CardRanker();
private Iterable<PaperCard> onColorCreatures;
private Iterable<PaperCard> onColorNonCreatures;
private static final boolean logToConsole = false;
private static final boolean logToConsole = true;
/**
*
@@ -84,14 +85,13 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
this.deckColors = pClrs;
this.colors = pClrs.getChosenColors();
// removeUnplayables();
// remove Unplayables
final Iterable<PaperCard> playables = Iterables.filter(availableList,
Predicates.compose(CardRulesPredicates.IS_KEPT_IN_AI_DECKS, PaperCard.FN_GET_RULES));
this.aiPlayables = Lists.newArrayList(playables);
this.availableList.removeAll(getAiPlayables());
this.aiPlayables = this.ranker.rankCards(playables);
this.availableList.removeAll(aiPlayables);
findBasicLandSets();
}
/**
@@ -132,7 +132,9 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
public Deck buildDeck(final String landSetCode) {
// 1. Prepare
hasColor = Predicates.or(new MatchColorIdentity(colors), COLORLESS_CARDS);
colorList = Iterables.filter(aiPlayables, Predicates.compose(hasColor, PaperCard.FN_GET_RULES));
Iterable<PaperCard> colorList = Iterables.filter(aiPlayables,
Predicates.compose(hasColor, PaperCard.FN_GET_RULES));
onColorCreatures = Iterables.filter(colorList,
Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES));
onColorNonCreatures = Iterables.filter(colorList,
@@ -153,28 +155,27 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
}
// 3. Add creatures, trying to follow mana curve
addManaCurveCreatures(rankCards(onColorCreatures), 15);
addManaCurveCreatures(onColorCreatures, 15);
// 4.Try to fill up to 22 with on-color non-creature cards
addNonCreatures(rankCards(onColorNonCreatures), numSpellsNeeded - deckList.size());
addNonCreatures(onColorNonCreatures, numSpellsNeeded - deckList.size());
// 5.If we couldn't get up to 22, try to fill up to 22 with on-color
// creature cards
addCreatures(rankCards(onColorCreatures), numSpellsNeeded - deckList.size());
addCreatures(onColorCreatures, numSpellsNeeded - deckList.size());
// 6. If there are still on-color cards, and the average cmc is low, add
// a 23rd card.
if (deckList.size() == numSpellsNeeded && getAverageCMC(deckList) < 3) {
final Iterable<PaperCard> nonLands = Iterables.filter(colorList,
Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES));
if (deckList.size() == numSpellsNeeded && getAverageCMC(deckList) < 3) {
final List<Pair<Double, PaperCard>> list = rankCards(nonLands);
if (!list.isEmpty()) {
final PaperCard c = list.get(0).getValue();
deckList.add(c);
getAiPlayables().remove(c);
final PaperCard card = Iterables.getFirst(nonLands, null);
if (card != null) {
deckList.add(card);
aiPlayables.remove(card);
landsNeeded--;
if (logToConsole) {
System.out.println("Low CMC: " + c.getName());
System.out.println("Low CMC: " + card.getName());
}
}
}
@@ -243,7 +244,7 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
}
i = 0;
System.out.println("NOT PICKED");
for (final PaperCard c : getAiPlayables()) {
for (final PaperCard c : aiPlayables) {
i++;
System.out.println(i + ". " + c.toString() + ": " + c.getRules().getManaCost().toString());
}
@@ -265,7 +266,7 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
}
final PaperCard c = deckList.get(MyRandom.getRandom().nextInt(deckList.size() - 1));
deckList.remove(c);
getAiPlayables().add(c);
aiPlayables.add(c);
if (logToConsole) {
System.out.println(" - Removed " + c.getName() + " randomly.");
}
@@ -275,17 +276,17 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
if (logToConsole) {
System.out.println("WARNING: Fixing deck size, currently " + deckList.size() + " cards.");
}
if (getAiPlayables().size() > 1) {
final PaperCard c = getAiPlayables().get(MyRandom.getRandom().nextInt(getAiPlayables().size() - 1));
if (aiPlayables.size() > 1) {
final PaperCard c = aiPlayables.get(MyRandom.getRandom().nextInt(aiPlayables.size() - 1));
deckList.add(c);
getAiPlayables().remove(c);
aiPlayables.remove(c);
if (logToConsole) {
System.out.println(" - Added " + c.getName() + " randomly.");
}
} else if (getAiPlayables().size() == 1) {
final PaperCard c = getAiPlayables().get(0);
} else if (aiPlayables.size() == 1) {
final PaperCard c = aiPlayables.get(0);
deckList.add(c);
getAiPlayables().remove(c);
aiPlayables.remove(c);
if (logToConsole) {
System.out.println(" - Added " + c.getName() + " randomly.");
}
@@ -429,39 +430,41 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
final List<String> inverseDuals = getInverseDualLandList();
final Iterable<PaperCard> lands = Iterables.filter(aiPlayables,
Predicates.compose(CardRulesPredicates.Presets.IS_NONBASIC_LAND, PaperCard.FN_GET_RULES));
final List<Pair<Double, PaperCard>> ranked = rankCards(lands);
for (final Pair<Double, PaperCard> bean : ranked) {
List<PaperCard> landsToAdd = new ArrayList<>();
for (final PaperCard card : lands) {
if (landsNeeded > 0) {
// Throw out any dual-lands for the wrong colors. Assume
// everything else is either
// (a) dual-land of the correct two colors, or
// (b) a land that generates colorless mana and has some other
// beneficial effect.
if (!inverseDuals.contains(bean.getValue().getName())) {
deckList.add(bean.getValue());
aiPlayables.remove(bean.getValue());
if (!inverseDuals.contains(card.getName())) {
landsToAdd.add(card);
landsNeeded--;
if (logToConsole) {
System.out.println("NonBasicLand[" + landsNeeded + "]:" + bean.getValue().getName());
System.out.println("NonBasicLand[" + landsNeeded + "]:" + card.getName());
}
}
}
}
deckList.addAll(landsToAdd);
aiPlayables.removeAll(landsToAdd);
}
/**
* Add a third color to the deck.
*
* @param nCards
* @param num
* number to add
*/
private void addThirdColorCards(int nCards) {
if (nCards > 0) {
private void addThirdColorCards(int num) {
if (num > 0) {
final Iterable<PaperCard> others = Iterables.filter(aiPlayables,
Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES));
List<Pair<Double, PaperCard>> ranked = rankCards(others);
for (final Pair<Double, PaperCard> bean : ranked) {
List<PaperCard> toAdd = new ArrayList<>(num);
for (final PaperCard card : others) {
// Want a card that has just one "off" color.
final ColorSet off = colors.getOffColors(bean.getValue().getRules().getColor());
final ColorSet off = colors.getOffColors(card.getRules().getColor());
if (off.isMonoColor()) {
colors = ColorSet.fromMask(colors.getColor() | off.getColor());
break;
@@ -472,45 +475,47 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
DeckGeneratorBase.COLORLESS_CARDS);
final Iterable<PaperCard> threeColorList = Iterables.filter(aiPlayables,
Predicates.compose(hasColor, PaperCard.FN_GET_RULES));
ranked = rankCards(threeColorList);
for (final Pair<Double, PaperCard> bean : ranked) {
if (nCards > 0) {
deckList.add(bean.getValue());
aiPlayables.remove(bean.getValue());
nCards--;
for (final PaperCard card : threeColorList) {
if (num > 0) {
toAdd.add(card);
num--;
if (logToConsole) {
System.out.println("Third Color[" + nCards + "]:" + bean.getValue().getName() + "("
+ bean.getValue().getRules().getManaCost() + ")");
System.out.println("Third Color[" + num + "]:" + card.getName() + "("
+ card.getRules().getManaCost() + ")");
}
} else {
break;
}
}
deckList.addAll(toAdd);
aiPlayables.removeAll(toAdd);
}
}
/**
* Add random cards to the deck.
*
* @param nCards
* @param num
* number to add
*/
private void addRandomCards(int nCards) {
private void addRandomCards(int num) {
final Iterable<PaperCard> others = Iterables.filter(aiPlayables,
Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES));
final List<Pair<Double, PaperCard>> ranked = rankCards(others);
for (final Pair<Double, PaperCard> bean : ranked) {
if (nCards > 0) {
deckList.add(bean.getValue());
aiPlayables.remove(bean.getValue());
nCards--;
List <PaperCard> toAdd = new ArrayList<>(num);
for (final PaperCard card : others) {
if (num > 0) {
toAdd.add(card);
num--;
if (logToConsole) {
System.out.println("Random[" + nCards + "]:" + bean.getValue().getName() + "("
+ bean.getValue().getRules().getManaCost() + ")");
System.out.println("Random[" + num + "]:" + card.getName() + "("
+ card.getRules().getManaCost() + ")");
}
} else {
break;
}
}
deckList.addAll(toAdd);
aiPlayables.removeAll(toAdd);
}
/**
@@ -519,60 +524,24 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
* @param nonCreatures
* cards to choose from
* @param num
* number to add
*/
private void addNonCreatures(final List<Pair<Double, PaperCard>> nonCreatures, int num) {
for (final Pair<Double, PaperCard> bean : nonCreatures) {
private void addNonCreatures(final Iterable<PaperCard> nonCreatures, int num) {
List<PaperCard> toAdd = new ArrayList<>(num);
for (final PaperCard card : nonCreatures) {
if (num > 0) {
final PaperCard cardToAdd = bean.getValue();
deckList.add(cardToAdd);
toAdd.add(card);
num--;
getAiPlayables().remove(cardToAdd);
if (logToConsole) {
System.out.println("Others[" + num + "]:" + cardToAdd.getName() + " ("
+ cardToAdd.getRules().getManaCost() + ")");
System.out.println("Others[" + num + "]:" + card.getName() + " ("
+ card.getRules().getManaCost() + ")");
}
num = addDeckHintsCards(cardToAdd, num);
} else {
break;
}
}
}
/**
* Add cards that work well with the given card.
*
* @param cardToAdd
* card being checked
* @param num
* number of cards
* @return number left after adding
*/
private int addDeckHintsCards(final PaperCard cardToAdd, int num) {
// cards with DeckHints will try to grab additional cards from the pool
final DeckHints hints = cardToAdd.getRules().getAiHints().getDeckHints();
if (hints != null && hints.getType() != DeckHints.Type.NONE) {
final Iterable<PaperCard> onColor = Iterables.filter(aiPlayables, Predicates.compose(hasColor, PaperCard.FN_GET_RULES));
final List<PaperCard> comboCards = hints.filter(onColor);
if (logToConsole) {
System.out.println("Found " + comboCards.size() + " cards for " + cardToAdd.getName());
}
for (final Pair<Double, PaperCard> comboBean : rankCards(comboCards)) {
if (num > 0) {
// This is not exactly right, because the
// rankedComboCards could include creatures and
// non-creatures.
// This code could add too many of one or the other.
final PaperCard combo = comboBean.getValue();
deckList.add(combo);
num--;
getAiPlayables().remove(combo);
} else {
break;
}
}
}
return num;
deckList.addAll(toAdd);
aiPlayables.removeAll(toAdd);
}
/**
@@ -620,10 +589,10 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
}
}
if (numCreatures > 0) {
addCreatures(rankCards(onColorCreatures), numCreatures);
addCreatures(onColorCreatures, numCreatures);
}
if (numOthers > 0) {
addNonCreatures(rankCards(onColorNonCreatures), numOthers);
addNonCreatures(onColorNonCreatures, numOthers);
}
// If we added some replacement cards, and we still have cards available
// in aiPlayables, call this function again in case the replacement
@@ -639,22 +608,23 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
* @param creatures
* cards to choose from
* @param num
* number to add
*/
private void addCreatures(final List<Pair<Double, PaperCard>> creatures, int num) {
for (final Pair<Double, PaperCard> bean : creatures) {
private void addCreatures(final Iterable<PaperCard> creatures, int num) {
List<PaperCard> creaturesToAdd = new ArrayList<>(num);
for (final PaperCard card : creatures) {
if (num > 0) {
final PaperCard c = bean.getValue();
deckList.add(c);
creaturesToAdd.add(card);
num--;
getAiPlayables().remove(c);
if (logToConsole) {
System.out.println("Creature[" + num + "]:" + c.getName() + " (" + c.getRules().getManaCost() + ")");
System.out.println("Creature[" + num + "]:" + card.getName() + " (" + card.getRules().getManaCost() + ")");
}
num = addDeckHintsCards(c, num);
} else {
break;
}
}
deckList.addAll(creaturesToAdd);
aiPlayables.removeAll(creaturesToAdd);
}
/**
@@ -665,8 +635,9 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
* @param creatures
* cards to choose from
* @param num
* number to add
*/
private void addManaCurveCreatures(final List<Pair<Double, PaperCard>> creatures, int num) {
private void addManaCurveCreatures(final Iterable<PaperCard> creatures, int num) {
final Map<Integer, Integer> creatureCosts = new HashMap<Integer, Integer>();
for (int i = 1; i < 7; i++) {
creatureCosts.put(i, 0);
@@ -683,9 +654,9 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
creatureCosts.put(cmc, creatureCosts.get(cmc) + 1);
}
for (final Pair<Double, PaperCard> bean : creatures) {
final PaperCard c = bean.getValue();
int cmc = c.getRules().getManaCost().getCMC();
List<PaperCard> creaturesToAdd = new ArrayList<>(num);
for (final PaperCard card : creatures) {
int cmc = card.getRules().getManaCost().getCMC();
if (cmc < 1) {
cmc = 1;
} else if (cmc > 6) {
@@ -708,58 +679,24 @@ public class LimitedDeckBuilder extends DeckGeneratorBase {
}
if (willAddCreature) {
deckList.add(c);
creaturesToAdd.add(card);
num--;
getAiPlayables().remove(c);
creatureCosts.put(cmc, creatureCosts.get(cmc) + 1);
if (logToConsole) {
System.out.println("Creature[" + num + "]:" + c.getName() + " (" + c.getRules().getManaCost() + ")");
System.out.println("Creature[" + num + "]:" + card.getName() + " (" + card.getRules().getManaCost() + ")");
}
num = addDeckHintsCards(c, num);
} else {
if (logToConsole) {
System.out.println(c.getName() + " not added because CMC " + c.getRules().getManaCost().getCMC()
System.out.println(card.getName() + " not added because CMC " + card.getRules().getManaCost().getCMC()
+ " has " + currentAtCmc + " already.");
}
}
if (num <= 0) {
break;
}
}
}
/**
* Rank cards.
*
* @param cards
* CardPrinteds to rank
* @return List of beans with card rankings
*/
protected List<Pair<Double, PaperCard>> rankCards(final Iterable<PaperCard> cards) {
final List<Pair<Double, PaperCard>> ranked = new ArrayList<Pair<Double, PaperCard>>();
for (final PaperCard card : cards) {
Double rkg;
String customRankings = IBoosterDraft.CUSTOM_RANKINGS_FILE[0];
if (customRankings != null) {
rkg = DraftRankCache.getCustomRanking(customRankings, card.getName());
if (rkg == null) {
// try the default rankings if custom rankings contain no entry
rkg = DraftRankCache.getRanking(card.getName(), card.getEdition());
}
} else {
rkg = DraftRankCache.getRanking(card.getName(), card.getEdition());
}
if (rkg != null) {
ranked.add(Pair.of(rkg, card));
} else {
ranked.add(Pair.of(0.0, card));
}
}
Collections.sort(ranked, new CardRankingComparator());
return ranked;
deckList.addAll(creaturesToAdd);
aiPlayables.removeAll(creaturesToAdd);
}
/**

View File

@@ -31,13 +31,11 @@ public class SealedDeckBuilder extends LimitedDeckBuilder {
* @return DeckColors
*/
private ColorSet chooseColors() {
List<Pair<Double, PaperCard>> rankedCards = rankCards(getAiPlayables());
// choose colors based on top 33% of cards
final List<PaperCard> colorChooserList = new ArrayList<PaperCard>();
double limit = rankedCards.size() * .33;
double limit = getAiPlayables().size() * .33;
for (int i = 0; i < limit; i++) {
PaperCard cp = rankedCards.get(i).getValue();
PaperCard cp = getAiPlayables().get(i);
colorChooserList.add(cp);
//System.out.println(cp.getName() + " " + cp.getRules().getManaCost().toString());
}
@@ -64,8 +62,8 @@ public class SealedDeckBuilder extends LimitedDeckBuilder {
}
}
String color1 = MagicColor.Constant.GREEN;
String color2 = MagicColor.Constant.BLACK;
String color1;
String color2;
final Random r = MyRandom.getRandom();
if (maxColors.size() > 1) {
int n = r.nextInt(maxColors.size());