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 forge.card.MagicColor;
/**
* <p>
* Constant interface.

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,6 @@
*/
package forge.deck.generate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -25,9 +24,12 @@ import java.util.Map.Entry;
import java.util.Random;
import java.util.TreeMap;
import org.apache.commons.lang3.tuple.ImmutablePair;
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.Constant;
import forge.Singletons;
@@ -73,20 +75,24 @@ public abstract class GenerateColoredDeckBase {
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);
// build subsets based on type
final Iterable<CardPrinted> creatures = Iterables.filter(cards, Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, CardPrinted.FN_GET_RULES));
final int creatCnt = (int) (getCreatPercentage() * size);
tmpDeck.append("Creature Count:").append(creatCnt).append("\n");
addCmcAdjusted(creatures, creatCnt, cmcLevels, cmcAmounts);
final int creatCnt = (int) Math.ceil(getCreatPercentage() * size);
tmpDeck.append("Creatures to add:").append(creatCnt).append("\n");
addCmcAdjusted(creatures, creatCnt, cmcLevels);
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 int spellCnt = (int) (getSpellPercentage() * size);
tmpDeck.append("Spell Count:").append(spellCnt).append("\n");
addCmcAdjusted(spells, spellCnt, cmcLevels, cmcAmounts);
final int spellCnt = (int) Math.ceil(getSpellPercentage() * size);
tmpDeck.append("Spells to add:").append(spellCnt).append("\n");
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) {
@@ -97,19 +103,22 @@ public abstract class GenerateColoredDeckBase {
for (int i = 0; i < cnt; i++) {
CardPrinted cp;
int lc = 0;
int srcLen = source.size();
do {
cp = source.get(this.r.nextInt(source.size()));
cp = source.get(this.r.nextInt(srcLen));
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) {
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());
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) {
tmpDeck.append(cnt).append(" basic lands remain").append("\n");
// attempt to optimize basic land counts according to colors of picked cards
final Map<String, Integer> clrCnts = countLands(tDeck);
// total of all ClrCnts
@@ -147,24 +158,23 @@ public abstract class GenerateColoredDeckBase {
tmpDeck.append("totalColor:").append(totalColor).append("\n");
int landsLeft = cnt;
for (Entry<String, Integer> c : clrCnts.entrySet()) {
String color = c.getKey();
// calculate number of lands for each color
float p = (float) c.getValue() / totalColor;
final int nLand = (int) (cnt * p);
final int nLand = Math.min(landsLeft, Math.round(cnt * c.getValue() / totalColor));
tmpDeck.append("nLand-").append(color).append(":").append(nLand).append("\n");
// just to prevent a null exception by the deck size fixing
// code
// just to prevent a null exception by the deck size fixing code
this.cardCounts.put(color, nLand);
CardPrinted cp = CardDb.instance().getCard(color);
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) {
final List<CardPrinted> curved = new ArrayList<CardPrinted>();
for (int i = 0; i < cmcAmounts.length; i++) {
Iterable<CardPrinted> matchingCards = Iterables.filter(source, Predicates.compose(cmcLevels.get(i), CardPrinted.FN_GET_RULES));
curved.addAll(Aggregates.random(matchingCards, cmcAmounts[i]));
protected void addCmcAdjusted(Iterable<CardPrinted> source, int cnt, List<ImmutablePair<FilterCMC, Integer>> cmcLevels) {
int totalWeight = 0;
for (ImmutablePair<FilterCMC, Integer> pair : cmcLevels) {
totalWeight += pair.getRight();
}
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) {
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) {

View File

@@ -58,7 +58,7 @@ public class GenerateDeckUtil {
@Override
public boolean apply(CardRules c) {
ManaCost mc = c.getManaCost();
return mc.getColorProfile() == 0 && !mc.isNoCost();
return c.getColorIdentity().isColorless() && !mc.isNoCost();
}
};
@@ -72,7 +72,8 @@ public class GenerateDeckUtil {
@Override
public boolean apply(CardRules subject) {
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());
}
}

View File

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