mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Basic AI sideboarding framework (#5089)
* - Add OTJ achievements by Marek14. * - AI hint for Transcendence. * - Basic AI sideboarding framework. * - Basic AI sideboarding framework. * - Add an option to allow planeswalkers to replace creatures. * - Add an option to only allow shared types for cards exchanged during sideboarding. * - Comment tweak * - Modifications according to recommendations. * - Logic tweak for lastOutcome.
This commit is contained in:
@@ -139,7 +139,13 @@ public enum AiProps { /** */
|
||||
SACRIFICE_DEFAULT_PREF_MIN_CMC("0"),
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CMC("2"),
|
||||
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS("true"),
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL("135");
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL("135"),
|
||||
SIDEBOARDING_ENABLE("true"),
|
||||
SIDEBOARDING_CHANCE_PER_CARD("50"),
|
||||
SIDEBOARDING_CHANCE_ON_WIN("0"),
|
||||
SIDEBOARDING_IN_LIMITED_FORMATS("false"),
|
||||
SIDEBOARDING_SHARED_TYPE_ONLY("false"),
|
||||
SIDEBOARDING_PLANESWALKER_EQ_CREATURE("false");
|
||||
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
|
||||
// <-- There are no experimental options here -->
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.*;
|
||||
import forge.LobbyPlayer;
|
||||
import forge.ai.ability.ProtectAi;
|
||||
import forge.card.CardStateName;
|
||||
@@ -105,8 +102,98 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public List<PaperCard> sideboard(Deck deck, GameType gameType, String message) {
|
||||
// AI does not know how to sideboard
|
||||
return null;
|
||||
if (!getAi().getBooleanProperty(AiProps.SIDEBOARDING_ENABLE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
int sbChanceOnWin = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_ON_WIN);
|
||||
int sbChancePerCard = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_PER_CARD);
|
||||
|
||||
if (!sbLimitedFormats && gameType.isCardPoolLimited()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GameOutcome lastOutcome = brains.getGame().getMatch().getLastOutcome();
|
||||
if (lastOutcome.getWinningPlayer().getPlayer().equals(player.getLobbyPlayer())
|
||||
&& MyRandom.getRandom().nextInt(100) > sbChanceOnWin) {
|
||||
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?
|
||||
List<PaperCard> processed = Lists.newArrayList();
|
||||
Map<PaperCard, PaperCard> sideboardPlan = Maps.newHashMap();
|
||||
for (PaperCard cSide : sideboard) {
|
||||
if (processed.contains(cSide)) {
|
||||
continue;
|
||||
} else if (cSide.getRules().getAiHints().getRemAIDecks()) {
|
||||
continue; // don't sideboard in anything that we don't know how to play
|
||||
} else if (cSide.getRules().getType().isLand()) {
|
||||
continue; // don't know how to sideboard lands efficiently yet
|
||||
}
|
||||
|
||||
for (PaperCard cMain : main) {
|
||||
if (processed.contains(cMain)) {
|
||||
continue;
|
||||
} else if (cMain.getName().equals(cSide.getName())) {
|
||||
continue;
|
||||
} else if (cMain.getRules().getType().isLand()) {
|
||||
continue; // don't know how to sideboard lands efficiently yet
|
||||
}
|
||||
|
||||
if (sbSharedTypesOnly) {
|
||||
if (!cMain.getRules().getType().sharesCardTypeWith(cSide.getRules().getType())) {
|
||||
continue; // Only equivalent types allowed
|
||||
}
|
||||
} else {
|
||||
if ((cMain.getRules().getType().isCreature() && !cSide.getRules().getType().isCreature())
|
||||
|| (cSide.getRules().getType().isCreature()) && !cMain.getRules().getType().isCreature()) {
|
||||
if (!(sbPlaneswalkerException && (cMain.getRules().getType().isPlaneswalker() || cSide.getRules().getType().isPlaneswalker()))) {
|
||||
continue; // Creature exception: only trade a creature for another creature unless planeswalkers are allowed as a replacement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Card.fromPaperCard(cMain, player).getManaAbilities().isEmpty()) {
|
||||
processed.add(cMain);
|
||||
continue; // Mana Ability exception: Don't sideboard out cards that produce mana, can screw up the mana base
|
||||
}
|
||||
|
||||
// Try not to screw up the mana curve or color distribution too much
|
||||
if (cSide.getRules().getColor().hasNoColorsExcept(cMain.getRules().getColor())
|
||||
&& cMain.getRules().getManaCost().getCMC() == cSide.getRules().getManaCost().getCMC()) {
|
||||
sideboardPlan.put(cMain, cSide);
|
||||
processed.add(cSide);
|
||||
processed.add(cMain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make changes according to the sideboarding plan suggested above
|
||||
for (Map.Entry<PaperCard, PaperCard> ent : sideboardPlan.entrySet()) {
|
||||
if (MyRandom.getRandom().nextInt(100) < sbChancePerCard) {
|
||||
continue;
|
||||
}
|
||||
long inMain = main.stream().filter(pc -> pc.getCardName().equals(ent.getKey().getName())).count();
|
||||
long inSide = sideboard.stream().filter(pc -> pc.getCardName().equals(ent.getValue().getName())).count();
|
||||
while (inMain-- > 0 && inSide-- > 0) {
|
||||
sideboard.remove(ent.getValue());
|
||||
sideboard.add(ent.getKey());
|
||||
main.add(ent.getValue());
|
||||
main.remove(ent.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
// Return the new Main. It's important to make sure that the overall content of the deck (Main+Sideboard)
|
||||
// does not change above, or the AI may cheat (sneak some cards in or remove them from the deck altogether).
|
||||
return main;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,7 +203,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
|
||||
// TODO: AI current can't use this so this is not implemented.
|
||||
// TODO: AI currently can't use this so this is not implemented.
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,10 @@ public class Match {
|
||||
return gameOutcomes.values();
|
||||
}
|
||||
|
||||
public GameOutcome getLastOutcome() {
|
||||
return lastOutcome;
|
||||
}
|
||||
|
||||
public boolean isMatchOver() {
|
||||
int[] victories = new int[players.size()];
|
||||
for (GameOutcome go : getOutcomes()) {
|
||||
|
||||
@@ -311,4 +311,18 @@ SACRIFICE_DEFAULT_PREF_MAX_CMC=1
|
||||
# consider the sacrifice of a matching card is a token
|
||||
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
|
||||
# Master toggle enabling AI sideboarding
|
||||
SIDEBOARDING_ENABLE=true
|
||||
# Enable sideboarding in limited formats (e.g. Sealed, Draft)
|
||||
SIDEBOARDING_IN_LIMITED_FORMATS=false
|
||||
# Chance to proceed with sideboarding any given pair of cards in the devised sideboarding plan
|
||||
SIDEBOARDING_CHANCE_PER_CARD=50
|
||||
# Chance to do some sideboarding after winning a game
|
||||
SIDEBOARDING_CHANCE_ON_WIN=0
|
||||
# Only allow replacing a card with another card that shares the same core types. If disabled, mixing types will be
|
||||
# allowed, although a creature is still only replaced with another creature (or planeswalker, see the next option)
|
||||
SIDEBOARDING_SHARED_TYPE_ONLY=true
|
||||
# Allow replacing a creature with a planeswalker and vice versa when sideboarding
|
||||
SIDEBOARDING_PLANESWALKER_EQ_CREATURE=false
|
||||
|
||||
@@ -312,4 +312,18 @@ SACRIFICE_DEFAULT_PREF_MAX_CMC=2
|
||||
# consider the sacrifice of a matching card is a token
|
||||
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
|
||||
# Master toggle enabling AI sideboarding
|
||||
SIDEBOARDING_ENABLE=true
|
||||
# Enable sideboarding in limited formats (e.g. Sealed, Draft)
|
||||
SIDEBOARDING_IN_LIMITED_FORMATS=false
|
||||
# Chance to proceed with sideboarding any given pair of cards in the devised sideboarding plan
|
||||
SIDEBOARDING_CHANCE_PER_CARD=50
|
||||
# Chance to do some sideboarding after winning a game
|
||||
SIDEBOARDING_CHANCE_ON_WIN=0
|
||||
# Only allow replacing a card with another card that shares the same core types. If disabled, mixing types will be
|
||||
# allowed, although a creature is still only replaced with another creature (or planeswalker, see the next option)
|
||||
SIDEBOARDING_SHARED_TYPE_ONLY=false
|
||||
# Allow replacing a creature with a planeswalker and vice versa when sideboarding if the previous option is disabled
|
||||
SIDEBOARDING_PLANESWALKER_EQ_CREATURE=false
|
||||
@@ -314,6 +314,20 @@ SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
|
||||
# Master toggle enabling AI sideboarding
|
||||
SIDEBOARDING_ENABLE=true
|
||||
# Enable sideboarding in limited formats (e.g. Sealed, Draft)
|
||||
SIDEBOARDING_IN_LIMITED_FORMATS=false
|
||||
# Chance to proceed with sideboarding any given pair of cards in the devised sideboarding plan
|
||||
SIDEBOARDING_CHANCE_PER_CARD=50
|
||||
# Chance to do some sideboarding after winning a game
|
||||
SIDEBOARDING_CHANCE_ON_WIN=25
|
||||
# Only allow replacing a card with another card that shares the same core types. If disabled, mixing types will be
|
||||
# allowed, although a creature is still only replaced with another creature (or planeswalker, see the next option)
|
||||
SIDEBOARDING_SHARED_TYPE_ONLY=true
|
||||
# Allow replacing a creature with a planeswalker and vice versa when sideboarding if the previous option is disabled
|
||||
SIDEBOARDING_PLANESWALKER_EQ_CREATURE=true
|
||||
|
||||
# -- Experimental feature toggles which only exist until the testing procedure for the relevant --
|
||||
# -- features is over. These toggles will be removed later, or may be reintroduced under a --
|
||||
# -- different name if necessary --
|
||||
|
||||
@@ -312,4 +312,18 @@ SACRIFICE_DEFAULT_PREF_MAX_CMC=3
|
||||
# consider the sacrifice of a matching card is a token
|
||||
SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS=true
|
||||
# A creature should evaluate to no more than this much to be considered for default SacCost preference
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL=135
|
||||
|
||||
# Master toggle enabling AI sideboarding
|
||||
SIDEBOARDING_ENABLE=true
|
||||
# Enable sideboarding in limited formats (e.g. Sealed, Draft)
|
||||
SIDEBOARDING_IN_LIMITED_FORMATS=false
|
||||
# Chance to proceed with sideboarding any given pair of cards in the devised sideboarding plan
|
||||
SIDEBOARDING_CHANCE_PER_CARD=65
|
||||
# Chance to do some sideboarding after winning a game
|
||||
SIDEBOARDING_CHANCE_ON_WIN=0
|
||||
# Only allow replacing a card with another card that shares the same core types. If disabled, mixing types will be
|
||||
# allowed, although a creature is still only replaced with another creature (or planeswalker, see the next option)
|
||||
SIDEBOARDING_SHARED_TYPE_ONLY=false
|
||||
# Allow replacing a creature with a planeswalker and vice versa when sideboarding
|
||||
SIDEBOARDING_PLANESWALKER_EQ_CREATURE=false
|
||||
|
||||
Reference in New Issue
Block a user