Deck generation based on color profile, strictly follows profile

cmc curve uses relative weights
This commit is contained in:
Maxmtg
2013-04-23 22:20:55 +00:00
parent deeba62794
commit 4e70925eac
7 changed files with 114 additions and 85 deletions

View File

@@ -26,8 +26,6 @@ import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import forge.card.MagicColor;
/** /**
* <p> * <p>
* Constant interface. * Constant interface.

View File

@@ -17,9 +17,11 @@
*/ */
package forge.deck.generate; package forge.deck.generate;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.generate.GenerateDeckUtil.FilterCMC; import forge.deck.generate.GenerateDeckUtil.FilterCMC;
@@ -40,12 +42,13 @@ public class Generate2ColorDeck extends GenerateColoredDeckBase {
@Override protected final float getCreatPercentage() { return 0.36f; } @Override protected final float getCreatPercentage() { return 0.36f; }
@Override protected final float getSpellPercentage() { return 0.25f; } @Override protected final float getSpellPercentage() { return 0.25f; }
final List<FilterCMC> cmcLevels = Arrays.asList( @SuppressWarnings("unchecked")
new GenerateDeckUtil.FilterCMC(0, 2), final List<ImmutablePair<FilterCMC, Integer>> cmcRelativeWeights = Lists.newArrayList(
new GenerateDeckUtil.FilterCMC(3, 4), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(0, 2), 6),
new GenerateDeckUtil.FilterCMC(5, 6), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(3, 4), 4),
new GenerateDeckUtil.FilterCMC(7, 20)); ImmutablePair.of(new GenerateDeckUtil.FilterCMC(5, 6), 2),
final int[] cmcAmounts = {10, 8, 6, 2}; ImmutablePair.of(new GenerateDeckUtil.FilterCMC(7, 20), 1)
);
// mana curve of the card pool // mana curve of the card pool
// 20x 0 - 2 // 20x 0 - 2
@@ -77,15 +80,14 @@ public class Generate2ColorDeck extends GenerateColoredDeckBase {
public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) { public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) {
addCreaturesAndSpells(size, cmcLevels, cmcAmounts, pt); addCreaturesAndSpells(size, cmcRelativeWeights, pt);
// Add lands // Add lands
int numLands = (int) (getLandsPercentage() * size); int numLands = Math.round(size * getLandsPercentage());
adjustDeckSize(size - numLands);
tmpDeck.append("numLands:").append(numLands).append("\n"); tmpDeck.append(String.format("Adjusted deck size to: %d, should add %d land(s)%n", size - numLands, numLands));
// Add dual lands // Add dual lands
List<String> duals = GenerateDeckUtil.getDualLandList(colors); List<String> duals = GenerateDeckUtil.getDualLandList(colors);
for (String s : duals) { for (String s : duals) {
this.cardCounts.put(s, 0); this.cardCounts.put(s, 0);
@@ -97,8 +99,7 @@ public class Generate2ColorDeck extends GenerateColoredDeckBase {
addBasicLand(numLands); addBasicLand(numLands);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n"); tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
adjustDeckSize(size); //System.out.println(tmpDeck.toString());
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
return tDeck; return tDeck;
} }

View File

@@ -17,9 +17,12 @@
*/ */
package forge.deck.generate; package forge.deck.generate;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.generate.GenerateDeckUtil.FilterCMC; import forge.deck.generate.GenerateDeckUtil.FilterCMC;
@@ -36,11 +39,12 @@ import forge.item.ItemPoolView;
* @version $Id$ * @version $Id$
*/ */
public class Generate3ColorDeck extends GenerateColoredDeckBase { public class Generate3ColorDeck extends GenerateColoredDeckBase {
final List<FilterCMC> cmcLevels = Arrays.asList( @SuppressWarnings("unchecked")
new GenerateDeckUtil.FilterCMC(0, 2), final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
new GenerateDeckUtil.FilterCMC(3, 5), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(0, 2), 12),
new GenerateDeckUtil.FilterCMC(6, 20)); ImmutablePair.of(new GenerateDeckUtil.FilterCMC(3, 5), 9),
final int[] cmcAmounts = {12, 9, 3}; ImmutablePair.of(new GenerateDeckUtil.FilterCMC(6, 20), 3)
);
/** /**
* <p> * <p>
@@ -76,28 +80,24 @@ public class Generate3ColorDeck extends GenerateColoredDeckBase {
* @return a {@link forge.CardList} object. * @return a {@link forge.CardList} object.
*/ */
public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) { public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) {
addCreaturesAndSpells(size, cmcLevels, cmcAmounts, pt); addCreaturesAndSpells(size, cmcLevels, pt);
// Add lands // Add lands
int numLands = (int) (getLandsPercentage() * size); int numLands = Math.round(size * getLandsPercentage());
adjustDeckSize(size - numLands);
tmpDeck.append("numLands:").append(numLands).append("\n"); tmpDeck.append("numLands:").append(numLands).append("\n");
// Add dual lands // Add dual lands
List<String> duals = GenerateDeckUtil.getDualLandList(colors); List<String> duals = GenerateDeckUtil.getDualLandList(colors);
for (String s : duals) { for (String s : duals) {
this.cardCounts.put(s, 0); this.cardCounts.put(s, 0);
} }
int dblsAdded = addSomeStr((numLands / 4), duals); int dblsAdded = addSomeStr((numLands / 4), duals);
numLands -= dblsAdded; numLands -= dblsAdded;
addBasicLand(numLands); addBasicLand(numLands);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n"); tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
adjustDeckSize(size);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
return tDeck; return tDeck;
} }
} }

View File

@@ -17,9 +17,12 @@
*/ */
package forge.deck.generate; package forge.deck.generate;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.deck.generate.GenerateDeckUtil.FilterCMC; import forge.deck.generate.GenerateDeckUtil.FilterCMC;
import forge.game.player.PlayerType; import forge.game.player.PlayerType;
@@ -35,11 +38,12 @@ import forge.item.ItemPoolView;
* @version $Id$ * @version $Id$
*/ */
public class Generate5ColorDeck extends GenerateColoredDeckBase { public class Generate5ColorDeck extends GenerateColoredDeckBase {
final List<FilterCMC> cmcLevels = Arrays.asList( @SuppressWarnings("unchecked")
new GenerateDeckUtil.FilterCMC(0, 2), final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
new GenerateDeckUtil.FilterCMC(3, 5), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(0, 2), 3),
new GenerateDeckUtil.FilterCMC(6, 20)); ImmutablePair.of(new GenerateDeckUtil.FilterCMC(3, 5), 2),
final int[] cmcAmounts = {15, 10, 5}; ImmutablePair.of(new GenerateDeckUtil.FilterCMC(6, 20), 1)
);
// resulting mana curve of the card pool // resulting mana curve of the card pool
// 30x 0 - 2 // 30x 0 - 2
@@ -66,28 +70,24 @@ public class Generate5ColorDeck extends GenerateColoredDeckBase {
* @return a {@link forge.CardList} object. * @return a {@link forge.CardList} object.
*/ */
public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) { public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) {
addCreaturesAndSpells(size, cmcLevels, cmcAmounts, pt); addCreaturesAndSpells(size, cmcLevels, pt);
// Add lands // Add lands
int numLands = (int) (getLandsPercentage() * size); int numLands = Math.round(size * getLandsPercentage());
adjustDeckSize(size - numLands);
tmpDeck.append("numLands:").append(numLands).append("\n"); tmpDeck.append("numLands:").append(numLands).append("\n");
// Add dual lands // Add dual lands
List<String> duals = GenerateDeckUtil.getDualLandList(colors); List<String> duals = GenerateDeckUtil.getDualLandList(colors);
for (String s : duals) { for (String s : duals) {
this.cardCounts.put(s, 0); this.cardCounts.put(s, 0);
} }
int dblsAdded = addSomeStr((numLands / 4), duals); int dblsAdded = addSomeStr((numLands / 4), duals);
numLands -= dblsAdded; numLands -= dblsAdded;
addBasicLand(numLands); addBasicLand(numLands);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n"); tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
adjustDeckSize(size);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
return tDeck; return tDeck;
} }
} }

View File

@@ -17,7 +17,6 @@
*/ */
package forge.deck.generate; package forge.deck.generate;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -25,9 +24,12 @@ import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.Constant; import forge.Constant;
import forge.Singletons; import forge.Singletons;
@@ -73,20 +75,24 @@ public abstract class GenerateColoredDeckBase {
tDeck = new ItemPool<CardPrinted>(CardPrinted.class); tDeck = new ItemPool<CardPrinted>(CardPrinted.class);
} }
protected void addCreaturesAndSpells(int size, List<FilterCMC> cmcLevels, int[] cmcAmounts, PlayerType pt) { protected void addCreaturesAndSpells(int size, List<ImmutablePair<FilterCMC, Integer>> cmcLevels, PlayerType pt) {
tmpDeck.append("Building deck of ").append(size).append("cards\n");
final Iterable<CardPrinted> cards = selectCardsOfMatchingColorForPlayer(pt); final Iterable<CardPrinted> cards = selectCardsOfMatchingColorForPlayer(pt);
// build subsets based on type // build subsets based on type
final Iterable<CardPrinted> creatures = Iterables.filter(cards, Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, CardPrinted.FN_GET_RULES)); final Iterable<CardPrinted> creatures = Iterables.filter(cards, Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, CardPrinted.FN_GET_RULES));
final int creatCnt = (int) (getCreatPercentage() * size); final int creatCnt = (int) Math.ceil(getCreatPercentage() * size);
tmpDeck.append("Creature Count:").append(creatCnt).append("\n"); tmpDeck.append("Creatures to add:").append(creatCnt).append("\n");
addCmcAdjusted(creatures, creatCnt, cmcLevels, cmcAmounts); addCmcAdjusted(creatures, creatCnt, cmcLevels);
Predicate<CardPrinted> preSpells = Predicates.compose(CardRulesPredicates.Presets.IS_NONCREATURE_SPELL_FOR_GENERATOR, CardPrinted.FN_GET_RULES); Predicate<CardPrinted> preSpells = Predicates.compose(CardRulesPredicates.Presets.IS_NONCREATURE_SPELL_FOR_GENERATOR, CardPrinted.FN_GET_RULES);
final Iterable<CardPrinted> spells = Iterables.filter(cards, preSpells); final Iterable<CardPrinted> spells = Iterables.filter(cards, preSpells);
final int spellCnt = (int) (getSpellPercentage() * size); final int spellCnt = (int) Math.ceil(getSpellPercentage() * size);
tmpDeck.append("Spell Count:").append(spellCnt).append("\n"); tmpDeck.append("Spells to add:").append(spellCnt).append("\n");
addCmcAdjusted(spells, spellCnt, cmcLevels, cmcAmounts); addCmcAdjusted(spells, spellCnt, cmcLevels);
tmpDeck.append(String.format("Current deck size: %d... should be %f%n", tDeck.countAll(), size * (getCreatPercentage() + getSpellPercentage())));
} }
public ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) { public ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) {
@@ -97,19 +103,22 @@ public abstract class GenerateColoredDeckBase {
for (int i = 0; i < cnt; i++) { for (int i = 0; i < cnt; i++) {
CardPrinted cp; CardPrinted cp;
int lc = 0; int lc = 0;
int srcLen = source.size();
do { do {
cp = source.get(this.r.nextInt(source.size())); cp = source.get(this.r.nextInt(srcLen));
lc++; lc++;
} while ((this.cardCounts.get(cp.getName()) > (this.maxDuplicates - 1)) && (lc <= 100)); } while (this.cardCounts.get(cp.getName()) > this.maxDuplicates - 1 && lc <= 100);
if (lc > 100) { if (lc > 100) {
throw new RuntimeException("Generate2ColorDeck : get2ColorDeck -- looped too much -- Cr12"); throw new RuntimeException("Generate2ColorDeck : get2ColorDeck -- looped too much -- Cr12");
} }
tDeck.add(CardDb.instance().getCard(cp.getName(), Aggregates.random(cp.getRules().getSets()))); tDeck.add(cp);
final int n = this.cardCounts.get(cp.getName()); final int n = this.cardCounts.get(cp.getName());
this.cardCounts.put(cp.getName(), n + 1); this.cardCounts.put(cp.getName(), n + 1);
tmpDeck.append(cp.getName() + " " + cp.getRules().getManaCost() + "\n"); if( n + 1 == this.maxDuplicates )
source.remove(cp);
tmpDeck.append(String.format("(%d) %s [%s]%n", cp.getRules().getManaCost().getCMC(), cp.getName(), cp.getRules().getManaCost()));
} }
} }
@@ -136,6 +145,8 @@ public abstract class GenerateColoredDeckBase {
} }
protected void addBasicLand(int cnt) { protected void addBasicLand(int cnt) {
tmpDeck.append(cnt).append(" basic lands remain").append("\n");
// attempt to optimize basic land counts according to colors of picked cards // attempt to optimize basic land counts according to colors of picked cards
final Map<String, Integer> clrCnts = countLands(tDeck); final Map<String, Integer> clrCnts = countLands(tDeck);
// total of all ClrCnts // total of all ClrCnts
@@ -147,24 +158,23 @@ public abstract class GenerateColoredDeckBase {
tmpDeck.append("totalColor:").append(totalColor).append("\n"); tmpDeck.append("totalColor:").append(totalColor).append("\n");
int landsLeft = cnt;
for (Entry<String, Integer> c : clrCnts.entrySet()) { for (Entry<String, Integer> c : clrCnts.entrySet()) {
String color = c.getKey(); String color = c.getKey();
// calculate number of lands for each color // calculate number of lands for each color
float p = (float) c.getValue() / totalColor; final int nLand = Math.min(landsLeft, Math.round(cnt * c.getValue() / totalColor));
final int nLand = (int) (cnt * p);
tmpDeck.append("nLand-").append(color).append(":").append(nLand).append("\n"); tmpDeck.append("nLand-").append(color).append(":").append(nLand).append("\n");
// just to prevent a null exception by the deck size fixing // just to prevent a null exception by the deck size fixing code
// code
this.cardCounts.put(color, nLand); this.cardCounts.put(color, nLand);
CardPrinted cp = CardDb.instance().getCard(color); CardPrinted cp = CardDb.instance().getCard(color);
String basicLandSet = Aggregates.random(cp.getRules().getSets()); String basicLandSet = Aggregates.random(cp.getRules().getSets());
for (int j = 0; j <= nLand; j++) {
tDeck.add(CardDb.instance().getCard(cp.getName(), basicLandSet)); tDeck.add(CardDb.instance().getCard(cp.getName(), basicLandSet), nLand);
} landsLeft -= nLand;
} }
} }
@@ -191,19 +201,34 @@ public abstract class GenerateColoredDeckBase {
} }
} }
protected void addCmcAdjusted(Iterable<CardPrinted> source, int cnt, List<FilterCMC> cmcLevels, int[] cmcAmounts) { protected void addCmcAdjusted(Iterable<CardPrinted> source, int cnt, List<ImmutablePair<FilterCMC, Integer>> cmcLevels) {
final List<CardPrinted> curved = new ArrayList<CardPrinted>(); int totalWeight = 0;
for (ImmutablePair<FilterCMC, Integer> pair : cmcLevels) {
for (int i = 0; i < cmcAmounts.length; i++) { totalWeight += pair.getRight();
Iterable<CardPrinted> matchingCards = Iterables.filter(source, Predicates.compose(cmcLevels.get(i), CardPrinted.FN_GET_RULES));
curved.addAll(Aggregates.random(matchingCards, cmcAmounts[i]));
} }
float variability = 0.6f; // if set to 1, you'll get minimum cards to choose from
float desiredWeight = (float)cnt / ( maxDuplicates * variability );
float desiredOverTotal = desiredWeight / totalWeight;
float requestedOverTotal = (float)cnt / totalWeight;
for (ImmutablePair<FilterCMC, Integer> pair : cmcLevels) {
Iterable<CardPrinted> matchingCards = Iterables.filter(source, Predicates.compose(pair.getLeft(), CardPrinted.FN_GET_RULES));
int cmcCountForPool = (int) Math.ceil(pair.getRight().intValue() * desiredOverTotal);
int addOfThisCmc = Math.round(pair.getRight().intValue() * requestedOverTotal);
tmpDeck.append(String.format("Adding %d cards for cmc range from a pool with %d cards:%n", addOfThisCmc, cmcCountForPool));
final List<CardPrinted> curved = Aggregates.random(matchingCards, cmcCountForPool);
final List<CardPrinted> curvedRandomized = Lists.newArrayList();
for (CardPrinted c : curved) { for (CardPrinted c : curved) {
this.cardCounts.put(c.getName(), 0); this.cardCounts.put(c.getName(), 0);
CardPrinted cpRandomSet = CardDb.instance().getCard(c.getName(), Aggregates.random(c.getRules().getSets()));
curvedRandomized.add(cpRandomSet);
} }
addSome(cnt, curved); addSome(addOfThisCmc, curvedRandomized);
}
} }
protected Iterable<CardPrinted> selectCardsOfMatchingColorForPlayer(PlayerType pt) { protected Iterable<CardPrinted> selectCardsOfMatchingColorForPlayer(PlayerType pt) {

View File

@@ -58,7 +58,7 @@ public class GenerateDeckUtil {
@Override @Override
public boolean apply(CardRules c) { public boolean apply(CardRules c) {
ManaCost mc = c.getManaCost(); ManaCost mc = c.getManaCost();
return mc.getColorProfile() == 0 && !mc.isNoCost(); return c.getColorIdentity().isColorless() && !mc.isNoCost();
} }
}; };
@@ -72,7 +72,8 @@ public class GenerateDeckUtil {
@Override @Override
public boolean apply(CardRules subject) { public boolean apply(CardRules subject) {
ManaCost mc = subject.getManaCost(); ManaCost mc = subject.getManaCost();
return !mc.isPureGeneric() && mc.canBePaidWithAvaliable(allowedColor); return !mc.isPureGeneric() && allowedColor.containsAllColorsFrom(subject.getColorIdentity().getColor());
//return mc.canBePaidWithAvaliable(allowedColor);
// return allowedColor.containsAllColorsFrom(mc.getColorProfile()); // return allowedColor.containsAllColorsFrom(mc.getColorProfile());
} }
} }

View File

@@ -17,9 +17,12 @@
*/ */
package forge.deck.generate; package forge.deck.generate;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.deck.generate.GenerateDeckUtil.FilterCMC; import forge.deck.generate.GenerateDeckUtil.FilterCMC;
@@ -40,12 +43,13 @@ public class GenerateMonoColorDeck extends GenerateColoredDeckBase {
@Override protected final float getCreatPercentage() { return 0.36f; } @Override protected final float getCreatPercentage() { return 0.36f; }
@Override protected final float getSpellPercentage() { return 0.25f; } @Override protected final float getSpellPercentage() { return 0.25f; }
final List<FilterCMC> cmcLevels = Arrays.asList( @SuppressWarnings("unchecked")
new GenerateDeckUtil.FilterCMC(0, 2), final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
new GenerateDeckUtil.FilterCMC(3, 4), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(0, 2), 10),
new GenerateDeckUtil.FilterCMC(5, 6), ImmutablePair.of(new GenerateDeckUtil.FilterCMC(3, 4), 8),
new GenerateDeckUtil.FilterCMC(7, 20)); ImmutablePair.of(new GenerateDeckUtil.FilterCMC(5, 6), 5),
final int[] cmcAmounts = {10, 8, 5, 3}; ImmutablePair.of(new GenerateDeckUtil.FilterCMC(7, 20), 3)
);
// mana curve of the card pool // mana curve of the card pool
// 20x 0 - 2 // 20x 0 - 2
@@ -76,7 +80,7 @@ public class GenerateMonoColorDeck extends GenerateColoredDeckBase {
public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) { public final ItemPoolView<CardPrinted> getDeck(final int size, final PlayerType pt) {
addCreaturesAndSpells(size, cmcLevels, cmcAmounts, pt); addCreaturesAndSpells(size, cmcLevels, pt);
// Add lands // Add lands
int numLands = (int) (getLandsPercentage() * size); int numLands = (int) (getLandsPercentage() * size);