Simple check for AI if specific attacker should go for PW

This commit is contained in:
TRT
2021-10-18 17:16:29 +02:00
committed by tool4EvEr
parent ff43491d07
commit ea65f77d16
5 changed files with 74 additions and 93 deletions

View File

@@ -44,6 +44,7 @@ import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions; import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
@@ -53,6 +54,7 @@ import forge.util.Aggregates;
import forge.util.Expressions; import forge.util.Expressions;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
@@ -207,7 +209,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) { public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat, final GameEntity defender) {
// if the attacker will die when attacking don't attack // if the attacker will die when attacking don't attack
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
return false; return false;
@@ -238,10 +240,8 @@ public class AiAttackController {
return true; return true;
} }
final Player opp = this.defendingOpponent;
// Damage opponent if unblocked // Damage opponent if unblocked
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true); final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, defender, combat, true);
if (dmgIfUnblocked > 0) { if (dmgIfUnblocked > 0) {
boolean onlyIfExalted = false; boolean onlyIfExalted = false;
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0 if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
@@ -255,14 +255,14 @@ public class AiAttackController {
} }
} }
// Poison opponent if unblocked // Poison opponent if unblocked
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { if (defender instanceof Player && ComputerUtilCombat.poisonIfUnblocked(attacker, (Player) defender) > 0) {
return true; return true;
} }
// TODO check if that makes sense // TODO check if that makes sense
int exalted = ai.countExaltedBonus(); int exalted = ai.countExaltedBonus();
if (this.attackers.size() == 1 && exalted > 0 if (this.attackers.size() == 1 && exalted > 0
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) { && ComputerUtilCombat.predictDamageTo(defender, exalted, attacker, true) > 0) {
return true; return true;
} }
@@ -423,7 +423,7 @@ public class AiAttackController {
if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) { if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) {
// total attack = biggest creature + exalted, *2 if Rafiq is in play // total attack = biggest creature + exalted, *2 if Rafiq is in play
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus; int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) { if (finestHour) {
// For Finest Hour, one creature could attack and get the bonus TWICE // For Finest Hour, one creature could attack and get the bonus TWICE
humanBasePower = humanBasePower + humanExaltedBonus; humanBasePower = humanBasePower + humanExaltedBonus;
@@ -736,7 +736,7 @@ public class AiAttackController {
} else if (attacker.getSVar("MustAttack").equals("True")) { } else if (attacker.getSVar("MustAttack").equals("True")) {
mustAttack = true; mustAttack = true;
} else if (attacker.hasSVar("EndOfTurnLeavePlay") } else if (attacker.hasSVar("EndOfTurnLeavePlay")
&& isEffectiveAttacker(ai, attacker, combat)) { && isEffectiveAttacker(ai, attacker, combat, defender)) {
mustAttack = true; mustAttack = true;
} else if (seasonOfTheWitch) { } else if (seasonOfTheWitch) {
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
@@ -776,7 +776,7 @@ public class AiAttackController {
if (attackMax != -1 && combat.getAttackers().size() >= attackMax) if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
return; return;
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat)) { if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
@@ -817,7 +817,7 @@ public class AiAttackController {
System.out.println("Exalted"); System.out.println("Exalted");
this.aiAggression = 6; this.aiAggression = 6;
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
return; return;
} }
@@ -833,7 +833,7 @@ public class AiAttackController {
// reached max, breakup // reached max, breakup
if (attackMax != -1 && combat.getAttackers().size() >= attackMax) if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
break; break;
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
@@ -1059,6 +1059,10 @@ public class AiAttackController {
if ( LOG_AI_ATTACKS ) if ( LOG_AI_ATTACKS )
System.out.println("attackersLeft = " + attackersLeft); System.out.println("attackersLeft = " + attackersLeft);
FCollection<GameEntity> possibleDefenders = new FCollection<>(combat.getDefenders());
while (true) {
CardCollection attackersAssigned = new CardCollection();
for (int i = 0; i < attackersLeft.size(); i++) { for (int i = 0; i < attackersLeft.size(); i++) {
final Card attacker = attackersLeft.get(i); final Card attacker = attackersLeft.get(i);
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
@@ -1067,64 +1071,47 @@ public class AiAttackController {
continue; continue;
} }
if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) { if (shouldAttack(ai, attacker, this.blockers, combat, defender) && canAttackWrapper(attacker, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
attackersAssigned.add(attacker);
// check if attackers are enough to finish the attacked planeswalker // check if attackers are enough to finish the attacked planeswalker
if (defender instanceof Card) { if (i < attackersLeft.size() - 1 && defender instanceof Card) {
Card pw = (Card) defender;
final int blockNum = this.blockers.size(); final int blockNum = this.blockers.size();
int attackNum = 0; int attackNum = 0;
int damage = 0; int damage = 0;
List<Card> attacking = combat.getAttackersOf(defender); List<Card> attacking = combat.getAttackersOf(defender);
CardLists.sortByPowerAsc(attacking); CardLists.sortByPowerDesc(attacking);
for (Card atta : attacking) { for (Card atta : attacking) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { if (attackNum >= blockNum || !CombatUtil.canBeBlocked(atta, this.blockers, combat)) {
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null, false); damage += ComputerUtilCombat.damageIfUnblocked(atta, defender, null, false);
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { } else {
attackNum++; attackNum++;
} }
} }
// if enough damage: switch to next planeswalker or player // if enough damage: switch to next planeswalker or player
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) { if (damage >= ComputerUtilCombat.getDamageToKill((Card) defender)) {
List<Card> pwDefending = combat.getDefendingPlaneswalkers(); break;
// look for next planeswalker
for (Card walker : Lists.newArrayList(pwDefending)) {
if (!combat.getAttackersOf(walker).isEmpty()) {
pwDefending.remove(walker);
} }
} }
}
}
attackersLeft.removeAll(attackersAssigned);
possibleDefenders.remove(defender);
if (attackersLeft.isEmpty() || possibleDefenders.isEmpty()) {
break;
}
CardCollection pwDefending = new CardCollection(Iterables.filter(possibleDefenders, Card.class));
if (pwDefending.isEmpty()) { if (pwDefending.isEmpty()) {
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife()); defender = Collections.min(new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)), PlayerPredicates.compareByLife());
} } else {
else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending); final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} }
} }
}
}
}
} // getAttackers() } // getAttackers()
/**
* <p>
* getAttack.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage();
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2;
}
return n;
}
/** /**
* <p> * <p>
* shouldAttack. * shouldAttack.
@@ -1138,7 +1125,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat) { public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat, final GameEntity defender) {
boolean canBeKilled = false; // indicates if the attacker can be killed boolean canBeKilled = false; // indicates if the attacker can be killed
boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker
boolean canKillAll = true; // indicates if the attacker can kill all single blockers boolean canKillAll = true; // indicates if the attacker can kill all single blockers
@@ -1169,7 +1156,7 @@ public class AiAttackController {
} }
} }
if (!isEffectiveAttacker(ai, attacker, combat)) { if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
return false; return false;
} }
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
@@ -1205,29 +1192,29 @@ public class AiAttackController {
// look at the attacker in relation to the blockers to establish a // look at the attacker in relation to the blockers to establish a
// number of factors about the attacking context that will be relevant // number of factors about the attacking context that will be relevant
// to the attackers decision according to the selected strategy // to the attackers decision according to the selected strategy
for (final Card defender : validBlockers) { for (final Card blocker : validBlockers) {
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) { if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
numberOfPossibleBlockers += 1; numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false) if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) { && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower // see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value // value. We don't want to attack only to lose value
if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe")
&& ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) { && ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) {
isWorthLessThanAllKillers = false; isWorthLessThanAllKillers = false;
} }
} }
// see if this attacking creature can destroy this defender, if // see if this attacking creature can destroy this defender, if
// not record that it can't kill everything // not record that it can't kill everything
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, defender, attacker, combat, false)) { if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) {
canKillAll = false; canKillAll = false;
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) { if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false; canKillAllDangerous = false;
} else { } else {
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT) if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|| defender.hasKeyword(Keyword.LIFELINK)) { || blocker.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false; canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature // there is a creature that can survive an attack from this creature
// and combat will have negative effects // and combat will have negative effects
@@ -1371,7 +1358,6 @@ public class AiAttackController {
break; break;
} }
} }
} }
if (missTarget) { if (missTarget) {

View File

@@ -167,7 +167,6 @@ public class ComputerUtilCombat {
return totalDamageOfBlockers(attacker, list); return totalDamageOfBlockers(attacker, list);
} }
// This function takes Doran and Double Strike into account // This function takes Doran and Double Strike into account
/** /**
* <p> * <p>
@@ -202,10 +201,10 @@ public class ComputerUtilCombat {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a int. * @return a int.
*/ */
public static int damageIfUnblocked(final Card attacker, final Player attacked, final Combat combat, boolean withoutAbilities) { public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
int damage = attacker.getNetCombatDamage(); int damage = attacker.getNetCombatDamage();
int sum = 0; int sum = 0;
if (!attacked.canLoseLife()) { if (attacked instanceof Player && !((Player) attacked).canLoseLife()) {
return 0; return 0;
} }
@@ -2139,7 +2138,6 @@ public class ComputerUtilCombat {
return damageMap; return damageMap;
} // setAssignedDamage() } // setAssignedDamage()
// how much damage is enough to kill the creature (for AI) // how much damage is enough to kill the creature (for AI)
/** /**
* <p> * <p>
@@ -2175,13 +2173,13 @@ public class ComputerUtilCombat {
*/ */
public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat, public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat,
final boolean noPrevention) { final boolean noPrevention) {
final int killDamage = c.isPlaneswalker() ? c.getCurrentLoyalty() : getDamageToKill(c); final int killDamage = getDamageToKill(c);
if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) { if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) {
if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) { if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
return maxDamage + 1; return maxDamage + 1;
} }
} else if (source.hasKeyword(Keyword.DEATHTOUCH)) { } else if (source.hasKeyword(Keyword.DEATHTOUCH) && !c.isPlaneswalker()) {
for (int i = 1; i <= maxDamage; i++) { for (int i = 1; i <= maxDamage; i++) {
if (noPrevention) { if (noPrevention) {
if (c.staticReplaceDamage(i, source, isCombat) > 0) { if (c.staticReplaceDamage(i, source, isCombat) > 0) {
@@ -2218,9 +2216,9 @@ public class ComputerUtilCombat {
*/ */
public final static int getDamageToKill(final Card c) { public final static int getDamageToKill(final Card c) {
int damageShield = c.getPreventNextDamageTotalShields(); int damageShield = c.getPreventNextDamageTotalShields();
int killDamage = c.getLethalDamage() + damageShield; int killDamage = (c.isPlaneswalker() ? c.getCurrentLoyalty() : c.getLethalDamage()) + damageShield;
if ((killDamage > damageShield) if (killDamage > damageShield
&& c.hasSVar("DestroyWhenDamaged")) { && c.hasSVar("DestroyWhenDamaged")) {
killDamage = 1 + damageShield; killDamage = 1 + damageShield;
} }

View File

@@ -169,7 +169,6 @@ public class CardLists {
Collections.sort(list, Collections.reverseOrder(PowerComparator)); Collections.sort(list, Collections.reverseOrder(PowerComparator));
} }
/** /**
* *
* Given a CardCollection c, return a CardCollection that contains a random amount of cards from c. * Given a CardCollection c, return a CardCollection that contains a random amount of cards from c.

View File

@@ -243,7 +243,6 @@ public class Combat {
public final void addAttacker(final Card c, GameEntity defender) { public final void addAttacker(final Card c, GameEntity defender) {
addAttacker(c, defender, null); addAttacker(c, defender, null);
} }
public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) {
Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender); Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender);
if (attackersOfDefender == null) { if (attackersOfDefender == null) {
@@ -260,8 +259,7 @@ public class Combat {
if (band == null || !attackersOfDefender.contains(band)) { if (band == null || !attackersOfDefender.contains(band)) {
band = new AttackingBand(c); band = new AttackingBand(c);
attackersOfDefender.add(band); attackersOfDefender.add(band);
} } else {
else {
band.addAttacker(c); band.addAttacker(c);
} }
c.updateAttackingForView(); c.updateAttackingForView();