mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18: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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
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.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -41,6 +43,7 @@ import forge.game.card.CounterEnumType;
|
|||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.combat.GlobalAttackRestrictions;
|
import forge.game.combat.GlobalAttackRestrictions;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -121,7 +124,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
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>() {
|
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card c) {
|
public boolean apply(Card c) {
|
||||||
@@ -475,7 +478,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
CardLists.sortByPowerDesc(this.attackers);
|
CardLists.sortByPowerDesc(this.attackers);
|
||||||
|
|
||||||
final CardCollection unblockedAttackers = new CardCollection();
|
CardCollection unblockedAttackers = new CardCollection();
|
||||||
final CardCollection remainingAttackers = new CardCollection(this.attackers);
|
final CardCollection remainingAttackers = new CardCollection(this.attackers);
|
||||||
final CardCollection remainingBlockers = new CardCollection(this.blockers);
|
final CardCollection remainingBlockers = new CardCollection(this.blockers);
|
||||||
final CardCollection blockedAttackers = new CardCollection();
|
final CardCollection blockedAttackers = new CardCollection();
|
||||||
@@ -564,6 +567,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
int numExtraBlocks = blocker.canBlockAdditional();
|
int numExtraBlocks = blocker.canBlockAdditional();
|
||||||
if (numExtraBlocks > 0) {
|
if (numExtraBlocks > 0) {
|
||||||
|
// TODO should be limited to how much getBlockCost the opp can pay
|
||||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
blockedAttackers.add(remainingAttackers.get(0));
|
||||||
remainingAttackers.remove(0);
|
remainingAttackers.remove(0);
|
||||||
@@ -580,27 +584,69 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
unblockedAttackers.addAll(remainingAttackers);
|
unblockedAttackers.addAll(remainingAttackers);
|
||||||
|
|
||||||
int trampleDamage = 0;
|
int totalCombatDamage = 0;
|
||||||
for (Card attacker : blockedAttackers) {
|
|
||||||
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
// TODO might want to only calculate that if it's needed
|
||||||
int damage = ComputerUtilCombat.getAttack(attacker);
|
// TODO might want to factor in isManaSourceReserved
|
||||||
for (Card blocker : this.blockers) {
|
int myFreeMana = ComputerUtilMana.getAvailableManaEstimate(ai, !nextTurn);
|
||||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
// skip attackers exceeding the attack tax
|
||||||
damage -= ComputerUtilCombat.shieldDamage(attacker, blocker);
|
// (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) {
|
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
|
||||||
trampleDamage += damage;
|
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()
|
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, defendingOpponent) >= defendingOpponent.getLife()
|
||||||
&& !((defendingOpponent.cantLoseForZeroOrLessLife() || ai.cantWin()) && defendingOpponent.getLife() < 1)) {
|
&& !((defendingOpponent.cantLoseForZeroOrLessLife() || ai.cantWin()) && defendingOpponent.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO tramplers
|
||||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, defendingOpponent);
|
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, defendingOpponent);
|
||||||
if (totalPoisonDamage >= 10 - defendingOpponent.getPoisonCounters()) {
|
if (totalPoisonDamage >= 10 - defendingOpponent.getPoisonCounters()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -609,6 +655,32 @@ public class AiAttackController {
|
|||||||
return false;
|
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) {
|
private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
|
||||||
final FCollectionView<GameEntity> defs = c.getDefenders();
|
final FCollectionView<GameEntity> defs = c.getDefenders();
|
||||||
if (defs.size() == 1) {
|
if (defs.size() == 1) {
|
||||||
|
|||||||
@@ -3057,6 +3057,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// TODO !thisCombat should include cards that will phase in
|
// TODO !thisCombat should include cards that will phase in
|
||||||
for (Card att : opp.getCreaturesInPlay()) {
|
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))) {
|
if ((thisCombat && CombatUtil.canAttack(att, ai)) || (!thisCombat && ComputerUtilCombat.canAttackNextTurn(att, ai))) {
|
||||||
// TODO need to copy the card
|
// TODO need to copy the card
|
||||||
// att = ComputerUtilCombat.applyPotentialAttackCloneTriggers(att);
|
// att = ComputerUtilCombat.applyPotentialAttackCloneTriggers(att);
|
||||||
|
|||||||
Reference in New Issue
Block a user