Deck-based AI hints metadata + predefined sideboarding plan support (#5113)

* - Support for AI hints in deck metadata.
- Support for pre-planned sideboarding using an AI hint.

* - Fix imports.

* - NPE prevention for cases when the AI has no sideboard.
This commit is contained in:
Agetian
2024-04-24 07:56:01 +03:00
committed by GitHub
parent b4b8d05260
commit 9f13d36799
4 changed files with 111 additions and 42 deletions

View File

@@ -102,10 +102,43 @@ public class PlayerControllerAi extends PlayerController {
@Override
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
if (!getAi().getBooleanProperty(AiProps.SIDEBOARDING_ENABLE)) {
if (!getAi().getBooleanProperty(AiProps.SIDEBOARDING_ENABLE)
|| !deck.has(DeckSection.Sideboard)) {
return null;
}
Map<PaperCard, PaperCard> sideboardPlan = Maps.newHashMap();
List<PaperCard> main = deck.get(DeckSection.Main).toFlatList();
List<PaperCard> sideboard = deck.get(DeckSection.Sideboard).toFlatList();
// Predefined sideboard plan from deck metadata (AI hints)
boolean definedSideboardPlan = false;
String sideboardAiHint = deck.getAiHint("SideboardingPlan");
if (!sideboardAiHint.isEmpty()) {
for (String element : sideboardAiHint.split(";")) {
String[] cardPair = element.split("->");
PaperCard src = null, tgt = null;
for (PaperCard cMain : main) {
if (cMain.getCardName().equals(cardPair[0].trim())) {
src = cMain;
break;
}
}
for (PaperCard cSide : sideboard) {
if (cSide.getCardName().equals(cardPair[1].trim())) {
tgt = cSide;
break;
}
}
if (src != null && tgt != null) {
sideboardPlan.put(src, tgt);
}
}
if (!sideboardPlan.isEmpty()) {
definedSideboardPlan = true;
}
}
boolean sbLimitedFormats = getAi().getBooleanProperty(AiProps.SIDEBOARDING_IN_LIMITED_FORMATS);
boolean sbSharedTypesOnly = getAi().getBooleanProperty(AiProps.SIDEBOARDING_SHARED_TYPE_ONLY);
boolean sbPlaneswalkerException = getAi().getBooleanProperty(AiProps.SIDEBOARDING_PLANESWALKER_EQ_CREATURE);
@@ -122,13 +155,9 @@ public class PlayerControllerAi extends PlayerController {
return null;
}
List<PaperCard> main = deck.get(DeckSection.Main).toFlatList();
List<PaperCard> sideboard = deck.get(DeckSection.Sideboard).toFlatList();
// Devise a sideboarding plan
// TODO: Allow a pre-planned (e.g. fully transformative) sideboard via deck metadata of some kind?
if (!definedSideboardPlan) {
List<PaperCard> processed = Lists.newArrayList();
Map<PaperCard, PaperCard> sideboardPlan = Maps.newHashMap();
for (PaperCard cSide : sideboard) {
if (processed.contains(cSide)) {
continue;
@@ -175,10 +204,11 @@ public class PlayerControllerAi extends PlayerController {
}
}
}
}
// Make changes according to the sideboarding plan suggested above
for (Map.Entry<PaperCard, PaperCard> ent : sideboardPlan.entrySet()) {
if (MyRandom.getRandom().nextInt(100) < sbChancePerCard) {
if (!definedSideboardPlan && MyRandom.getRandom().nextInt(100) < sbChancePerCard) {
continue;
}
long inMain = main.stream().filter(pc -> pc.getCardName().equals(ent.getKey().getName())).count();

View File

@@ -48,6 +48,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
private final Set<String> tags = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
// Supports deferring loading a deck until we actually need its contents. This works in conjunction with
// the lazy card load feature to ensure we don't need to load all cards on start up.
private final Set<String> aiHints = new TreeSet<>();
private Map<String, List<String>> deferredSections = null;
private Map<String, List<String>> loadedSections = null;
private String lastCardArtPreferenceUsed = "";
@@ -207,6 +208,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
result.parts.put(kv.getKey(), cp);
cp.addAll(kv.getValue());
}
result.setAiHints(StringUtils.join(aiHints, " | "));
}
/*
@@ -536,6 +538,29 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return allCards;
}
public void setAiHints(String aiHintsInfo) {
if (aiHintsInfo == null || aiHintsInfo.trim().equals("")) {
return;
}
String[] hints = aiHintsInfo.split("\\|");
for (String hint : hints) {
aiHints.add(hint.trim());
}
}
public Set<String> getAiHints() {
return aiHints;
}
public String getAiHint(String name) {
for (String aiHint : aiHints) {
if (aiHint.toLowerCase().startsWith(name.toLowerCase() + "$")) {
return aiHint.substring(aiHint.indexOf("$") + 1).trim();
}
}
return "";
}
public UnplayableAICards getUnplayableAICards() {
if (unplayableAI == null) {
unplayableAI = new UnplayableAICards(this);

View File

@@ -45,6 +45,7 @@ public class DeckFileHeader {
private static final String PLAYER = "Player";
private static final String CSTM_POOL = "Custom Pool";
private static final String PLAYER_TYPE = "PlayerType";
public static final String AI_HINTS = "AiHints";
private final DeckFormat deckType;
private final boolean customPool;
@@ -55,6 +56,7 @@ public class DeckFileHeader {
private final Set<String> tags;
private final boolean intendedForAi;
private final String aiHints;
/**
* @return the intendedForAi
@@ -63,6 +65,13 @@ public class DeckFileHeader {
return intendedForAi;
}
/**
* @return the AI hints
*/
public String getAiHints() {
return aiHints;
}
/**
* TODO: Write javadoc for Constructor.
*
@@ -75,6 +84,7 @@ public class DeckFileHeader {
this.deckType = DeckFormat.smartValueOf(kvPairs.get(DeckFileHeader.DECK_TYPE), DeckFormat.Constructed);
this.customPool = kvPairs.getBoolean(DeckFileHeader.CSTM_POOL);
this.intendedForAi = "computer".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER)) || "ai".equalsIgnoreCase(kvPairs.get(DeckFileHeader.PLAYER_TYPE));
this.aiHints = kvPairs.get(DeckFileHeader.AI_HINTS);
this.tags = new TreeSet<>();
String rawTags = kvPairs.get(DeckFileHeader.TAGS);

View File

@@ -53,6 +53,9 @@ public class DeckSerializer {
if (!d.getTags().isEmpty()) {
out.add(TextUtil.concatNoSpace(DeckFileHeader.TAGS,"=", StringUtils.join(d.getTags(), DeckFileHeader.TAGS_SEPARATOR)));
}
if (!d.getAiHints().isEmpty()) {
out.add(TextUtil.concatNoSpace(DeckFileHeader.AI_HINTS, "=", StringUtils.join(d.getAiHints(), " | ")));
}
for(Entry<DeckSection, CardPool> s : d) {
out.add(TextUtil.enclosedBracket(s.getKey().toString()));
@@ -77,6 +80,7 @@ public class DeckSerializer {
Deck d = new Deck(dh.getName());
d.setComment(dh.getComment());
d.setAiHints(dh.getAiHints());
d.getTags().addAll(dh.getTags());
d.setDeferredSections(sections);
return d;