- Highly experimental: added a way for the AI to reserve mana for casting a Main 2 phase spell when deciding whether to pump a creature or not. This may not be optimal and probably needs refactoring as well. It's a somewhat drastic changes so bugs may arise (though the main use cases were tested). Please assist if possible.

This commit is contained in:
Agetian
2014-09-04 20:18:48 +00:00
parent 8041d7a440
commit a481c0b536
3 changed files with 169 additions and 1 deletions

View File

@@ -32,12 +32,14 @@ import com.esotericsoftware.minlog.Log;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
@@ -60,6 +62,7 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -634,6 +637,33 @@ public class AiController {
return bestSA;
} // playCounterSpell()
public SpellAbility evaluateSpellInMain2(ApiType exceptSA) {
final List<Card> cards = getAvailableCards();
ArrayList<SpellAbility> all = getSpellAbilities(cards);
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
continue;
}
sa.setActivatingPlayer(player);
if (sa.canPlay() && !ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().getType().contains("Land") && ComputerUtilCost.canPayCost(sa, player)) {
return sa;
}
}
return null;
}
public void reserveManaSourcesForMain2(SpellAbility sa) {
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
ArrayList<Card> manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
for (Card c : manaSources) {
((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
}
}
// This is for playing spells regularly (no Cascade/Ripple etc.)
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
if (!sa.canPlay()) {

View File

@@ -21,6 +21,7 @@ import forge.game.mana.Mana;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
@@ -230,6 +231,121 @@ public class ComputerUtilMana {
}
public static ArrayListMultimap<ManaCostShard, SpellAbility> getManaSourcesToPayCost(final Player ai, final ManaCostBeingPaid cost) {
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
sortManaAbilities(sourcesForShards);
return sourcesForShards;
}
public static ArrayList<Card> getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
ArrayList<Card> manaSources = new ArrayList<Card>();
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = new ArrayList<Mana>();
final ManaPool manapool = ai.getManaPool();
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
if (part != ManaCostShard.X) {
if (cost.isPaid()) {
continue;
}
// get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
manaSpentToPay.add(0, mana);
}
}
}
}
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources;
}
// arrange all mana abilities by color produced.
final ArrayListMultimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
if (manaAbilityMap.isEmpty()) {
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, true, cost.isPaid());
return manaSources;
}
// select which abilities may be used for each shard
ArrayListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
sortManaAbilities(sourcesForShards);
ManaCostShard toPay = null;
// Loop over mana needed
while (!cost.isPaid()) {
toPay = getNextShardToPay(cost);
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
SpellAbility saPayment = null;
if (saList != null) {
for (final SpellAbility ma : saList) {
if (ma.getHostCard() == sa.getHostCard()) {
continue;
}
final String typeRes = cost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
continue;
}
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, true)) {
saPayment = ma;
manaSources.add(saPayment.getHostCard());
break;
}
}
} else {
break;
}
if (saPayment == null) {
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
break; // cannot pay
}
cost.payPhyrexian();
continue;
}
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
//System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
while (itSa.hasNext()) {
SpellAbility srcSa = itSa.next();
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
itSa.remove();
}
}
}
handleOfferingsAI(sa, true, cost.isPaid());
refundMana(manaSpentToPay, ai, sa);
return manaSources;
} // getManaSourcesToPayCost()
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
@@ -514,8 +630,12 @@ public class ComputerUtilMana {
}
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
final Card sourceCard = ma.getHostCard();
Card sourceCard = ma.getHostCard();
if (isManaSourceReserved(ai, sourceCard)) {
return false;
}
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
AbilityManaPart m = ma.getManaPart();
@@ -556,6 +676,18 @@ public class ComputerUtilMana {
return true;
}
private static boolean isManaSourceReserved(Player ai, Card sourceCard) {
if (ai.getGame().getPhaseHandler().getPhase() == PhaseType.MAIN2) {
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearRememberedManaSources();
} else {
if (((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
// This mana source is held elsewhere for a Main Phase 2 spell.
return true;
}
}
return false;
}
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
// mind the priorities

View File

@@ -5,6 +5,7 @@ import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
@@ -48,6 +49,11 @@ public class PumpAi extends PumpAiBase {
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
SpellAbility futureSpell = ((PlayerControllerAi)ai.getController()).getAi().evaluateSpellInMain2(ApiType.Pump);
if (futureSpell != null && futureSpell.getHostCard() != null) {
((PlayerControllerAi)ai.getController()).getAi().reserveManaSourcesForMain2(futureSpell);
}
final Cost cost = sa.getPayCosts();
final Game game = ai.getGame();
final PhaseHandler ph = game.getPhaseHandler();