deck generation moved to core

This commit is contained in:
Maxmtg
2013-11-21 20:59:18 +00:00
parent 2f85d50778
commit 482af12b67
24 changed files with 303 additions and 318 deletions

View File

@@ -0,0 +1,138 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Nate
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.common.base.Function;
/**
* TODO: Write javadoc for this type.
*
*/
public class DeckGroup extends DeckBase {
/**
* Instantiates a new deck group.
*
* @param name0 the name0
*/
public DeckGroup(final String name0) {
super(name0);
}
private static final long serialVersionUID = -1628725522049635829L;
private Deck humanDeck;
private List<Deck> aiDecks = new ArrayList<Deck>();
/**
* Gets the human deck.
*
* @return the human deck
*/
public final Deck getHumanDeck() {
return this.humanDeck;
}
/**
* Gets the ai decks.
*
* @return the ai decks
*/
public final List<Deck> getAiDecks() {
return this.aiDecks;
}
/**
* Sets the human deck.
*
* @param humanDeck the new human deck
*/
public final void setHumanDeck(final Deck humanDeck) {
this.humanDeck = humanDeck;
}
/**
* Evaluate and 'rank' the ai decks.
*
*
*/
public final void rankAiDecks(Comparator<Deck> comparator) {
if (this.aiDecks.size() < 2) {
return;
}
Collections.sort(aiDecks, comparator);
}
@Override
protected void cloneFieldsTo(final DeckBase clone) {
super.cloneFieldsTo(clone);
DeckGroup myClone = (DeckGroup) clone;
myClone.setHumanDeck((Deck) this.getHumanDeck().copyTo(this.getHumanDeck().getName()));
for (int i = 0; i < this.getAiDecks().size(); i++) {
Deck src = this.getAiDecks().get(i);
myClone.addAiDeck((Deck) src.copyTo(src.getName()));
}
}
/**
* Adds the ai deck.
*
* @param aiDeck the ai deck
*/
public final void addAiDeck(final Deck aiDeck) {
if (aiDeck == null) {
return;
}
this.aiDecks.add(aiDeck);
}
/**
* Adds the ai decks.
*
* @param computer the computer
*/
public void addAiDecks(final Deck[] computer) {
for (final Deck element : computer) {
this.aiDecks.add(element);
}
}
/*
* (non-Javadoc)
*
* @see forge.deck.DeckBase#newInstance(java.lang.String)
*/
@Override
protected DeckBase newInstance(final String name0) {
return new DeckGroup(name0);
}
public static final Function<DeckGroup, String> FN_NAME_SELECTOR = new Function<DeckGroup, String>() {
@Override
public String apply(DeckGroup arg1) {
return arg1.getName();
}
};
}

View File

@@ -0,0 +1,286 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import forge.card.CardDb;
import forge.card.ICardDatabase;
import forge.item.PaperCard;
/**
* <p>
* DeckRecognizer class.
* </p>
*
* @author Forge
* @version $Id: DeckRecognizer.java 10499 2011-09-17 15:08:47Z Max mtg $
*
*/
public class DeckRecognizer {
/**
* The Enum TokenType.
*/
public enum TokenType {
/** The Known card. */
KnownCard,
/** The Unknown card. */
UnknownCard,
/** The Section name. */
SectionName,
/** The Comment. */
Comment,
/** The Unknown text. */
UnknownText
}
/**
* The Class Token.
*/
public static class Token {
private final TokenType type;
private final PaperCard card;
private final int number;
private final String text;
/**
* Known card.
*
* @param theCard
* the the card
* @param count
* the count
* @return the token
*/
public static Token knownCard(final PaperCard theCard, final int count) {
return new Token(theCard, TokenType.KnownCard, count, null);
}
/**
* Unknown card.
*
* @param cardNme
* the card nme
* @param count
* the count
* @return the token
*/
public static Token unknownCard(final String cardNme, final int count) {
return new Token(null, TokenType.UnknownCard, count, cardNme);
}
private Token(final PaperCard knownCard, final TokenType type1, final int count, final String message) {
this.card = knownCard;
this.number = count;
this.type = type1;
this.text = message;
}
/**
* Instantiates a new token.
*
* @param type1
* the type1
* @param count
* the count
* @param message
* the message
*/
public Token(final TokenType type1, final int count, final String message) {
this(null, type1, count, message);
if ((type1 == TokenType.KnownCard) || (type1 == TokenType.UnknownCard)) {
throw new IllegalArgumentException("Use factory methods for recognized card lines");
}
}
/**
* Gets the text.
*
* @return the text
*/
public final String getText() {
return this.text;
}
/**
* Gets the card.
*
* @return the card
*/
public final PaperCard getCard() {
return this.card;
}
/**
* Gets the type.
*
* @return the type
*/
public final TokenType getType() {
return this.type;
}
/**
* Gets the number.
*
* @return the number
*/
public final int getNumber() {
return this.number;
}
}
// Let's think about it numbers in the back later
// private static final Pattern searchNumbersInBack =
// Pattern.compile("(.*)[^A-Za-wyz]*\\s+([\\d]{1,2})");
private static final Pattern SEARCH_NUMBERS_IN_FRONT = Pattern.compile("([\\d]{1,2})[^A-Za-wyz]*\\s+(.*)");
//private static final Pattern READ_SEPARATED_EDITION = Pattern.compile("[[\\(\\{]([a-zA-Z0-9]){1,3})[]*\\s+(.*)");
private final boolean useLastSet;
private final ICardDatabase db;
private Date recognizeCardsPrintedBefore = null;
public DeckRecognizer(boolean fromLatestSet, CardDb db) {
useLastSet = fromLatestSet;
this.db = db;
}
public Token recognizeLine(final String rawLine) {
if (StringUtils.isBlank(rawLine)) {
return new Token(TokenType.Comment, 0, rawLine);
}
final String line = rawLine.trim();
Token result = null;
final Matcher foundNumbersInFront = DeckRecognizer.SEARCH_NUMBERS_IN_FRONT.matcher(line);
// Matcher foundNumbersInBack = searchNumbersInBack.matcher(line);
if (foundNumbersInFront.matches()) {
final String cardName = foundNumbersInFront.group(2);
final int amount = Integer.parseInt(foundNumbersInFront.group(1));
result = recognizePossibleNameAndNumber(cardName, amount);
} /*
* else if (foundNumbersInBack.matches()) { String cardName =
* foundNumbersInBack.group(1); int amount =
* Integer.parseInt(foundNumbersInBack.group(2)); return new
* Token(cardName, amount); }
*/
else {
PaperCard pc = tryGetCard(line);
if (null != pc) {
return Token.knownCard(pc, 1);
}
result = DeckRecognizer.recognizeNonCard(line, 1);
}
return result != null ? result : new Token(TokenType.UnknownText, 0, line);
}
private PaperCard tryGetCard(String text) {
if(recognizeCardsPrintedBefore != null )
return db.tryGetCardPrintedByDate(text, useLastSet, recognizeCardsPrintedBefore);
return db.tryGetCard(text, useLastSet);
}
private Token recognizePossibleNameAndNumber(final String name, final int n) {
PaperCard pc = tryGetCard(name);
if (null != pc) {
return Token.knownCard(pc, n);
}
// TODO: recognize format: http://topdeck.ru/forum/index.php?showtopic=12711
//final Matcher foundEditionName = READ_SEPARATED_EDITION.matcher(name);
final Token known = DeckRecognizer.recognizeNonCard(name, n);
return null == known ? Token.unknownCard(name, n) : known;
}
private static Token recognizeNonCard(final String text, final int n) {
if (DeckRecognizer.isDecoration(text)) {
return new Token(TokenType.Comment, n, text);
}
if (DeckRecognizer.isSectionName(text)) {
return new Token(TokenType.SectionName, n, text);
}
return null;
}
private static final String[] KNOWN_COMMENTS = new String[] { "land", "lands", "creatures", "creature", "spells",
"enchancements", "other spells", "artifacts" };
private static final String[] KNOWN_COMMENT_PARTS = new String[] { "card" };
private static boolean isDecoration(final String lineAsIs) {
final String line = lineAsIs.toLowerCase();
for (final String s : DeckRecognizer.KNOWN_COMMENT_PARTS) {
if (line.contains(s)) {
return true;
}
}
for (final String s : DeckRecognizer.KNOWN_COMMENTS) {
if (line.equalsIgnoreCase(s)) {
return true;
}
}
return false;
}
private static boolean isSectionName(final String line) {
if (line.toLowerCase().contains("side")) {
return true;
}
if (line.toLowerCase().contains("main")) {
return true;
}
if (line.toLowerCase().contains("commander")) {
return true;
}
if (line.toLowerCase().contains("planes")) {
return true;
}
if (line.toLowerCase().contains("schemes")) {
return true;
}
if (line.toLowerCase().contains("vanguard")) {
return true;
}
return false;
}
/**
* TODO: Write javadoc for this method.
* @param month
* @param year
*/
public void setDateConstraint(int month, Integer year) {
Calendar ca = Calendar.getInstance();
ca.set(year, month, 1);
recognizeCardsPrintedBefore = ca.getTime();
}
}

View File

@@ -0,0 +1,115 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck.generation;
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.ICardDatabase;
import forge.card.MagicColor;
import forge.item.PaperCard;
import forge.util.ItemPoolView;
/**
* <p>
* Generate2ColorDeck class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class DeckGenerator2Color extends DeckGeneratorBase {
@Override protected final float getLandsPercentage() { return 0.39f; }
@Override protected final float getCreatPercentage() { return 0.36f; }
@Override protected final float getSpellPercentage() { return 0.25f; }
@SuppressWarnings("unchecked")
final List<ImmutablePair<FilterCMC, Integer>> cmcRelativeWeights = Lists.newArrayList(
ImmutablePair.of(new FilterCMC(0, 2), 6),
ImmutablePair.of(new FilterCMC(3, 4), 4),
ImmutablePair.of(new FilterCMC(5, 6), 2),
ImmutablePair.of(new FilterCMC(7, 20), 1)
);
// mana curve of the card pool
// 20x 0 - 2
// 16x 3 - 4
// 12x 5 - 6
// 4x 7 - 20
// = 52x - card pool (before further random filtering)
/**
* <p>
* Constructor for Generate2ColorDeck.
* </p>
*
* @param clr1
* a {@link java.lang.String} object.
* @param clr2
* a {@link java.lang.String} object.
*/
public DeckGenerator2Color(ICardDatabase cardDb, final String clr1, final String clr2) {
super(cardDb);
int c1 = MagicColor.fromName(clr1);
int c2 = MagicColor.fromName(clr2);
if( c1 == 0 && c2 == 0) {
int color1 = r.nextInt(5);
int color2 = (color1 + 1 + r.nextInt(4)) % 5;
colors = ColorSet.fromMask(MagicColor.WHITE << color1 | MagicColor.WHITE << color2);
} else if ( c1 == 0 || c2 == 0 ) {
byte knownColor = (byte) (c1 | c2);
int color1 = Arrays.binarySearch(MagicColor.WUBRG, knownColor);
int color2 = (color1 + 1 + r.nextInt(4)) % 5;
colors = ColorSet.fromMask(MagicColor.WHITE << color1 | MagicColor.WHITE << color2);
} else {
colors = ColorSet.fromMask(c1 | c2);
}
}
@Override
public final ItemPoolView<PaperCard> getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcRelativeWeights, forAi);
// Add lands
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 = getDualLandList();
for (String s : duals) {
this.cardCounts.put(s, 0);
}
int dblsAdded = addSomeStr((numLands / 6), duals);
numLands -= dblsAdded;
addBasicLand(numLands);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
//System.out.println(tmpDeck.toString());
return tDeck;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck.generation;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet;
import forge.card.ICardDatabase;
import forge.card.MagicColor;
import forge.item.PaperCard;
import forge.util.ItemPoolView;
import forge.util.MyRandom;
/**
* <p>
* Generate3ColorDeck class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class DeckGenerator3Color extends DeckGeneratorBase {
@SuppressWarnings("unchecked")
final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
ImmutablePair.of(new FilterCMC(0, 2), 12),
ImmutablePair.of(new FilterCMC(3, 5), 9),
ImmutablePair.of(new FilterCMC(6, 20), 3)
);
/**
* <p>
* Constructor for Generate3ColorDeck.
* </p>
*
* @param clr1
* a {@link java.lang.String} object.
* @param clr2
* a {@link java.lang.String} object.
* @param clr3
* a {@link java.lang.String} object.
*/
public DeckGenerator3Color(ICardDatabase cardDb, final String clr1, final String clr2, final String clr3) {
super(cardDb);
int c1 = MagicColor.fromName(clr1);
int c2 = MagicColor.fromName(clr2);
int c3 = MagicColor.fromName(clr3);
int rc = 0;
int combo = c1 | c2 | c3;
ColorSet param = ColorSet.fromMask(combo);
switch(param.countColors()) {
case 3:
colors = param;
return;
case 0:
int color1 = r.nextInt(5);
int color2 = (color1 + 1 + r.nextInt(4)) % 5;
colors = ColorSet.fromMask(MagicColor.WHITE << color1 | MagicColor.WHITE << color2).inverse();
return;
case 1:
do {
rc = MagicColor.WHITE << MyRandom.getRandom().nextInt(5);
} while ( rc == combo );
combo |= rc;
// fall-through
case 2:
do {
rc = MagicColor.WHITE << MyRandom.getRandom().nextInt(5);
} while ( (rc & combo) != 0 );
combo |= rc;
break;
}
colors = ColorSet.fromMask(combo);
}
@Override
public final ItemPoolView<PaperCard> getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcLevels, forAi);
// Add lands
int numLands = Math.round(size * getLandsPercentage());
adjustDeckSize(size - numLands);
tmpDeck.append("numLands:").append(numLands).append("\n");
// Add dual lands
List<String> duals = getDualLandList();
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");
return tDeck;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck.generation;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet;
import forge.card.ICardDatabase;
import forge.item.PaperCard;
import forge.util.ItemPoolView;
/**
* <p>
* Generate5ColorDeck class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class DeckGenerator5Color extends DeckGeneratorBase {
@SuppressWarnings("unchecked")
final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
ImmutablePair.of(new FilterCMC(0, 2), 3),
ImmutablePair.of(new FilterCMC(3, 5), 2),
ImmutablePair.of(new FilterCMC(6, 20), 1)
);
// resulting mana curve of the card pool
// 30x 0 - 2
// 20x 3 - 5
// 10x 6 - 20
// =60x - card pool
/**
* Instantiates a new generate5 color deck.
*/
public DeckGenerator5Color(ICardDatabase cardDb) {
super(cardDb);
colors = ColorSet.fromMask(0).inverse();
}
@Override
public final ItemPoolView<PaperCard> getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcLevels, forAi);
// Add lands
int numLands = Math.round(size * getLandsPercentage());
adjustDeckSize(size - numLands);
tmpDeck.append("numLands:").append(numLands).append("\n");
// Add dual lands
List<String> duals = getDualLandList();
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");
return tDeck;
}
}

View File

@@ -0,0 +1,405 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck.generation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.card.ColorSet;
import forge.card.ICardDatabase;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.ItemPool;
import forge.util.ItemPoolView;
import forge.util.MyRandom;
/**
* <p>
* Generate2ColorDeck class.
* </p>
*
* @author Forge
* @version $Id: Generate2ColorDeck.java 14959 2012-03-28 14:03:43Z Chris H. $
*/
public abstract class DeckGeneratorBase {
protected final Random r = MyRandom.getRandom();
protected final Map<String, Integer> cardCounts = new HashMap<String, Integer>();
protected int maxDuplicates = 4;
protected boolean useArtifacts = true;
protected ColorSet colors;
protected final ItemPool<PaperCard> tDeck = new ItemPool<PaperCard>(PaperCard.class);
protected final ICardDatabase cardDb;
// 2-colored deck generator has its own constants. The rest works fine with these ones
protected float getLandsPercentage() { return 0.44f; }
protected float getCreatPercentage() { return 0.34f; }
protected float getSpellPercentage() { return 0.22f; }
StringBuilder tmpDeck = new StringBuilder();
public DeckGeneratorBase(ICardDatabase cardDb) {
this.cardDb = cardDb;
}
public void setSingleton(boolean singleton){
this.maxDuplicates = singleton ? 1 : 4;
}
public void setUseArtifacts(boolean value) {
this.useArtifacts = value;
}
protected void addCreaturesAndSpells(int size, List<ImmutablePair<FilterCMC, Integer>> cmcLevels, boolean forAi) {
tmpDeck.append("Building deck of ").append(size).append("cards\n");
final Iterable<PaperCard> cards = selectCardsOfMatchingColorForPlayer(forAi);
// build subsets based on type
final Iterable<PaperCard> creatures = Iterables.filter(cards, Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES));
final int creatCnt = (int) Math.ceil(getCreatPercentage() * size);
tmpDeck.append("Creatures to add:").append(creatCnt).append("\n");
addCmcAdjusted(creatures, creatCnt, cmcLevels);
Predicate<PaperCard> preSpells = Predicates.compose(CardRulesPredicates.Presets.IS_NONCREATURE_SPELL_FOR_GENERATOR, PaperCard.FN_GET_RULES);
final Iterable<PaperCard> spells = Iterables.filter(cards, preSpells);
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<PaperCard> getDeck(final int size, final boolean forAi) {
return null; // all but theme deck do override this method
}
protected void addSome(int cnt, List<PaperCard> source) {
for (int i = 0; i < cnt; i++) {
PaperCard cp;
int lc = 0;
int srcLen = source.size();
do {
cp = source.get(this.r.nextInt(srcLen));
lc++;
} 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(cp);
final int n = this.cardCounts.get(cp.getName());
this.cardCounts.put(cp.getName(), n + 1);
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()));
}
}
protected int addSomeStr(int cnt, List<String> source) {
int res = 0;
for (int i = 0; i < cnt; i++) {
String s;
int lc = 0;
do {
s = source.get(this.r.nextInt(source.size()));
lc++;
} while ((this.cardCounts.get(s) > 3) && (lc <= 20));
// not an error if looped too much - could play singleton mode, with 6 slots for 3 non-basic lands.
tDeck.add(cardDb.getCard(s, false));
final int n = this.cardCounts.get(s);
this.cardCounts.put(s, n + 1);
tmpDeck.append(s + "\n");
res++;
}
return res;
}
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
float totalColor = 0;
for (Entry<String, Integer> c : clrCnts.entrySet()) {
totalColor += c.getValue();
tmpDeck.append(c.getKey()).append(":").append(c.getValue()).append("\n");
}
tmpDeck.append("totalColor:").append(totalColor).append("\n");
int landsLeft = cnt;
for (Entry<String, Integer> c : clrCnts.entrySet()) {
String basicLandName = c.getKey();
// calculate number of lands for each color
final int nLand = Math.min(landsLeft, Math.round(cnt * c.getValue() / totalColor));
tmpDeck.append("nLand-").append(basicLandName).append(":").append(nLand).append("\n");
// just to prevent a null exception by the deck size fixing code
this.cardCounts.put(basicLandName, nLand);
PaperCard cp = cardDb.getCard(basicLandName);
String basicLandSet = cp.getEdition();
tDeck.add(cardDb.getCard(cp.getName(), basicLandSet), nLand);
landsLeft -= nLand;
}
}
protected void adjustDeckSize(int targetSize) {
// fix under-sized or over-sized decks, due to integer arithmetic
int actualSize = tDeck.countAll();
if (actualSize < targetSize) {
final int diff = targetSize - actualSize;
addSome(diff, tDeck.toFlatList());
} else if (actualSize > targetSize) {
Predicate<PaperCard> exceptBasicLand = Predicates.not(Predicates.compose(CardRulesPredicates.Presets.IS_BASIC_LAND, PaperCard.FN_GET_RULES));
for (int i = 0; i < 3 && actualSize > targetSize; i++) {
Iterable<PaperCard> matchingCards = Iterables.filter(tDeck.toFlatList(), exceptBasicLand);
List<PaperCard> toRemove = Aggregates.random(matchingCards, actualSize - targetSize);
tDeck.removeAllFlat(toRemove);
for (PaperCard c : toRemove) {
tmpDeck.append("Removed:").append(c.getName()).append("\n");
}
actualSize = tDeck.countAll();
}
}
}
protected void addCmcAdjusted(Iterable<PaperCard> 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<PaperCard> matchingCards = Iterables.filter(source, Predicates.compose(pair.getLeft(), PaperCard.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<PaperCard> curved = Aggregates.random(matchingCards, cmcCountForPool);
final List<PaperCard> curvedRandomized = Lists.newArrayList();
for (PaperCard c : curved) {
this.cardCounts.put(c.getName(), 0);
curvedRandomized.add(cardDb.getCard(c.getName(), false));
}
addSome(addOfThisCmc, curvedRandomized);
}
}
protected Iterable<PaperCard> selectCardsOfMatchingColorForPlayer(boolean forAi) {
// start with all cards
// remove cards that generated decks don't like
Predicate<CardRules> canPlay = forAi ? AI_CAN_PLAY : HUMAN_CAN_PLAY;
Predicate<CardRules> hasColor = new MatchColorIdentity(colors);
if (useArtifacts) {
hasColor = Predicates.or(hasColor, COLORLESS_CARDS);
}
return Iterables.filter(cardDb.getAllCards(), Predicates.compose(Predicates.and(canPlay, hasColor), PaperCard.FN_GET_RULES));
}
protected static Map<String, Integer> countLands(ItemPool<PaperCard> outList) {
// attempt to optimize basic land counts according
// to color representation
Map<String, Integer> res = new TreeMap<String, Integer>();
// count each card color using mana costs
// TODO: count hybrid mana differently?
for (Entry<PaperCard, Integer> cpe : outList) {
int profile = cpe.getKey().getRules().getManaCost().getColorProfile();
if ((profile & MagicColor.WHITE) != 0) {
increment(res, MagicColor.Constant.BASIC_LANDS.get(0), cpe.getValue());
} else if ((profile & MagicColor.BLUE) != 0) {
increment(res, MagicColor.Constant.BASIC_LANDS.get(1), cpe.getValue());
} else if ((profile & MagicColor.BLACK) != 0) {
increment(res, MagicColor.Constant.BASIC_LANDS.get(2), cpe.getValue());
} else if ((profile & MagicColor.RED) != 0) {
increment(res, MagicColor.Constant.BASIC_LANDS.get(3), cpe.getValue());
} else if ((profile & MagicColor.GREEN) != 0) {
increment(res, MagicColor.Constant.BASIC_LANDS.get(4), cpe.getValue());
}
}
return res;
}
protected static void increment(Map<String, Integer> map, String key, int delta)
{
final Integer boxed = map.get(key);
map.put(key, boxed == null ? delta : boxed.intValue() + delta);
}
public static final Predicate<CardRules> AI_CAN_PLAY = new Predicate<CardRules>() {
@Override
public boolean apply(CardRules c) {
return !c.getAiHints().getRemAIDecks() && !c.getAiHints().getRemRandomDecks();
}
};
public static final Predicate<CardRules> HUMAN_CAN_PLAY = new Predicate<CardRules>() {
@Override
public boolean apply(CardRules c) {
return !c.getAiHints().getRemRandomDecks();
}
};
public static final Predicate<CardRules> COLORLESS_CARDS = new Predicate<CardRules>() {
@Override
public boolean apply(CardRules c) {
ManaCost mc = c.getManaCost();
return c.getColorIdentity().isColorless() && !mc.isNoCost();
}
};
public static class MatchColorIdentity implements Predicate<CardRules> {
private final ColorSet allowedColor;
public MatchColorIdentity(ColorSet color) {
allowedColor = color;
}
@Override
public boolean apply(CardRules subject) {
ManaCost mc = subject.getManaCost();
return !mc.isPureGeneric() && allowedColor.containsAllColorsFrom(subject.getColorIdentity().getColor());
//return mc.canBePaidWithAvaliable(allowedColor);
// return allowedColor.containsAllColorsFrom(mc.getColorProfile());
}
}
public static class FilterCMC implements Predicate<CardRules> {
private final int min;
private final int max;
public FilterCMC(int from, int to) {
min = from;
max = to;
}
@Override
public boolean apply(CardRules c) {
ManaCost mc = c.getManaCost();
int cmc = mc.getCMC();
return cmc >= min && cmc <= max && !mc.isNoCost();
}
}
private static Map<Integer, String[]> dualLands = new HashMap<Integer, String[]>();
static {
dualLands.put(MagicColor.WHITE | MagicColor.BLUE, new String[] { "Tundra", "Hallowed Fountain", "Flooded Strand" });
dualLands.put(MagicColor.BLACK | MagicColor.BLUE, new String[] { "Underground Sea", "Watery Grave", "Polluted Delta" });
dualLands.put(MagicColor.BLACK | MagicColor.RED, new String[] { "Badlands", "Blood Crypt", "Bloodstained Mire" });
dualLands.put(MagicColor.GREEN | MagicColor.RED, new String[] { "Taiga", "Stomping Ground", "Wooded Foothills" });
dualLands.put(MagicColor.GREEN | MagicColor.WHITE, new String[] { "Savannah", "Temple Garden", "Windswept Heath" });
dualLands.put(MagicColor.WHITE | MagicColor.BLACK, new String[] { "Scrubland", "Godless Shrine", "Marsh Flats" });
dualLands.put(MagicColor.BLUE | MagicColor.RED, new String[] { "Volcanic Island", "Steam Vents", "Scalding Tarn" });
dualLands.put(MagicColor.BLACK | MagicColor.GREEN, new String[] { "Bayou", "Overgrown Tomb", "Verdant Catacombs" });
dualLands.put(MagicColor.WHITE | MagicColor.RED, new String[] { "Plateau", "Sacred Foundry", "Arid Mesa" });
dualLands.put(MagicColor.GREEN | MagicColor.BLUE, new String[] { "Tropical Island", "Breeding Pool", "Misty Rainforest" });
}
/**
* Get list of dual lands for this color combo.
*
* @param color
* the color
* @return dual land names
*/
protected List<String> getDualLandList() {
final List<String> dLands = new ArrayList<String>();
if (colors.countColors() > 3) {
dLands.add("Rupture Spire");
dLands.add("Undiscovered Paradise");
}
if (colors.countColors() > 2) {
dLands.add("Evolving Wilds");
dLands.add("Terramorphic Expanse");
}
for (Entry<Integer, String[]> dual : dualLands.entrySet()) {
if (colors.hasAllColors(dual.getKey())) {
for (String s : dual.getValue()) {
dLands.add(s);
}
}
}
return dLands;
}
/**
* Get all dual lands that do not match this color combo.
*
* @param color
* the color
* @return dual land names
*/
protected List<String> getInverseDualLandList() {
final List<String> dLands = new ArrayList<String>();
for (Entry<Integer, String[]> dual : dualLands.entrySet()) {
if (!colors.hasAllColors(dual.getKey())) {
for (String s : dual.getValue()) {
dLands.add(s);
}
}
}
return dLands;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.deck.generation;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.card.ColorSet;
import forge.card.ICardDatabase;
import forge.card.MagicColor;
import forge.item.PaperCard;
import forge.util.ItemPoolView;
/**
* <p>
* Generate2ColorDeck class.
* </p>
*
* @author Forge
* @version $Id: Generate2ColorDeck.java 19765 2013-02-20 03:01:37Z myk $
*/
public class DeckGeneratorMonoColor extends DeckGeneratorBase {
@Override protected final float getLandsPercentage() { return 0.39f; }
@Override protected final float getCreatPercentage() { return 0.36f; }
@Override protected final float getSpellPercentage() { return 0.25f; }
@SuppressWarnings("unchecked")
final List<ImmutablePair<FilterCMC, Integer>> cmcLevels = Lists.newArrayList(
ImmutablePair.of(new FilterCMC(0, 2), 10),
ImmutablePair.of(new FilterCMC(3, 4), 8),
ImmutablePair.of(new FilterCMC(5, 6), 5),
ImmutablePair.of(new FilterCMC(7, 20), 3)
);
// mana curve of the card pool
// 20x 0 - 2
// 16x 3 - 4
// 12x 5 - 6
// 4x 7 - 20
// = 52x - card pool (before further random filtering)
/**
* <p>
* Constructor for Generate2ColorDeck.
* </p>
*
* @param clr1
* a {@link java.lang.String} object.
* @param clr2
* a {@link java.lang.String} object.
*/
public DeckGeneratorMonoColor(ICardDatabase cardDb, final String clr1) {
super(cardDb);
if (MagicColor.fromName(clr1) == 0) {
int color1 = r.nextInt(5);
colors = ColorSet.fromMask(MagicColor.WHITE << color1);
} else {
colors = ColorSet.fromNames(clr1);
}
}
@Override
public final ItemPoolView<PaperCard> getDeck(final int size, final boolean forAi) {
addCreaturesAndSpells(size, cmcLevels, forAi);
// Add lands
int numLands = (int) (getLandsPercentage() * size);
tmpDeck.append("numLands:").append(numLands).append("\n");
addBasicLand(numLands);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
adjustDeckSize(size);
tmpDeck.append("DeckSize:").append(tDeck.countAll()).append("\n");
return tDeck;
}
}

View File

@@ -0,0 +1,3 @@
/** Forge Card Game. */
package forge.deck.generation;