Merge branch 'ldaarchetypedeckgenerationchanges' into 'master'

Archetype based deck generation

See merge request core-developers/forge!625
This commit is contained in:
Michael Kamensky
2018-06-08 07:59:36 +00:00
18 changed files with 277 additions and 65 deletions

View File

@@ -164,7 +164,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
private void updateMatrix(GameFormat format) {
lstDecks.setAllowMultipleSelections(false);
lstDecks.setPool(CardThemedDeckGenerator.getMatrixDecks(format, isAi));
lstDecks.setPool(ArchetypeDeckGenerator.getMatrixDecks(format, isAi));
lstDecks.setup(ItemManagerConfig.STRING_ONLY);
btnRandom.setText("Random");

View File

@@ -10,6 +10,7 @@ import com.badlogic.gdx.math.Rectangle;
import forge.Forge;
import forge.Graphics;
import forge.assets.FSkinImage;
import forge.deck.ArchetypeDeckGenerator;
import forge.deck.CardThemedDeckGenerator;
import forge.deck.CommanderDeckGenerator;
import forge.deck.DeckProxy;
@@ -131,6 +132,8 @@ public class CardZoom extends FOverlay {
return CardView.getCardForUi(((CardThemedDeckGenerator)item).getPaperCard());
}else if (item instanceof CommanderDeckGenerator){
return CardView.getCardForUi(((CommanderDeckGenerator)item).getPaperCard());
}else if (item instanceof ArchetypeDeckGenerator){
return CardView.getCardForUi(((ArchetypeDeckGenerator)item).getPaperCard());
}else{
DeckProxy deck = ((DeckProxy)item);
return new CardView(-1, null, deck.getName(), null, deck.getImageKey(false));

View File

@@ -654,7 +654,7 @@ public class FDeckChooser extends FScreen {
maxSelections = 1;
pool= new ArrayList<>();
if(FModel.isdeckGenMatrixLoaded()) {
pool = CardThemedDeckGenerator.getMatrixDecks(FModel.getFormats().getStandard(), isAi);
pool = ArchetypeDeckGenerator.getMatrixDecks(FModel.getFormats().getStandard(), isAi);
}
config = ItemManagerConfig.STRING_ONLY;
break;
@@ -662,7 +662,7 @@ public class FDeckChooser extends FScreen {
maxSelections = 1;
pool= new ArrayList<>();
if(FModel.isdeckGenMatrixLoaded()) {
pool = CardThemedDeckGenerator.getMatrixDecks(FModel.getFormats().getModern(), isAi);
pool = ArchetypeDeckGenerator.getMatrixDecks(FModel.getFormats().getModern(), isAi);
}
config = ItemManagerConfig.STRING_ONLY;
break;

View File

@@ -11,10 +11,7 @@ import forge.assets.ImageCache;
import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom;
import forge.deck.CardThemedDeckGenerator;
import forge.deck.CommanderDeckGenerator;
import forge.deck.DeckProxy;
import forge.deck.FDeckViewer;
import forge.deck.*;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.itemmanager.ColumnDef;
@@ -864,7 +861,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
public boolean longPress(float x, float y) {
ItemInfo item = getItemAtPoint(x + getLeft(), y + getTop());
if (item != null) {
if(item.getKey() instanceof CardThemedDeckGenerator || item.getKey() instanceof CommanderDeckGenerator){
if(item.getKey() instanceof CardThemedDeckGenerator || item.getKey() instanceof CommanderDeckGenerator
|| item.getKey() instanceof ArchetypeDeckGenerator){
FDeckViewer.show(((DeckProxy)item.getKey()).getDeck());
return true;
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
package forge.deck;
import forge.card.CardEdition;
import forge.deck.io.Archetype;
import forge.game.GameFormat;
import forge.item.PaperCard;
import forge.model.FModel;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.List;
/**
* Created by maustin on 09/05/2017.
*/
public class ArchetypeDeckGenerator extends DeckProxy implements Comparable<ArchetypeDeckGenerator> {
public static List<DeckProxy> getMatrixDecks(GameFormat format, boolean isForAi){
final List<DeckProxy> decks = new ArrayList<DeckProxy>();
for(Archetype archetype: CardArchetypeLDAGenerator.ldaArchetypes.get(format.getName())) {
decks.add(new ArchetypeDeckGenerator(archetype, format, isForAi));
}
return decks;
}
private final Archetype archetype;
private final int index;
private final GameFormat format;
private final boolean isForAi;
private PaperCard card;
private ArchetypeDeckGenerator(Archetype archetype0, GameFormat format0, boolean isForAi0) {
super();
archetype = archetype0;
index = 0;
format=format0;
isForAi=isForAi0;
for(Pair<String, Double> cardPair : archetype.getCardProbabilities()){
PaperCard candidate = FModel.getMagicDb().getCommonCards().getUniqueByName(cardPair.getLeft());
if(!candidate.getRules().getType().isLand()){
card = candidate;
break;
}
}
}
public CardEdition getEdition() {
return CardEdition.UNKNOWN;
}
@Override
public String getName() {
return archetype.getName();
}
@Override
public String toString() {
return archetype.getName();
}
public Archetype getArchetype() {
return archetype;
}
@Override
public int compareTo(final ArchetypeDeckGenerator d) {
return d.getArchetype().getDeckCount().compareTo(archetype.getDeckCount());
}
@Override
public Deck getDeck() {
return DeckgenUtil.buildLDACArchetypeDeck(archetype,format,isForAi);
}
@Override
public boolean isGeneratedDeck() {
return true;
}
public String getImageKey(boolean altState) {
/* Predicate<PaperCard> cardFilter = Predicates.and(format.getFilterPrinted(),PaperCard.Predicates.name(name));
List<PaperCard> cards=FModel.getMagicDb().getCommonCards().getAllCards(cardFilter);
return cards.get(cards.size()-1).getImageKey(altState);*/
return card.getImageKey(altState);
}
public PaperCard getPaperCard(){
return card;
}
}

View File

@@ -1,6 +1,7 @@
package forge.deck;
import forge.StaticData;
import forge.deck.io.Archetype;
import forge.deck.io.CardThemedLDAIO;
import forge.game.GameFormat;
import forge.model.FModel;
@@ -15,6 +16,7 @@ import java.util.*;
public final class CardArchetypeLDAGenerator {
public static Map<String, Map<String,List<List<Pair<String, Double>>>>> ldaPools = new HashMap();
public static Map<String, List<Archetype>> ldaArchetypes = new HashMap<>();
public static boolean initialize(){
@@ -33,10 +35,10 @@ public final class CardArchetypeLDAGenerator {
/** Try to load matrix .dat files, otherwise check for deck folders and build .dat, otherwise return false **/
public static boolean initializeFormat(String format){
List<Archetype> lda = CardThemedLDAIO.loadRawLDA(format);
Map<String,List<List<Pair<String, Double>>>> formatMap = CardThemedLDAIO.loadLDA(format);
if(formatMap==null) {
try {
List<List<Pair<String, Double>>> lda = CardThemedLDAIO.loadRawLDA(format);
formatMap = loadFormat(lda);
CardThemedLDAIO.saveLDA(format, formatMap);
}catch (Exception e){
@@ -45,17 +47,18 @@ public final class CardArchetypeLDAGenerator {
}
}
ldaPools.put(format, formatMap);
ldaArchetypes.put(format, lda);
return true;
}
public static Map<String,List<List<Pair<String, Double>>>> loadFormat(List<List<Pair<String, Double>>> lda) throws Exception{
public static Map<String,List<List<Pair<String, Double>>>> loadFormat(List<Archetype> lda) throws Exception{
List<List<Pair<String, Double>>> topics = new ArrayList<>();
Set<String> cards = new HashSet<String>();
for (int t = 0; t < lda.size(); ++t) {
List<Pair<String, Double>> topic = new ArrayList<>();
Set<String> topicCards = new HashSet<>();
List<Pair<String, Double>> highRankVocabs = lda.get(t);
List<Pair<String, Double>> highRankVocabs = lda.get(t).getCardProbabilities();
if (highRankVocabs.get(0).getRight()<=0.01d){
continue;
}

View File

@@ -17,8 +17,8 @@ public enum DeckType {
PRECONSTRUCTED_DECK("Preconstructed Decks"),
QUEST_OPPONENT_DECK ("Quest Opponent Decks"),
COLOR_DECK ("Random Color Decks"),
STANDARD_CARDGEN_DECK ("Random Standard Card-themed Decks"),
MODERN_CARDGEN_DECK ("Random Modern Card-themed Decks"),
STANDARD_CARDGEN_DECK ("Random Standard Archetype Decks"),
MODERN_CARDGEN_DECK ("Random Modern Archetype Decks"),
STANDARD_COLOR_DECK ("Random Standard Color Decks"),
MODERN_COLOR_DECK ("Random Modern Color Decks"),
THEME_DECK ("Random Theme Decks"),
@@ -36,9 +36,9 @@ public enum DeckType {
DeckType.PRECONSTRUCTED_DECK,
DeckType.QUEST_OPPONENT_DECK,
DeckType.COLOR_DECK,
DeckType.STANDARD_COLOR_DECK,
DeckType.STANDARD_CARDGEN_DECK,
DeckType.MODERN_CARDGEN_DECK,
DeckType.STANDARD_COLOR_DECK,
DeckType.MODERN_COLOR_DECK,
DeckType.THEME_DECK,
DeckType.RANDOM_DECK,

View File

@@ -13,10 +13,12 @@ import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.generation.*;
import forge.deck.io.Archetype;
import forge.game.GameFormat;
import forge.game.GameType;
import forge.item.PaperCard;
import forge.itemmanager.IItemManager;
import forge.limited.ArchetypeDeckBuilder;
import forge.limited.CardThemedCommanderDeckBuilder;
import forge.limited.CardThemedConquestDeckBuilder;
import forge.limited.CardThemedDeckBuilder;
@@ -70,48 +72,6 @@ public class DeckgenUtil {
}
}
/**
* Take two lists of cards with counts of each and combine the second into the first by adding a mean normalized fraction
* of the count in the second list to the first list.
* @param cards1
* @param cards2
*/
public static void combineDistances(List<Map.Entry<PaperCard,Integer>> cards1,List<Map.Entry<PaperCard,Integer>> cards2){
Float secondListWeighting=0.4f;
Integer maxDistance=0;
for (Map.Entry<PaperCard,Integer> pair1:cards1){
maxDistance=maxDistance+pair1.getValue();
}
maxDistance=maxDistance/cards1.size();
Integer maxDistance2=0;
for (Map.Entry<PaperCard,Integer> pair2:cards2){
maxDistance2=maxDistance2+pair2.getValue();
}
maxDistance2=maxDistance2/cards2.size();
for (Map.Entry<PaperCard,Integer> pair2:cards2){
boolean isCardPresent=false;
for (Map.Entry<PaperCard,Integer> pair1:cards1){
if (pair1.getKey().equals(pair2.getKey())){
pair1.setValue(pair1.getValue()+new Float((pair2.getValue()*secondListWeighting*maxDistance/maxDistance2)).intValue());
isCardPresent=true;
break;
}
}
if(!isCardPresent){
Map.Entry<PaperCard,Integer> newEntry=new AbstractMap.SimpleEntry<PaperCard, Integer>(pair2.getKey(),new Float((pair2.getValue()*0.4*maxDistance/maxDistance2)).intValue());
cards1.add(newEntry);
}
}
}
public static class CardDistanceComparator implements Comparator<Map.Entry<PaperCard,Integer>>
{
@Override
public int compare(Map.Entry<PaperCard,Integer> index1, Map.Entry<PaperCard,Integer> index2)
{
return index1.getValue().compareTo(index2.getValue());
}
}
public static Deck buildPlanarConquestDeck(PaperCard card, GameFormat format, DeckFormat deckFormat){
return buildPlanarConquestDeck(card, null, format, deckFormat, false);
@@ -239,11 +199,96 @@ public class DeckgenUtil {
if(deck.getMain().countAll()!=60){
System.out.println(deck.getMain().countAll());
System.out.println("Wrong card count "+deck.getMain().countAll());
deck=buildCardGenDeck(format,isForAI);
deck=buildLDACArchetypeDeck(format,isForAI);
}
if(deck.getMain().countAll(Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES))>27){
System.out.println("Too many lands "+deck.getMain().countAll(Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES)));
deck=buildCardGenDeck(format,isForAI);
deck=buildLDACArchetypeDeck(format,isForAI);
}
while(deck.get(DeckSection.Sideboard).countAll()>15){
deck.get(DeckSection.Sideboard).remove(deck.get(DeckSection.Sideboard).get(0));
}
return deck;
}
public static Deck buildLDACArchetypeDeck(GameFormat format, boolean isForAI){
List<Archetype> keys = new ArrayList<>(CardArchetypeLDAGenerator.ldaArchetypes.get(format.getName()));
Archetype randomKey = keys.get( MyRandom.getRandom().nextInt(keys.size()) );
return buildLDACArchetypeDeck(randomKey,format,isForAI);
}
/**
* Build a deck based on the chosen card.
*
* @param archetype
* @param format
* @param isForAI
* @return
*/
public static Deck buildLDACArchetypeDeck(Archetype archetype, GameFormat format, boolean isForAI){
List<Pair<String, Double>> preSelectedCardNames = archetype.getCardProbabilities();
PaperCard card = StaticData.instance().getCommonCards().getUniqueByName(preSelectedCardNames.get(0).getLeft());
List<PaperCard> selectedCards = new ArrayList<>();
for(Pair<String, Double> pair:preSelectedCardNames){
String name = pair.getLeft();
PaperCard cardToAdd = StaticData.instance().getCommonCards().getUniqueByName(name);
//for(int i=0; i<1;++i) {
if(!cardToAdd.getName().equals(card.getName())) {
selectedCards.add(cardToAdd);
}
//}
}
List<PaperCard> toRemove = new ArrayList<>();
//randomly remove cards
int removeCount=0;
int i=0;
for(PaperCard c:selectedCards){
if(MyRandom.getRandom().nextInt(100)>70+(15-(i/selectedCards.size())*selectedCards.size()) && removeCount<4 //randomly remove some cards - more likely as distance increases
&&!c.getName().contains("Urza")){ //avoid breaking Tron decks
toRemove.add(c);
removeCount++;
}
if(c.getName().equals(card.getName())){//may have been added in secondary list
toRemove.add(c);
}
++i;
}
selectedCards.removeAll(toRemove);
//Add keycard
List<PaperCard> playsetList = new ArrayList<>();
int keyCardCount=4;
if(card.getRules().getMainPart().getManaCost().getCMC()>7){
keyCardCount=1+MyRandom.getRandom().nextInt(4);
}else if(card.getRules().getMainPart().getManaCost().getCMC()>5){
keyCardCount=2+MyRandom.getRandom().nextInt(3);
}
for(int j=0;j<keyCardCount;++j) {
playsetList.add(card);
}
for (PaperCard c:selectedCards){
for(int j=0;j<4;++j) {
if(MyRandom.getRandom().nextInt(100)<90) {
playsetList.add(c);
}
}
}
//build deck from combined list
ArchetypeDeckBuilder dBuilder = new ArchetypeDeckBuilder(archetype, card, playsetList,format,isForAI);
Deck deck = dBuilder.buildDeck();
if(deck.getMain().countAll()!=60){
System.out.println(deck.getMain().countAll());
System.out.println("Wrong card count "+deck.getMain().countAll());
deck=buildLDACArchetypeDeck(format,isForAI);
}
if(deck.getMain().countAll(Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES))>27){
System.out.println("Too many lands "+deck.getMain().countAll(Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES)));
deck=buildLDACArchetypeDeck(format,isForAI);
}
while(deck.get(DeckSection.Sideboard).countAll()>15){
deck.get(DeckSection.Sideboard).remove(deck.get(DeckSection.Sideboard).get(0));

View File

@@ -101,9 +101,9 @@ public class RandomDeckGenerator extends DeckProxy implements Comparable<RandomD
}
return DeckgenUtil.buildColorDeck(colors, null, isAi);
case STANDARD_CARDGEN_DECK:
return DeckgenUtil.buildCardGenDeck(FModel.getFormats().getStandard(),isAi);
return DeckgenUtil.buildLDACArchetypeDeck(FModel.getFormats().getStandard(),isAi);
case MODERN_CARDGEN_DECK:
return DeckgenUtil.buildCardGenDeck(FModel.getFormats().getModern(),isAi);
return DeckgenUtil.buildLDACArchetypeDeck(FModel.getFormats().getModern(),isAi);
case STANDARD_COLOR_DECK:
colors = new ArrayList<String>();
count = Aggregates.randomInt(1, 3);

View File

@@ -0,0 +1,43 @@
package forge.deck.io;
import org.apache.commons.lang3.tuple.Pair;
import java.io.Serializable;
import java.util.List;
public class Archetype implements Serializable {
private List<Pair<String, Double>> cardProbabilities;
private String name;
private Integer deckCount;
public Archetype(List<Pair<String, Double>> cardProbabilities, String name, Integer deckCount){
this.cardProbabilities = cardProbabilities;
this.name = name;
this.deckCount = deckCount;
}
public List<Pair<String, Double>> getCardProbabilities() {
return cardProbabilities;
}
public void setCardProbabilities(List<Pair<String, Double>> cardProbabilities) {
this.cardProbabilities = cardProbabilities;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getDeckCount() {
return deckCount;
}
public void setDeckCount(Integer deckCount) {
this.deckCount = deckCount;
}
}

View File

@@ -19,7 +19,7 @@ public class CardThemedLDAIO {
public static final String SUFFIX_DATA = ".lda.dat";
public static final String RAW_SUFFIX_DATA = ".raw.dat";
public static void saveRawLDA(String format, List<List<Pair<String, Double>>> lda){
public static void saveRawLDA(String format, List<Archetype> lda){
File file = getRAWLDAFile(format);
ObjectOutputStream s = null;
try {
@@ -40,11 +40,11 @@ public class CardThemedLDAIO {
}
}
public static List<List<Pair<String, Double>>> loadRawLDA(String format){
public static List<Archetype> loadRawLDA(String format){
try {
FileInputStream fin = new FileInputStream(getRAWLDAFile(format));
ObjectInputStream s = new ObjectInputStream(fin);
List<List<Pair<String, Double>>> matrix = (List<List<Pair<String, Double>>>) s.readObject();
List<Archetype> matrix = (List<Archetype>) s.readObject();
s.close();
return matrix;
}catch (Exception e){

View File

@@ -34,10 +34,10 @@ public class GauntletUtil {
deck = DeckgenUtil.getRandomColorDeck(FModel.getFormats().getStandard().getFilterPrinted(),true);
break;
case STANDARD_CARDGEN_DECK:
deck = DeckgenUtil.buildCardGenDeck(FModel.getFormats().getStandard(),true);
deck = DeckgenUtil.buildLDACArchetypeDeck(FModel.getFormats().getStandard(),true);
break;
case MODERN_CARDGEN_DECK:
deck = DeckgenUtil.buildCardGenDeck(FModel.getFormats().getModern(),true);
deck = DeckgenUtil.buildLDACArchetypeDeck(FModel.getFormats().getModern(),true);
break;
case MODERN_COLOR_DECK:
deck = DeckgenUtil.getRandomColorDeck(FModel.getFormats().getModern().getFilterPrinted(),true);

View File

@@ -0,0 +1,28 @@
package forge.limited;
import forge.deck.DeckFormat;
import forge.deck.io.Archetype;
import forge.game.GameFormat;
import forge.item.PaperCard;
import java.util.List;
public class ArchetypeDeckBuilder extends CardThemedDeckBuilder{
private Archetype archetype;
public ArchetypeDeckBuilder(Archetype archetype0, PaperCard keyCard0, final List<PaperCard> dList, GameFormat format, boolean isForAI){
super(keyCard0,null, dList, format, isForAI, DeckFormat.Constructed);
archetype = archetype0;
}
/**
* Generate a descriptive name.
*
* @return name
*/
protected String generateName() {
return archetype.getName() + " generated deck";
}
}