mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Merge branch 'Card-Forge:master' into master
This commit is contained in:
@@ -179,7 +179,7 @@ public class AiAttackController {
|
||||
|
||||
List<Player> opps = Lists.newArrayList(ai.getOpponents());
|
||||
if (forCombatDmg) {
|
||||
for (Player p : ai.getOpponents().threadSafeIterable()) {
|
||||
for (Player p : ai.getOpponents()) {
|
||||
if (p.isMonarch() && ai.canBecomeMonarch()) {
|
||||
// just increase the odds for now instead of being fully predictable
|
||||
// as it could lead to other too complex factors giving this reasoning negative impact
|
||||
|
||||
@@ -782,15 +782,9 @@ public class AiController {
|
||||
return AiPlayDecision.CantAfford;
|
||||
}
|
||||
}
|
||||
if (wardCost.hasSpecificCostType(CostPayLife.class)) {
|
||||
int lifeToPay = wardCost.getCostPartByType(CostPayLife.class).convertAmount();
|
||||
if (lifeToPay > player.getLife() || (lifeToPay == player.getLife() && !player.cantLoseForZeroOrLessLife())) {
|
||||
return AiPlayDecision.CantAfford;
|
||||
}
|
||||
}
|
||||
if (wardCost.hasSpecificCostType(CostDiscard.class)
|
||||
&& wardCost.getCostPartByType(CostDiscard.class).convertAmount() > player.getCardsIn(ZoneType.Hand).size()) {
|
||||
return AiPlayDecision.CantAfford;
|
||||
SpellAbilityAi topAI = new SpellAbilityAi() {};
|
||||
if (!topAI.willPayCosts(player, sa , wardCost, host)) {
|
||||
return AiPlayDecision.CostNotAcceptable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1473,11 +1467,9 @@ public class AiController {
|
||||
return singleSpellAbilityList(simPicker.chooseSpellAbilityToPlay(null));
|
||||
}
|
||||
|
||||
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
||||
CardCollection playBeforeLand = CardLists.filter(
|
||||
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
|
||||
);
|
||||
|
||||
if (!playBeforeLand.isEmpty()) {
|
||||
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
|
||||
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
|
||||
@@ -1486,7 +1478,8 @@ public class AiController {
|
||||
return singleSpellAbilityList(wantToPlayBeforeLand);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
||||
if (landsWannaPlay != null) {
|
||||
landsWannaPlay = filterLandsToPlay(landsWannaPlay);
|
||||
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);
|
||||
|
||||
@@ -448,7 +448,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, isEffect(), c, null);
|
||||
return PaymentDecision.card(list);
|
||||
return list == null ? null : PaymentDecision.card(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.ai.AiCardMemory.MemorySet;
|
||||
import forge.ai.ability.ChooseGenericEffectAi;
|
||||
import forge.ai.ability.ProtectAi;
|
||||
import forge.ai.ability.TokenAi;
|
||||
@@ -449,14 +450,27 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
// try everything when about to die
|
||||
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
|
||||
if (!nonCreatures.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||
} else if (!typeList.isEmpty()) {
|
||||
// TODO make sure survival is possible in case the creature blocks a trampler
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// in some rare situations the call to lifeInDanger could lead us back here, this will prevent an overflow
|
||||
boolean preventReturn = sa != null && sa.isManaAbility();
|
||||
if (preventReturn) {
|
||||
AiCardMemory.rememberCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL);
|
||||
}
|
||||
|
||||
boolean danger = ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat());
|
||||
|
||||
if (preventReturn) {
|
||||
AiCardMemory.forgetCard(ai, sa.getHostCard(), MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL);
|
||||
}
|
||||
|
||||
if (danger) {
|
||||
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
|
||||
if (!nonCreatures.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||
} else if (!typeList.isEmpty()) {
|
||||
// TODO make sure survival is possible in case the creature blocks a trampler
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,7 +623,7 @@ public class ComputerUtil {
|
||||
int count = 0;
|
||||
|
||||
while (count < amount) {
|
||||
Card prefCard = getCardPreference(ai, source, "SacCost", typeList);
|
||||
Card prefCard = getCardPreference(ai, source, "SacCost", typeList, ability);
|
||||
if (prefCard == null) {
|
||||
prefCard = ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostUntap;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordCollection;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -1417,12 +1418,14 @@ public class ComputerUtilCard {
|
||||
double nonCombatChance = 0.0f;
|
||||
double combatChance = 0.0f;
|
||||
// non-combat Haste: has an activated ability with tap cost
|
||||
for (SpellAbility ab : c.getSpellAbilities()) {
|
||||
Cost abCost = ab.getPayCosts();
|
||||
if (abCost != null && abCost.hasTapCost()
|
||||
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, 0, false))) {
|
||||
nonCombatChance += 0.5f;
|
||||
break;
|
||||
if (c.isAbilitySick()) {
|
||||
for (SpellAbility ab : c.getSpellAbilities()) {
|
||||
Cost abCost = ab.getPayCosts();
|
||||
if (abCost != null && (abCost.hasTapCost() || abCost.hasSpecificCostType(CostUntap.class))
|
||||
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, sa.getPayCosts().getTotalMana().getCMC(), false))) {
|
||||
nonCombatChance += 0.5f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// combat Haste: only grant it if the creature will attack
|
||||
@@ -1730,6 +1733,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
|
||||
pumped.addChangedCardKeywordsInternal(toCopy, null, false, timestamp2, 0, false);
|
||||
pumped.updateKeywordsCache(pumped.getCurrentState());
|
||||
applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
|
||||
return pumped;
|
||||
}
|
||||
@@ -1916,7 +1920,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
}
|
||||
if (!canBeBlocked) {
|
||||
boolean threat = atk.getNetCombatDamage() >= ai.getLife() - lifeInDanger;
|
||||
boolean threat = ComputerUtilCombat.getAttack(atk) >= ai.getLife() - lifeInDanger;
|
||||
if (!priorityRemovalOnlyInDanger || threat) {
|
||||
priorityCards.add(atk);
|
||||
}
|
||||
|
||||
@@ -328,10 +328,10 @@ public class ComputerUtilCombat {
|
||||
if (blockers.size() == 0
|
||||
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
if (!attacker.hasKeyword(Keyword.INFECT)) {
|
||||
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE) && !attacker.hasKeyword(Keyword.INFECT)) {
|
||||
int dmgAfterShielding = getAttack(attacker) - totalShieldDamage(attacker, blockers);
|
||||
if (dmgAfterShielding > 0) {
|
||||
damage += dmgAfterShielding;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,13 +369,14 @@ public class ComputerUtilCombat {
|
||||
if (blockers.size() == 0
|
||||
|| StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
|
||||
unblocked.add(attacker);
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
|
||||
&& getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
|
||||
} else if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
|
||||
if (attacker.hasKeyword(Keyword.INFECT)) {
|
||||
poison += trampleDamage;
|
||||
if (trampleDamage > 0) {
|
||||
if (attacker.hasKeyword(Keyword.INFECT)) {
|
||||
poison += trampleDamage;
|
||||
}
|
||||
poison += predictPoisonFromTriggers(attacker, ai, trampleDamage);
|
||||
}
|
||||
poison += predictPoisonFromTriggers(attacker, ai, trampleDamage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,7 +687,7 @@ public class ComputerUtilCombat {
|
||||
final int defenderDefense = blocker.getLethalDamage() - flankingMagnitude + defBushidoMagnitude;
|
||||
|
||||
return defenderDefense;
|
||||
} // shieldDamage
|
||||
}
|
||||
|
||||
// For AI safety measures like Regeneration
|
||||
/**
|
||||
@@ -2053,7 +2054,6 @@ public class ComputerUtilCombat {
|
||||
if (block.size() == 1) {
|
||||
final Card blocker = block.getFirst();
|
||||
|
||||
// trample
|
||||
if (hasTrample) {
|
||||
int dmgToKill = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true);
|
||||
|
||||
@@ -2109,7 +2109,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
}
|
||||
return damageMap;
|
||||
} // setAssignedDamage()
|
||||
}
|
||||
|
||||
// how much damage is enough to kill the creature (for AI)
|
||||
/**
|
||||
|
||||
@@ -234,7 +234,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility, final boolean effect) {
|
||||
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final SpellAbility sourceAbility, final boolean effect) {
|
||||
// TODO cheating via autopay can still happen, need to get the real ai player from controlledBy
|
||||
if (cost == null || !ai.isAI()) {
|
||||
return true;
|
||||
@@ -247,18 +247,17 @@ public class ComputerUtilCost {
|
||||
exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_SAC_COST));
|
||||
}
|
||||
if (part.payCostFromSource()) {
|
||||
list.add(source);
|
||||
list.add(sourceAbility.getHostCard());
|
||||
} else if (part.getType().equals("OriginalHost")) {
|
||||
list.add(sourceAbility.getOriginalHost());
|
||||
} else if (part.getAmount().equals("All")) {
|
||||
// Does the AI want to use Sacrifice All?
|
||||
return false;
|
||||
} else {
|
||||
final String amount = part.getAmount();
|
||||
Integer c = part.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, amount, sourceAbility);
|
||||
c = part.getAbilityAmount(sourceAbility);
|
||||
}
|
||||
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
|
||||
@@ -636,7 +635,7 @@ public class ComputerUtilCost {
|
||||
|
||||
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
|
||||
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
|
||||
} // canPayCost()
|
||||
}
|
||||
|
||||
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
@@ -316,10 +316,6 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.Animate) {
|
||||
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
|
||||
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
|
||||
@@ -381,9 +377,15 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||
return paymentChoice;
|
||||
if (!canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return paymentChoice;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -620,6 +620,9 @@ public abstract class GameState {
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
|
||||
// prevent interactions with objects from old state
|
||||
game.copyLastState();
|
||||
|
||||
// Set negative or zero life after state effects if need be, important for some puzzles that rely on
|
||||
// pre-setting negative life (e.g. PS_NEO4).
|
||||
for (int i = 0; i < playerStates.size(); i++) {
|
||||
|
||||
@@ -43,8 +43,6 @@ import forge.game.card.CardView;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostAdjustment;
|
||||
import forge.game.cost.CostExile;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -698,24 +696,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
ability.setActivatingPlayer(c.getController(), true);
|
||||
ability.setCardState(sa.getCardState());
|
||||
|
||||
// FIXME: This is a hack to check if the AI can play the "exile from library" pay costs (Cumulative Upkeep,
|
||||
// e.g. Thought Lash). We have to do it and bail early if the AI can't pay, because otherwise the AI will
|
||||
// pay the cost partially, which should not be possible
|
||||
int nExileLib = 0;
|
||||
List<CostPart> parts = CostAdjustment.adjust(cost, sa).getCostParts();
|
||||
for (final CostPart part : parts) {
|
||||
if (part instanceof CostExile) {
|
||||
CostExile exile = (CostExile) part;
|
||||
if (exile.from == ZoneType.Library) {
|
||||
nExileLib += exile.convertAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nExileLib > c.getController().getCardsIn(ZoneType.Library).size()) {
|
||||
return false;
|
||||
}
|
||||
// - End of hack for Exile a card from library Cumulative Upkeep -
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, c.getController(), true)) {
|
||||
ComputerUtil.playNoStack(c.getController(), ability, getGame(), true);
|
||||
// transfer this info for Balduvian Fallen
|
||||
|
||||
@@ -1580,7 +1580,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
&& canBeBlocked
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
} else if (keyword.equals("Haste")) {
|
||||
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
|
||||
return card.hasSickness() && ph.isPlayerTurn(ai) && !card.isTapped()
|
||||
&& card.getNetCombatDamage() + powerBonus > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
|
||||
@@ -113,9 +113,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
||||
}
|
||||
if (!ph.isPlayerTurn(ai)) {
|
||||
return CombatUtil.canAttack(card, ai)
|
||||
&& (card.getNetCombatDamage() > 0)
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||
return card.getNetCombatDamage() > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& CombatUtil.canAttack(card, ai);
|
||||
} else {
|
||||
if (ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| ph.getPhase().isBefore(PhaseType.MAIN1)) {
|
||||
@@ -129,7 +129,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
&& (combat == null || !combat.isAttacking(c))) {
|
||||
return false;
|
||||
}
|
||||
return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c));
|
||||
return (combat != null && combat.isAttacking(c)) || CombatUtil.canAttack(c, card.getController());
|
||||
}
|
||||
});
|
||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
||||
@@ -148,8 +148,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
// the cards controller needs to be the one attacked
|
||||
return CombatUtil.canAttack(c, card.getController()) || (combat != null && combat.isAttacking(c)
|
||||
&& card.getController().equals(combat.getDefenderPlayerByAttacker(c)));
|
||||
return (combat != null && combat.isAttacking(c) && card.getController().equals(combat.getDefenderPlayerByAttacker(c))) ||
|
||||
CombatUtil.canAttack(c, card.getController());
|
||||
}
|
||||
});
|
||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
||||
@@ -199,7 +199,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
final boolean evasive = keyword.endsWith("Shadow");
|
||||
// give evasive keywords to creatures that can or do attack
|
||||
if (evasive) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
@@ -231,7 +231,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||
@@ -244,25 +244,25 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
return true;
|
||||
}
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||
Keyword.HORSEMANSHIP).isEmpty();
|
||||
} else if (keyword.endsWith("Intimidate")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getNotType(CardLists.filter(
|
||||
opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact").isEmpty();
|
||||
} else if (keyword.endsWith("Fear")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getNotColor(CardLists.getNotType(CardLists.filter(
|
||||
opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Artifact"), MagicColor.BLACK).isEmpty();
|
||||
} else if (keyword.endsWith("Haste")) {
|
||||
return card.hasSickness() && !ph.isPlayerTurn(opp) && !card.isTapped()
|
||||
return CombatUtil.isAttackerSick(card, opp) && !ph.isPlayerTurn(opp) && !card.isTapped()
|
||||
&& newPower > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& ComputerUtilCombat.canAttackNextTurn(card);
|
||||
@@ -293,7 +293,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
return false;
|
||||
} else if (keyword.startsWith("Bushido")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& !opp.getCreaturesInPlay().isEmpty()
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
@@ -320,22 +320,22 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
return false;
|
||||
} else if (keyword.equals("Double Strike")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& newPower > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
} else if (keyword.startsWith("Rampage")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& newPower > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() >= 2;
|
||||
} else if (keyword.startsWith("Flanking")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& newPower > 0
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& !CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||
Keyword.FLANKING).isEmpty();
|
||||
} else if (keyword.startsWith("Trample")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& CombatUtil.canBeBlocked(card, null, opp)
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 1
|
||||
@@ -347,8 +347,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
if (combat != null && combat.isBlocking(card) && !card.hasKeyword(Keyword.WITHER)) {
|
||||
return true;
|
||||
}
|
||||
return (!ph.isPlayerTurn(opp))
|
||||
&& (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
} else if (keyword.endsWith("Wither")) {
|
||||
if (newPower <= 0 || card.hasKeyword(Keyword.INFECT)) {
|
||||
@@ -376,25 +375,25 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
} else if (keyword.equals("Persist")) {
|
||||
return card.getBaseToughness() > 1 && !card.hasKeyword(Keyword.UNDYING);
|
||||
} else if (keyword.equals("Islandwalk")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
} else if (keyword.equals("Swampwalk")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
} else if (keyword.equals("Mountainwalk")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
} else if (keyword.equals("Forestwalk")) {
|
||||
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
|
||||
return !ph.isPlayerTurn(opp) && ((combat != null && combat.isAttacking(card)) || CombatUtil.canAttack(card, opp))
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& newPower > 0
|
||||
&& !CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
|
||||
|
||||
@@ -206,12 +206,7 @@ public class GameCopier {
|
||||
Card newCard = map.map(origHostCard);
|
||||
SpellAbility newSa = null;
|
||||
if (origSa.isSpell()) {
|
||||
for (SpellAbility sa : newCard.getAllSpellAbilities()) {
|
||||
if (sa.getDescription().equals(origSa.getDescription())) {
|
||||
newSa = sa;
|
||||
break;
|
||||
}
|
||||
}
|
||||
newSa = findSAInCard(origSa, newCard);
|
||||
}
|
||||
if (newSa != null) {
|
||||
newSa.setActivatingPlayer(map.map(origSa.getActivatingPlayer()), true);
|
||||
|
||||
@@ -11,6 +11,10 @@ public class MultiTargetSelector {
|
||||
public static class Targets {
|
||||
private ArrayList<PossibleTargetSelector.Targets> targets;
|
||||
|
||||
public int size() {
|
||||
return targets.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -24,8 +28,8 @@ public class MultiTargetSelector {
|
||||
}
|
||||
}
|
||||
|
||||
private List<PossibleTargetSelector> selectors;
|
||||
private List<SpellAbility> targetingSAs;
|
||||
private final List<PossibleTargetSelector> selectors;
|
||||
private final List<SpellAbility> targetingSAs;
|
||||
private int currentIndex;
|
||||
|
||||
public MultiTargetSelector(SpellAbility sa, List<AbilitySub> plannedSubs) {
|
||||
@@ -52,8 +56,8 @@ public class MultiTargetSelector {
|
||||
public Targets getLastSelectedTargets() {
|
||||
Targets targets = new Targets();
|
||||
targets.targets = new ArrayList<>(selectors.size());
|
||||
for (int i = 0; i < selectors.size(); i++) {
|
||||
targets.targets.add(selectors.get(i).getLastSelectedTargets());
|
||||
for (PossibleTargetSelector selector : selectors) {
|
||||
targets.targets.add(selector.getLastSelectedTargets());
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
@@ -78,34 +82,62 @@ public class MultiTargetSelector {
|
||||
currentIndex = -1;
|
||||
}
|
||||
|
||||
public void selectTargetsByIndex(int i) {
|
||||
public boolean selectTargetsByIndex(int i) {
|
||||
// The caller is telling us to select the i-th possible set of targets.
|
||||
if (i < currentIndex) {
|
||||
reset();
|
||||
}
|
||||
while (currentIndex < i) {
|
||||
selectNextTargets();
|
||||
if (!selectNextTargets()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean selectNextTargets() {
|
||||
if (currentIndex == -1) {
|
||||
for (PossibleTargetSelector selector : selectors) {
|
||||
if (!selector.selectNextTargets()) {
|
||||
private boolean selectTargetsStartingFrom(int selectorIndex) {
|
||||
// Don't reset the current selector, as it still has the correct list of targets set and has
|
||||
// to remember its current/next target index. Subsequent selectors need a reset since their
|
||||
// possible targets may change based on what was chosen for earlier ones.
|
||||
if (selectors.get(selectorIndex).selectNextTargets()) {
|
||||
for (int i = selectorIndex + 1; i < selectors.size(); i++) {
|
||||
selectors.get(i).reset();
|
||||
if (!selectors.get(i).selectNextTargets()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
currentIndex = 0;
|
||||
return true;
|
||||
}
|
||||
for (int i = selectors.size() - 1; i >= 0; i--) {
|
||||
if (selectors.get(i).selectNextTargets()) {
|
||||
currentIndex++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean selectNextTargets() {
|
||||
if (selectors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (currentIndex == -1) {
|
||||
// Select the first set of targets (calls selectNextTargets() on each selector).
|
||||
if (selectTargetsStartingFrom(0)) {
|
||||
currentIndex = 0;
|
||||
return true;
|
||||
}
|
||||
selectors.get(i).reset();
|
||||
selectors.get(i).selectNextTargets();
|
||||
// No possible targets.
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// Subsequent call, first try selecting a new target for the last selector. If that doesn't
|
||||
// work, backtrack (decrement selector index) and try selecting targets from there.
|
||||
// This approach ensures that leaf selectors (end of list) are advanced first, before
|
||||
// previous ones, so that we get an AA,AB,BA,BB ordering.
|
||||
int selectorIndex = selectors.size() - 1;
|
||||
while (!selectTargetsStartingFrom(selectorIndex)) {
|
||||
if (selectorIndex == 0) {
|
||||
// No more possible targets.
|
||||
return false;
|
||||
}
|
||||
selectorIndex--;
|
||||
}
|
||||
currentIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean conditionsAreMet(SpellAbility saOrSubSa) {
|
||||
|
||||
@@ -17,12 +17,11 @@ import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class PossibleTargetSelector {
|
||||
private final SpellAbility sa;
|
||||
private SpellAbility targetingSa;
|
||||
private int targetingSaIndex;
|
||||
private final SpellAbility targetingSa;
|
||||
private final int targetingSaIndex;
|
||||
private int maxTargets;
|
||||
private TargetRestrictions tgt;
|
||||
private int targetIndex;
|
||||
private List<GameObject> validTargets;
|
||||
private int nextTargetIndex;
|
||||
private final List<GameObject> validTargets = new ArrayList<>();
|
||||
|
||||
public static class Targets {
|
||||
final int targetingSaIndex;
|
||||
@@ -36,7 +35,7 @@ public class PossibleTargetSelector {
|
||||
this.targetIndex = targetIndex;
|
||||
this.description = description;
|
||||
|
||||
if (targetIndex < 0 || targetIndex >= originalTargetCount) {
|
||||
if (targetIndex != -1 && (targetIndex < 0 || targetIndex >= originalTargetCount)) {
|
||||
throw new IllegalArgumentException("Invalid targetIndex=" + targetIndex);
|
||||
}
|
||||
}
|
||||
@@ -51,12 +50,11 @@ public class PossibleTargetSelector {
|
||||
this.sa = sa;
|
||||
this.targetingSa = targetingSa;
|
||||
this.targetingSaIndex = targetingSaIndex;
|
||||
this.validTargets = new ArrayList<>();
|
||||
generateValidTargets(sa.getHostCard().getController());
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
targetIndex = 0;
|
||||
nextTargetIndex = 0;
|
||||
validTargets.clear();
|
||||
generateValidTargets(sa.getHostCard().getController());
|
||||
}
|
||||
@@ -67,7 +65,7 @@ public class PossibleTargetSelector {
|
||||
}
|
||||
sa.setActivatingPlayer(player, true);
|
||||
targetingSa.resetTargets();
|
||||
tgt = targetingSa.getTargetRestrictions();
|
||||
TargetRestrictions tgt = targetingSa.getTargetRestrictions();
|
||||
maxTargets = tgt.getMaxTargets(sa.getHostCard(), targetingSa);
|
||||
|
||||
SimilarTargetSkipper skipper = new SimilarTargetSkipper();
|
||||
@@ -80,8 +78,8 @@ public class PossibleTargetSelector {
|
||||
}
|
||||
|
||||
private static class SimilarTargetSkipper {
|
||||
private ArrayListMultimap<String, Card> validTargetsMap = ArrayListMultimap.create();
|
||||
private HashMap<Card, String> cardTypeStrings = new HashMap<>();
|
||||
private final ArrayListMultimap<String, Card> validTargetsMap = ArrayListMultimap.create();
|
||||
private final HashMap<Card, String> cardTypeStrings = new HashMap<>();
|
||||
private HashMap<Card, Integer> creatureScores;
|
||||
|
||||
private int getCreatureScore(Card c) {
|
||||
@@ -190,16 +188,7 @@ public class PossibleTargetSelector {
|
||||
}
|
||||
|
||||
public Targets getLastSelectedTargets() {
|
||||
return new Targets(targetingSaIndex, validTargets.size(), targetIndex - 1, targetingSa.getTargets().toString());
|
||||
}
|
||||
|
||||
public boolean selectTargetsByIndex(int targetIndex) {
|
||||
if (targetIndex >= validTargets.size()) {
|
||||
return false;
|
||||
}
|
||||
selectTargetsByIndexImpl(targetIndex);
|
||||
this.targetIndex = targetIndex + 1;
|
||||
return true;
|
||||
return new Targets(targetingSaIndex, validTargets.size(), nextTargetIndex - 1, targetingSa.getTargets().toString());
|
||||
}
|
||||
|
||||
public boolean selectTargets(Targets targets) {
|
||||
@@ -208,16 +197,16 @@ public class PossibleTargetSelector {
|
||||
return false;
|
||||
}
|
||||
selectTargetsByIndexImpl(targets.targetIndex);
|
||||
this.targetIndex = targets.targetIndex + 1;
|
||||
this.nextTargetIndex = targets.targetIndex + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean selectNextTargets() {
|
||||
if (targetIndex >= validTargets.size()) {
|
||||
if (nextTargetIndex >= validTargets.size()) {
|
||||
return false;
|
||||
}
|
||||
selectTargetsByIndexImpl(targetIndex);
|
||||
targetIndex++;
|
||||
selectTargetsByIndexImpl(nextTargetIndex);
|
||||
nextTargetIndex++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,11 @@ public class SimulationController {
|
||||
}
|
||||
|
||||
public void doneEvaluating(Score score) {
|
||||
if (score.value > bestScore.value) {
|
||||
// if we're here during a deeper level this hasn't been called for the level above yet
|
||||
// in such case we need to check that this decision has really lead to the improvement in score
|
||||
if (getLastDecision().initialScore.value < score.value && score.value > bestScore.value) {
|
||||
bestScore = score;
|
||||
bestSequence = currentStack.get(currentStack.size() - 1);
|
||||
bestSequence = getLastDecision();
|
||||
}
|
||||
currentStack.remove(currentStack.size() - 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user