mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Improve Attack/Block costs decisions
This commit is contained in:
@@ -20,6 +20,8 @@ package forge.ai;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -41,6 +43,7 @@ import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.combat.GlobalAttackRestrictions;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -121,7 +124,7 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
||||
List<Card> defenders = new ArrayList<>(defender.getCreaturesInPlay());
|
||||
List<Card> defenders = defender.getCreaturesInPlay();
|
||||
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
@@ -475,7 +478,7 @@ public class AiAttackController {
|
||||
|
||||
CardLists.sortByPowerDesc(this.attackers);
|
||||
|
||||
final CardCollection unblockedAttackers = new CardCollection();
|
||||
CardCollection unblockedAttackers = new CardCollection();
|
||||
final CardCollection remainingAttackers = new CardCollection(this.attackers);
|
||||
final CardCollection remainingBlockers = new CardCollection(this.blockers);
|
||||
final CardCollection blockedAttackers = new CardCollection();
|
||||
@@ -564,6 +567,7 @@ public class AiAttackController {
|
||||
|
||||
int numExtraBlocks = blocker.canBlockAdditional();
|
||||
if (numExtraBlocks > 0) {
|
||||
// TODO should be limited to how much getBlockCost the opp can pay
|
||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
||||
blockedAttackers.add(remainingAttackers.get(0));
|
||||
remainingAttackers.remove(0);
|
||||
@@ -580,27 +584,69 @@ public class AiAttackController {
|
||||
}
|
||||
unblockedAttackers.addAll(remainingAttackers);
|
||||
|
||||
int trampleDamage = 0;
|
||||
for (Card attacker : blockedAttackers) {
|
||||
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||
int damage = ComputerUtilCombat.getAttack(attacker);
|
||||
for (Card blocker : this.blockers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||
damage -= ComputerUtilCombat.shieldDamage(attacker, blocker);
|
||||
int totalCombatDamage = 0;
|
||||
|
||||
// TODO might want to only calculate that if it's needed
|
||||
// TODO might want to factor in isManaSourceReserved
|
||||
int myFreeMana = ComputerUtilMana.getAvailableManaEstimate(ai, !nextTurn);
|
||||
// skip attackers exceeding the attack tax
|
||||
// (this prevents the AI from only making a partial attack that could backfire)
|
||||
|
||||
Pair<Integer, Integer> tramplerFirst = getDamageFromBlockingTramplers(blockedAttackers, remainingAttackers, myFreeMana);
|
||||
int trampleDamage = tramplerFirst.getLeft();
|
||||
int tramplerTaxPaid = tramplerFirst.getRight();
|
||||
|
||||
// see how far we can get if paying for the unblockable first instead
|
||||
if (tramplerTaxPaid > 0) {
|
||||
int unblockableAttackTax = 0;
|
||||
final CardCollection unblockableWithPaying = new CardCollection();
|
||||
final CardCollection unblockableCantPayFor = new CardCollection();
|
||||
final CardCollection unblockableWithoutCost = new CardCollection();
|
||||
// TODO also check poison
|
||||
for (Card attacker : CardLists.getKeyword(unblockedAttackers, Keyword.TRAMPLE)) {
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
if (tax == null) {
|
||||
unblockableWithoutCost.add(attacker);
|
||||
} else {
|
||||
int taxCMC = tax.getCostMana().getMana().getCMC();
|
||||
if (myFreeMana < unblockableAttackTax + taxCMC) {
|
||||
unblockableCantPayFor.add(attacker);
|
||||
continue;
|
||||
}
|
||||
unblockableAttackTax += taxCMC;
|
||||
unblockableWithPaying.add(attacker);
|
||||
}
|
||||
if (damage > 0) {
|
||||
trampleDamage += damage;
|
||||
}
|
||||
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
|
||||
if (dmgUnblockableAfterPaying > trampleDamage) {
|
||||
unblockedAttackers.removeAll(unblockableCantPayFor);
|
||||
unblockedAttackers.removeAll(unblockableWithPaying);
|
||||
totalCombatDamage = dmgUnblockableAfterPaying;
|
||||
// recalculate the trampler damage with the reduced mana available now
|
||||
trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingAttackers, myFreeMana - unblockableAttackTax).getLeft();
|
||||
} else {
|
||||
unblockedAttackers = unblockableWithoutCost;
|
||||
myFreeMana -= tramplerTaxPaid;
|
||||
// find out if we can still pay for some left
|
||||
for (Card attacker : unblockableWithPaying) {
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
int taxCMC = tax.getCostMana().getMana().getCMC();
|
||||
if (myFreeMana < unblockableAttackTax + taxCMC) {
|
||||
continue;
|
||||
}
|
||||
unblockableAttackTax += taxCMC;
|
||||
unblockedAttackers.add(attacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, defendingOpponent) + trampleDamage;
|
||||
totalCombatDamage += ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, defendingOpponent) + trampleDamage;
|
||||
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, defendingOpponent) >= defendingOpponent.getLife()
|
||||
&& !((defendingOpponent.cantLoseForZeroOrLessLife() || ai.cantWin()) && defendingOpponent.getLife() < 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO tramplers
|
||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, defendingOpponent);
|
||||
if (totalPoisonDamage >= 10 - defendingOpponent.getPoisonCounters()) {
|
||||
return true;
|
||||
@@ -609,6 +655,32 @@ public class AiAttackController {
|
||||
return false;
|
||||
}
|
||||
|
||||
private final Pair<Integer, Integer> getDamageFromBlockingTramplers(final List<Card> blockedAttackers, final List<Card> blockers, final int myFreeMana) {
|
||||
int currentAttackTax = 0;
|
||||
int trampleDamage = 0;
|
||||
CardCollection remainingBlockers = new CardCollection(blockers);
|
||||
for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) {
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0;
|
||||
if (myFreeMana < currentAttackTax + taxCMC) {
|
||||
continue;
|
||||
}
|
||||
currentAttackTax += taxCMC;
|
||||
|
||||
int damage = ComputerUtilCombat.getAttack(attacker);
|
||||
for (Card blocker : remainingBlockers.threadSafeIterable()) {
|
||||
if (CombatUtil.canBlock(attacker, blocker) && damage > 0) {
|
||||
damage -= ComputerUtilCombat.shieldDamage(attacker, blocker);
|
||||
remainingBlockers.remove(blocker);
|
||||
}
|
||||
}
|
||||
if (damage > 0) {
|
||||
trampleDamage += damage;
|
||||
}
|
||||
}
|
||||
return Pair.of(trampleDamage, currentAttackTax);
|
||||
}
|
||||
|
||||
private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
|
||||
final FCollectionView<GameEntity> defs = c.getDefenders();
|
||||
if (defs.size() == 1) {
|
||||
|
||||
@@ -3057,6 +3057,7 @@ public class ComputerUtil {
|
||||
|
||||
// TODO !thisCombat should include cards that will phase in
|
||||
for (Card att : opp.getCreaturesInPlay()) {
|
||||
// TODO should be limited based on how much getAttackCost the opp can pay
|
||||
if ((thisCombat && CombatUtil.canAttack(att, ai)) || (!thisCombat && ComputerUtilCombat.canAttackNextTurn(att, ai))) {
|
||||
// TODO need to copy the card
|
||||
// att = ComputerUtilCombat.applyPotentialAttackCloneTriggers(att);
|
||||
|
||||
Reference in New Issue
Block a user