diff --git a/forge-gui-desktop/src/test/java/forge/CardRankerTest.java b/forge-gui-desktop/src/test/java/forge/CardRankerTest.java index 3e5820842fb..bbfa4971209 100644 --- a/forge-gui-desktop/src/test/java/forge/CardRankerTest.java +++ b/forge-gui-desktop/src/test/java/forge/CardRankerTest.java @@ -37,10 +37,7 @@ public class CardRankerTest { PaperCard c1 = readCard("makindi_patrol.txt"); list.add(c1); - List> ranked = ranker.rankCards(list); - for (Pair pair : ranked) { - System.out.println(pair.getKey().toString() + " " + pair.getValue().getName()); - } + List ranked = ranker.rankCards(list); } /** diff --git a/forge-gui/res/cardsfolder/d/deathless_behemoth.txt b/forge-gui/res/cardsfolder/d/deathless_behemoth.txt index 67120529a9f..41b00eb4abb 100644 --- a/forge-gui/res/cardsfolder/d/deathless_behemoth.txt +++ b/forge-gui/res/cardsfolder/d/deathless_behemoth.txt @@ -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. diff --git a/forge-gui/src/main/java/forge/limited/CardRanker.java b/forge-gui/src/main/java/forge/limited/CardRanker.java index 6727e432537..1c5db755e53 100644 --- a/forge-gui/src/main/java/forge/limited/CardRanker.java +++ b/forge-gui/src/main/java/forge/limited/CardRanker.java @@ -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> rankCards(final Iterable cards) { - final List> ranked = new ArrayList>(); + public List rankCards(final Iterable cards) { + List> cardScores = getScores(cards); - getInitialRankings(cards, ranked); + Collections.sort(cardScores, Collections.reverseOrder(new CardRankingComparator())); - Collections.sort(ranked, Collections.reverseOrder(new CardRankingComparator())); - return ranked; + List rankedCards = new ArrayList<>(cardScores.size()); + for (Pair pair : cardScores) { + System.out.println(pair.getKey().toString() + " " + pair.getValue().getName()); + rankedCards.add(pair.getValue()); + } + + return rankedCards; } - private void getInitialRankings(Iterable cards, List> ranked) { + private List> getScores(Iterable cards) { + List> cardScores = new ArrayList<>(); + List cache = new ArrayList<>(); for (PaperCard card : cards) { cache.add(card); @@ -42,36 +46,46 @@ public class CardRanker { for (int i = 0; i < cache.size(); i++) { final PaperCard card = cache.get(i); - Double rkg; - 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()); - } + double score = getRawScore(card, customRankings); - double rawScore; - if (rkg != null) { - rawScore = 100 - (100 * rkg); - } else { - rawScore = 0.0; - } - - double score = rawScore; - - List otherCards = new ArrayList<>(); - otherCards.addAll(cache.subList(0, i)); - if (i + 1 < cache.size()) { - otherCards.addAll(cache.subList(i + 1, cache.size())); - } + List otherCards = getCardsExcept(cache, i); score += calculateSynergies(card, otherCards); - ranked.add(Pair.of(score, card)); + 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()); + 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()); + } + + double rawScore; + if (rkg != null) { + rawScore = 100 - (100 * rkg); + } else { + rawScore = 0.0; + } + return rawScore; + } + + private List getCardsExcept(List cache, int i) { + List otherCards = new ArrayList<>(); + otherCards.addAll(cache.subList(0, i)); + if (i + 1 < cache.size()) { + otherCards.addAll(cache.subList(i + 1, cache.size())); + } + return otherCards; } private double calculateSynergies(PaperCard card, Iterable otherCards) { @@ -83,6 +97,16 @@ public class CardRanker { return synergyScore; } + private double getScoreForDeckHints(PaperCard card, Iterable otherCards) { + double score = 0.0; + final DeckHints hints = card.getRules().getAiHints().getDeckHints(); + if (hints != null && hints.getType() != DeckHints.Type.NONE) { + final List comboCards = hints.filter(otherCards); + score = comboCards.size() * 10; + } + return score; + } + private double getScoreForBuffedBy(PaperCard card, Iterable otherCards) { double matchBuffScore = 0.0; Iterable> vars = card.getRules().getMainPart().getVariables(); @@ -91,20 +115,9 @@ public class CardRanker { String buff = var.getValue(); final Iterable 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 otherCards) { - double score = 0.0; - final DeckHints hints = card.getRules().getAiHints().getDeckHints(); - if (hints != null && hints.getType() != DeckHints.Type.NONE) { - final List comboCards = hints.filter(otherCards); - score = comboCards.size() * 3; - } - return score; - } } diff --git a/forge-gui/src/main/java/forge/limited/LimitedDeckBuilder.java b/forge-gui/src/main/java/forge/limited/LimitedDeckBuilder.java index 0572880713f..18488597c8d 100644 --- a/forge-gui/src/main/java/forge/limited/LimitedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/limited/LimitedDeckBuilder.java @@ -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 setsWithBasicLands = new ArrayList(); // Views for aiPlayable - private Iterable colorList; + protected CardRanker ranker = new CardRanker(); private Iterable onColorCreatures; private Iterable 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 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 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. - final Iterable nonLands = Iterables.filter(colorList, - Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); if (deckList.size() == numSpellsNeeded && getAverageCMC(deckList) < 3) { - final List> list = rankCards(nonLands); - if (!list.isEmpty()) { - final PaperCard c = list.get(0).getValue(); - deckList.add(c); - getAiPlayables().remove(c); + final Iterable nonLands = Iterables.filter(colorList, + Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); + 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 inverseDuals = getInverseDualLandList(); final Iterable lands = Iterables.filter(aiPlayables, Predicates.compose(CardRulesPredicates.Presets.IS_NONBASIC_LAND, PaperCard.FN_GET_RULES)); - final List> ranked = rankCards(lands); - for (final Pair bean : ranked) { + List 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 others = Iterables.filter(aiPlayables, Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); - List> ranked = rankCards(others); - for (final Pair bean : ranked) { + List 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 threeColorList = Iterables.filter(aiPlayables, Predicates.compose(hasColor, PaperCard.FN_GET_RULES)); - ranked = rankCards(threeColorList); - for (final Pair 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 others = Iterables.filter(aiPlayables, Predicates.compose(CardRulesPredicates.Presets.IS_NON_LAND, PaperCard.FN_GET_RULES)); - final List> ranked = rankCards(others); - for (final Pair bean : ranked) { - if (nCards > 0) { - deckList.add(bean.getValue()); - aiPlayables.remove(bean.getValue()); - nCards--; + List 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> nonCreatures, int num) { - for (final Pair bean : nonCreatures) { + private void addNonCreatures(final Iterable nonCreatures, int num) { + List 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 onColor = Iterables.filter(aiPlayables, Predicates.compose(hasColor, PaperCard.FN_GET_RULES)); - final List comboCards = hints.filter(onColor); - if (logToConsole) { - System.out.println("Found " + comboCards.size() + " cards for " + cardToAdd.getName()); - } - for (final Pair 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> creatures, int num) { - for (final Pair bean : creatures) { + private void addCreatures(final Iterable creatures, int num) { + List 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> creatures, int num) { + private void addManaCurveCreatures(final Iterable creatures, int num) { final Map creatureCosts = new HashMap(); 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 bean : creatures) { - final PaperCard c = bean.getValue(); - int cmc = c.getRules().getManaCost().getCMC(); + List 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> rankCards(final Iterable cards) { - final List> ranked = new ArrayList>(); - 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); } /** diff --git a/forge-gui/src/main/java/forge/limited/SealedDeckBuilder.java b/forge-gui/src/main/java/forge/limited/SealedDeckBuilder.java index 980d5b931ab..c25a397d87f 100644 --- a/forge-gui/src/main/java/forge/limited/SealedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/limited/SealedDeckBuilder.java @@ -31,13 +31,11 @@ public class SealedDeckBuilder extends LimitedDeckBuilder { * @return DeckColors */ private ColorSet chooseColors() { - List> rankedCards = rankCards(getAiPlayables()); - // choose colors based on top 33% of cards final List colorChooserList = new ArrayList(); - 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());