LandEvaluator for AI

This commit is contained in:
Anthony Calosa
2023-02-21 21:21:39 +08:00
parent 5852942e54
commit e2c3908df6
2 changed files with 236 additions and 209 deletions

View File

@@ -108,6 +108,7 @@ public class AiController {
public boolean usesSimulation() {
return this.useSimulation;
}
public void setUseSimulation(boolean value) {
this.useSimulation = value;
}
@@ -217,6 +218,7 @@ public class AiController {
card.setCastSA(null);
return result;
}
private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility sa, final ApiType api) {
final Player activator = sa.getActivatingPlayer();
@@ -476,6 +478,8 @@ public class AiController {
// If nothing is done here, proceeds to the default land picking strategy
}
return ComputerUtilCard.getBestLandToPlayAI(landList);
/*
//Skip reflected lands.
CardCollection unreflectedLands = new CardCollection(landList);
for (Card l : landList) {
@@ -574,7 +578,7 @@ public class AiController {
landList = CardLists.filter(landList, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
}
}
return landList.get(0);
return landList.get(0);*/
}
// if return true, go to next phase
@@ -617,6 +621,7 @@ public class AiController {
public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
return predictSpellToCastInMain2(exceptSA, true);
}
private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) {
if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) {
return null;
@@ -629,8 +634,7 @@ public class AiController {
try {
Collections.sort(all, saComparator); // put best spells first
}
catch (IllegalArgumentException ex) {
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
@@ -657,12 +661,15 @@ public class AiController {
public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) {
return reserveManaSources(sa, null, false, true, exceptForSa);
}
public boolean reserveManaSources(SpellAbility sa) {
return reserveManaSources(sa, PhaseType.MAIN2, false, false, null);
}
public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
return reserveManaSources(sa, phaseType, enemy, true, null);
}
public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy, boolean forNextSpell, SpellAbility exceptForThisSa) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
@@ -784,7 +791,8 @@ public class AiController {
return AiPlayDecision.CantAfford;
}
}
SpellAbilityAi topAI = new SpellAbilityAi() {};
SpellAbilityAi topAI = new SpellAbilityAi() {
};
if (!topAI.willPayCosts(player, sa, wardCost, host)) {
return AiPlayDecision.CostNotAcceptable;
}
@@ -1142,6 +1150,7 @@ public class AiController {
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) {
return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY);
}
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) {
boolean noFiltering = sa != null && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
@@ -1193,7 +1202,9 @@ public class AiController {
}
}
for (Card c : inHand) {
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) {
continue;
}
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) {
discards.add(c);
}
@@ -1232,8 +1243,7 @@ public class AiController {
discardList.add(prefCard);
validCards.remove(prefCard);
count++;
}
else {
} else {
break;
}
}
@@ -1256,8 +1266,7 @@ public class AiController {
if (canDiscardLands) {
discardList.add(landsInHand.get(0));
validCards.remove(landsInHand.get(0));
}
else { // Discard other stuff
} else { // Discard other stuff
CardLists.sortByCmcDesc(validCards);
int numLandsAvailable = numLandsInPlay;
if (numLandsInHand > 0) {
@@ -1448,7 +1457,9 @@ public class AiController {
}
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
if (sa == null) { return null; }
if (sa == null) {
return null;
}
final List<SpellAbility> abilities = Lists.newArrayList();
abilities.add(sa);
@@ -1705,8 +1716,7 @@ public class AiController {
try {
Collections.sort(all, saComparator); // put best spells first
}
catch (IllegalArgumentException ex) {
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
String assertex = ComparatorUtil.verifyTransitivity(saComparator, all);
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
@@ -1949,8 +1959,7 @@ public class AiController {
}
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
switch(sa.getApi())
{
switch (sa.getApi()) {
case SetLife: // Reverse the Sands
if (relatedPlayer.equals(sa.getHostCard().getController())) {
return Collections.max(options);
@@ -2082,8 +2091,7 @@ public class AiController {
* smoothComputerManaCurve.
* </p>
*
* @param in
* an array of {@link forge.game.card.Card} objects.
* @param in an array of {@link forge.game.card.Card} objects.
* @return an array of {@link forge.game.card.Card} objects.
*/
public CardCollectionView cheatShuffle(CardCollectionView in) {

View File

@@ -9,6 +9,8 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Function;
import forge.ai.simulation.GameStateEvaluator;
import forge.card.mana.ManaCost;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
@@ -92,6 +94,7 @@ public class ComputerUtilCard {
}
// The AI doesn't really pick the best artifact, just the most expensive.
/**
* <p>
* getBestArtifactAI.
@@ -111,6 +114,7 @@ public class ComputerUtilCard {
/**
* Returns the best Planeswalker from a given list
*
* @param list list of cards to evaluate
* @return best Planeswalker
*/
@@ -125,6 +129,7 @@ public class ComputerUtilCard {
/**
* Returns the worst Planeswalker from a given list
*
* @param list list of cards to evaluate
* @return best Planeswalker
*/
@@ -188,16 +193,15 @@ public class ComputerUtilCard {
}
// The AI doesn't really pick the best enchantment, just the most expensive.
/**
* <p>
* getBestEnchantmentAI.
* </p>
*
* @param list
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @param spell a {@link forge.game.card.Card} object.
* @param targeted a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
@@ -326,10 +330,8 @@ public class ComputerUtilCard {
* </p>
*
* @param all
* @param spell
* a {@link forge.game.card.Card} object.
* @param targeted
* a boolean.
* @param spell a {@link forge.game.card.Card} object.
* @param targeted a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getCheapestPermanentAI(Iterable<Card> all, final SpellAbility spell, final boolean targeted) {
@@ -358,6 +360,7 @@ public class ComputerUtilCard {
}
// returns null if list.size() == 0
/**
* <p>
* getBestAI.
@@ -381,8 +384,7 @@ public class ComputerUtilCard {
/**
* getBestCreatureAI.
*
* @param list
* the list
* @param list the list
* @return the card
*/
public static Card getBestCreatureAI(final Iterable<Card> list) {
@@ -392,6 +394,19 @@ public class ComputerUtilCard {
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator);
}
/**
* getBestLandToPlayAI.
*
* @param list the list
* @return the card
*/
public static Card getBestLandToPlayAI(final Iterable<Card> list) {
if (Iterables.size(list) == 1) {
return Iterables.get(list, 0);
}
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.LANDS), ComputerUtilCard.landEvaluator);
}
/**
* <p>
* getWorstCreatureAI.
@@ -408,6 +423,7 @@ public class ComputerUtilCard {
}
// This selection rates tokens higher
/**
* <p>
* getBestCreatureToBounceAI.
@@ -465,14 +481,10 @@ public class ComputerUtilCard {
* </p>
*
* @param list
* @param biasEnch
* a boolean.
* @param biasLand
* a boolean.
* @param biasArt
* a boolean.
* @param biasCreature
* a boolean.
* @param biasEnch a boolean.
* @param biasLand a boolean.
* @param biasArt a boolean.
* @param biasCreature a boolean.
* @return a {@link forge.game.card.Card} object.
*/
public static Card getWorstPermanentAI(final Iterable<Card> list, final boolean biasEnch, final boolean biasLand,
@@ -557,14 +569,14 @@ public class ComputerUtilCard {
};
private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
private static final LandEvaluator landEvaluator = new LandEvaluator();
/**
* <p>
* evaluateCreature.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @param c a {@link forge.game.card.Card} object.
* @return a int.
*/
public static int evaluateCreature(final Card c) {
@@ -610,6 +622,7 @@ public class ComputerUtilCard {
/**
* Extension of doesCreatureAttackAI() for "virtual" creatures that do not actually exist on the battlefield yet
* such as unanimated manlands.
*
* @param ai controller of creature
* @param card creature to be evaluated
* @return creature will be attack
@@ -623,6 +636,7 @@ public class ComputerUtilCard {
/**
* Create a mock combat where ai is being attacked and returns the list of likely blockers.
*
* @param ai blocking player
* @param blockers list of additional blockers to be considered
* @return list of creatures assigned to block in the simulation
@@ -654,6 +668,7 @@ public class ComputerUtilCard {
/**
* Decide if a creature is going to be used as a blocker.
*
* @param ai controller of creature
* @param blocker creature to be evaluated
* @return creature will be a blocker
@@ -664,6 +679,7 @@ public class ComputerUtilCard {
/**
* Check if an attacker can be blocked profitably (ie. kill attacker)
*
* @param ai controller of attacking creature
* @param attacker attacking creature to evaluate
* @return attacker will die
@@ -709,8 +725,7 @@ public class ComputerUtilCard {
/**
* getMostExpensivePermanentAI.
*
* @param all
* the all
* @param all the all
* @return the card
*/
public static Card getMostExpensivePermanentAI(final Iterable<Card> all) {
@@ -941,7 +956,8 @@ public class ComputerUtilCard {
}
Collections.sort(map, new Comparator<Pair<Byte, Integer>>() {
@Override public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
@Override
public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
@@ -978,49 +994,38 @@ public class ComputerUtilCard {
if (logic.equals("MostProminentInHumanDeck")) {
chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
}
else if (logic.equals("MostProminentInComputerDeck")) {
} else if (logic.equals("MostProminentInComputerDeck")) {
chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices));
}
else if (logic.equals("MostProminentDualInComputerDeck")) {
} else if (logic.equals("MostProminentDualInComputerDeck")) {
List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
chosen.add(prominence.get(0));
chosen.add(prominence.get(1));
}
else if (logic.equals("MostProminentInGame")) {
} else if (logic.equals("MostProminentInGame")) {
chosen.add(getMostProminentColor(game.getCardsInGame(), colorChoices));
}
else if (logic.equals("MostProminentHumanCreatures")) {
} else if (logic.equals("MostProminentHumanCreatures")) {
CardCollectionView list = opp.getCreaturesInPlay();
if (list.isEmpty()) {
list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES);
}
chosen.add(getMostProminentColor(list, colorChoices));
}
else if (logic.equals("MostProminentComputerControls")) {
} else if (logic.equals("MostProminentComputerControls")) {
chosen.add(getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentHumanControls")) {
} else if (logic.equals("MostProminentHumanControls")) {
chosen.add(getMostProminentColor(opp.getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentPermanent")) {
} else if (logic.equals("MostProminentPermanent")) {
chosen.add(getMostProminentColor(game.getCardsIn(ZoneType.Battlefield), colorChoices));
}
else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
} else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
chosen.add(getMostProminentColor(game.getCombat().getAttackers(), colorChoices));
}
else if (logic.equals("MostProminentInActivePlayerHand")) {
} else if (logic.equals("MostProminentInActivePlayerHand")) {
chosen.add(getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices));
}
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
} else if (logic.equals("MostProminentInComputerDeckButGreen")) {
List<String> prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
chosen.add(prominence.get(1));
} else {
chosen.add(prominence.get(0));
}
}
else if (logic.equals("MostExcessOpponentControls")) {
} else if (logic.equals("MostExcessOpponentControls")) {
int maxExcess = 0;
String bestColor = Constant.GREEN;
for (byte color : MagicColor.WUBRG) {
@@ -1034,8 +1039,7 @@ public class ComputerUtilCard {
}
}
chosen.add(bestColor);
}
else if (logic.equals("MostProminentKeywordInComputerDeck")) {
} else if (logic.equals("MostProminentKeywordInComputerDeck")) {
CardCollectionView list = ai.getAllCards();
int m1 = 0;
String chosenColor = MagicColor.Constant.WHITE;
@@ -1048,8 +1052,7 @@ public class ComputerUtilCard {
}
}
chosen.add(chosenColor);
}
else if (logic.equals("HighestDevotionToColor")) {
} else if (logic.equals("HighestDevotionToColor")) {
int curDevotion = 0;
String chosenColor = MagicColor.Constant.WHITE;
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
@@ -1296,6 +1299,7 @@ public class ComputerUtilCard {
/**
* Decides if the "pump" is worthwhile
*
* @param ai casting player
* @param sa Pump* or CounterPut*
* @param c target of sa
@@ -1308,6 +1312,7 @@ public class ComputerUtilCard {
final int power, final List<String> keywords) {
return shouldPumpCard(ai, sa, c, toughness, power, keywords, false);
}
public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness,
final int power, final List<String> keywords, boolean immediately) {
final Game game = ai.getGame();
@@ -1452,7 +1457,9 @@ public class ComputerUtilCard {
boolean pumpedWillDie = false;
final boolean isAttacking = combat.isAttacking(c);
if ((isBerserk && isAttacking) || loseCardAtEOT) { pumpedWillDie = true; }
if ((isBerserk && isAttacking) || loseCardAtEOT) {
pumpedWillDie = true;
}
if (isAttacking) {
pumpedCombat.addAttacker(pumped, opp);
@@ -1663,6 +1670,7 @@ public class ComputerUtilCard {
/**
* Apply "pump" ability and return modified creature
*
* @param ai casting player
* @param sa Pump* or CounterPut*
* @param c target of sa
@@ -1740,6 +1748,7 @@ public class ComputerUtilCard {
/**
* Applies static continuous Power/Toughness effects to a (virtual) creature.
*
* @param game game instance to work with
* @param vCard creature to work with
* @param exclude list of cards to exclude when considering ability sources, accepts null
@@ -1787,6 +1796,7 @@ public class ComputerUtilCard {
/**
* Evaluate if the ability can save a target against removal
*
* @param ai casting player
* @param sa Pump* or CounterPut*
* @return
@@ -2083,10 +2093,19 @@ public class ComputerUtilCard {
public static boolean isCardRemAIDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemAIDecks();
}
public static boolean isCardRemRandomDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks();
}
public static boolean isCardRemNonCommanderDeck(final Card card) {
return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks();
}
static class LandEvaluator implements Function<Card, Integer> {
@Override
public Integer apply(Card card) {
return GameStateEvaluator.evaluateLand(card);
}
}
}