mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
- 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:
@@ -32,12 +32,14 @@ import com.esotericsoftware.minlog.Log;
|
|||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
@@ -60,6 +62,7 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostDiscard;
|
import forge.game.cost.CostDiscard;
|
||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -634,6 +637,33 @@ public class AiController {
|
|||||||
return bestSA;
|
return bestSA;
|
||||||
} // playCounterSpell()
|
} // 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.)
|
// This is for playing spells regularly (no Cascade/Ripple etc.)
|
||||||
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
|
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
|
||||||
if (!sa.canPlay()) {
|
if (!sa.canPlay()) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import forge.game.mana.Mana;
|
|||||||
import forge.game.mana.ManaCostAdjustment;
|
import forge.game.mana.ManaCostAdjustment;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.mana.ManaPool;
|
import forge.game.mana.ManaPool;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
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) {
|
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
|
||||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||||
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
||||||
@@ -514,7 +630,11 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
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; }
|
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
|
||||||
|
|
||||||
@@ -556,6 +676,18 @@ public class ComputerUtilMana {
|
|||||||
return true;
|
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) {
|
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
|
||||||
// mind the priorities
|
// mind the priorities
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import forge.game.Game;
|
|||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
@@ -48,6 +49,11 @@ public class PumpAi extends PumpAiBase {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
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 Cost cost = sa.getPayCosts();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
|
|||||||
Reference in New Issue
Block a user