package forge; import forge.card.cardFactory.CardFactoryUtil; import java.util.ArrayList; /** * <p>ComputerUtil_Block2 class.</p> * * @author Forge * @version $Id: $ */ public class ComputerUtil_Block2 { /** Constant <code>attackers</code> */ private static CardList attackers = new CardList(); //all attackers /** Constant <code>attackersLeft</code> */ private static CardList attackersLeft = new CardList(); //keeps track of all currently unblocked attackers /** Constant <code>blockedButUnkilled</code> */ private static CardList blockedButUnkilled = new CardList(); //blocked attackers that currently wouldn't be destroyed /** Constant <code>blockersLeft</code> */ private static CardList blockersLeft = new CardList(); //keeps track of all unassigned blockers /** Constant <code>diff=0</code> */ private static int diff = 0; /** * <p>Getter for the field <code>attackers</code>.</p> * * @return a {@link forge.CardList} object. */ private static CardList getAttackers() { return attackers; } /** * <p>Setter for the field <code>attackers</code>.</p> * * @param cardList a {@link forge.CardList} object. */ private static void setAttackers(CardList cardList) { attackers = (cardList); } /** * <p>Getter for the field <code>attackersLeft</code>.</p> * * @return a {@link forge.CardList} object. */ private static CardList getAttackersLeft() { return attackersLeft; } /** * <p>Setter for the field <code>attackersLeft</code>.</p> * * @param cardList a {@link forge.CardList} object. */ private static void setAttackersLeft(CardList cardList) { attackersLeft = (cardList); } /** * <p>Getter for the field <code>blockedButUnkilled</code>.</p> * * @return a {@link forge.CardList} object. */ private static CardList getBlockedButUnkilled() { return blockedButUnkilled; } /** * <p>Setter for the field <code>blockedButUnkilled</code>.</p> * * @param cardList a {@link forge.CardList} object. */ private static void setBlockedButUnkilled(CardList cardList) { blockedButUnkilled = (cardList); } /** * <p>Getter for the field <code>blockersLeft</code>.</p> * * @return a {@link forge.CardList} object. */ private static CardList getBlockersLeft() { return blockersLeft; } /** * <p>Setter for the field <code>blockersLeft</code>.</p> * * @param cardList a {@link forge.CardList} object. */ private static void setBlockersLeft(CardList cardList) { blockersLeft = (cardList); } /** * <p>Getter for the field <code>diff</code>.</p> * * @return a int. */ private static int getDiff() { return diff; } /** * <p>Setter for the field <code>diff</code>.</p> * * @param diff a int. */ private static void setDiff(int diff) { ComputerUtil_Block2.diff = (diff); } //finds the creatures able to block the attacker /** * <p>getPossibleBlockers.</p> * * @param attacker a {@link forge.Card} object. * @param blockersLeft a {@link forge.CardList} object. * @param combat a {@link forge.Combat} object. * @return a {@link forge.CardList} object. */ private static CardList getPossibleBlockers(Card attacker, CardList blockersLeft, Combat combat) { CardList blockers = new CardList(); for (Card blocker : blockersLeft) { //if the blocker can block a creature with lure it can't block a creature without if (CombatUtil.canBlock(attacker, blocker, combat)) blockers.add(blocker); } return blockers; } //finds blockers that won't be destroyed /** * <p>getSafeBlockers.</p> * * @param attacker a {@link forge.Card} object. * @param blockersLeft a {@link forge.CardList} object. * @param combat a {@link forge.Combat} object. * @return a {@link forge.CardList} object. */ private static CardList getSafeBlockers(Card attacker, CardList blockersLeft, Combat combat) { CardList blockers = new CardList(); for (Card b : blockersLeft) { if (!CombatUtil.canDestroyBlocker(b, attacker, combat, false)) blockers.add(b); } return blockers; } //finds blockers that destroy the attacker /** * <p>getKillingBlockers.</p> * * @param attacker a {@link forge.Card} object. * @param blockersLeft a {@link forge.CardList} object. * @param combat a {@link forge.Combat} object. * @return a {@link forge.CardList} object. */ private static CardList getKillingBlockers(Card attacker, CardList blockersLeft, Combat combat) { CardList blockers = new CardList(); for (Card b : blockersLeft) { if (CombatUtil.canDestroyAttacker(attacker, b, combat, false)) blockers.add(b); } return blockers; } /** * <p>sortPotentialAttackers.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.CardList} object. */ public static CardList sortPotentialAttackers(Combat combat) { CardList[] attackerLists = combat.sortAttackerByDefender(); CardList sortedAttackers = new CardList(); ArrayList<Object> defenders = combat.getDefenders(); //Begin with the attackers that pose the biggest threat CardListUtil.sortByEvaluateCreature(attackerLists[0]); CardListUtil.sortAttack(attackerLists[0]); // If I don't have any planeswalkers than sorting doesn't really matter if (defenders.size() == 1) return attackerLists[0]; boolean bLifeInDanger = CombatUtil.lifeInDanger(combat); // TODO: Add creatures attacking Planeswalkers in order of which we want to protect // defend planeswalkers with more loyalty before planeswalkers with less loyalty // if planeswalker will be too difficult to defend don't even bother for (int i = 1; i < attackerLists.length; i++) { //Begin with the attackers that pose the biggest threat CardListUtil.sortAttack(attackerLists[i]); for (Card c : attackerLists[i]) sortedAttackers.add(c); } if (bLifeInDanger) { // add creatures attacking the Player to the front of the list for (Card c : attackerLists[0]) sortedAttackers.add(0, c); } else { // add creatures attacking the Player to the back of the list for (Card c : attackerLists[0]) sortedAttackers.add(c); } return sortedAttackers; } // ======================= block assignment functions ================================ // Good Blocks means a good trade or no trade /** * <p>makeGoodBlocks.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat makeGoodBlocks(Combat combat) { CardList currentAttackers = new CardList(getAttackersLeft().toArray()); for (Card attacker : getAttackersLeft()) { Card blocker = new Card(); CardList blockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); CardList safeBlockers = getSafeBlockers(attacker, blockers, combat); CardList killingBlockers; if (safeBlockers.size() > 0) { // 1.Blockers that can destroy the attacker but won't get destroyed killingBlockers = getKillingBlockers(attacker, safeBlockers, combat); if (killingBlockers.size() > 0) blocker = CardFactoryUtil.AI_getWorstCreature(killingBlockers); // 2.Blockers that won't get destroyed else { blocker = CardFactoryUtil.AI_getWorstCreature(safeBlockers); getBlockedButUnkilled().add(attacker); } } // no safe blockers else { killingBlockers = getKillingBlockers(attacker, blockers, combat); if (killingBlockers.size() > 0) { // 3.Blockers that can destroy the attacker and are worth less Card worst = CardFactoryUtil.AI_getWorstCreature(killingBlockers); if (CardFactoryUtil.evaluateCreature(worst) + getDiff() < CardFactoryUtil.evaluateCreature(attacker)) { blocker = worst; } } } if (blocker.getName() != "") { currentAttackers.remove(attacker); getBlockersLeft().remove(blocker); combat.addBlocker(attacker, blocker); } } setAttackersLeft(new CardList(currentAttackers.toArray())); return combat; } // Good Gang Blocks means a good trade or no trade /** * <p>makeGangBlocks.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat makeGangBlocks(Combat combat) { CardList currentAttackers = new CardList(getAttackersLeft().toArray()); currentAttackers = currentAttackers.getKeywordsDontContain("Rampage"); currentAttackers = currentAttackers.getKeywordsDontContain("CARDNAME can't be blocked by more than one creature."); CardList blockers; //Try to block an attacker without first strike with a gang of first strikers for (Card attacker : getAttackersLeft()) { if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) { blockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); CardList firstStrikeBlockers = new CardList(); CardList blockGang = new CardList(); for (int i = 0; i < blockers.size(); i++) if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) firstStrikeBlockers.add(blockers.get(i)); if (firstStrikeBlockers.size() > 1) { CardListUtil.sortAttack(firstStrikeBlockers); for (Card blocker : firstStrikeBlockers) { //if the total damage of the blockgang was not enough without but is enough with this blocker finish the blockgang if (CombatUtil.totalDamageOfBlockers(attacker, blockGang) < attacker.getKillDamage()) { blockGang.add(blocker); if (CombatUtil.totalDamageOfBlockers(attacker, blockGang) >= attacker.getKillDamage()) { currentAttackers.remove(attacker); for (Card b : blockGang) { getBlockersLeft().remove(b); combat.addBlocker(attacker, b); } } } } } } } setAttackersLeft(new CardList(currentAttackers.toArray())); currentAttackers = new CardList(getAttackersLeft().toArray()); //Try to block an attacker with two blockers of which only one will die for (final Card attacker : getAttackersLeft()) { blockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); CardList usableBlockers; CardList blockGang = new CardList(); int absorbedDamage = 0; //The amount of damage needed to kill the first blocker int currentValue = 0; //The value of the creatures in the blockgang //Try to add blockers that could be destroyed, but are worth less than the attacker //Don't use blockers without First Strike or Double Strike if attacker has it usableBlockers = blockers.filter(new CardListFilter() { public boolean addCard(Card c) { if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) && !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) return false; return CardFactoryUtil.evaluateCreature(c) + getDiff() < CardFactoryUtil.evaluateCreature(attacker); } }); if (usableBlockers.size() < 2) return combat; Card leader = CardFactoryUtil.AI_getBestCreature(usableBlockers); blockGang.add(leader); usableBlockers.remove(leader); absorbedDamage = leader.getEnoughDamageToKill(attacker.getNetCombatDamage(), attacker, true); currentValue = CardFactoryUtil.evaluateCreature(leader); for (Card blocker : usableBlockers) { //Add an additional blocker if the current blockers are not enough and the new one would deal the remaining damage int currentDamage = CombatUtil.totalDamageOfBlockers(attacker, blockGang); int additionalDamage = CombatUtil.dealsDamageAsBlocker(attacker, blocker); int absorbedDamage2 = blocker.getEnoughDamageToKill(attacker.getNetCombatDamage(), attacker, true); int addedValue = CardFactoryUtil.evaluateCreature(blocker); if (attacker.getKillDamage() > currentDamage && !(attacker.getKillDamage() > currentDamage + additionalDamage) //The attacker will be killed && (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage() //only one blocker can be killed || currentValue + addedValue - 50 <= CardFactoryUtil.evaluateCreature(attacker)) //attacker is worth more && CombatUtil.canBlock(attacker, blocker, combat)) {//this is needed for attackers that can't be blocked by more than 1 currentAttackers.remove(attacker); combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, leader); getBlockersLeft().remove(blocker); getBlockersLeft().remove(leader); break; } } } setAttackersLeft(new CardList(currentAttackers.toArray())); return combat; } // Bad Trade Blocks (should only be made if life is in danger) /** * <p>makeTradeBlocks.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat makeTradeBlocks(Combat combat) { CardList currentAttackers = new CardList(getAttackersLeft().toArray()); CardList killingBlockers; for (Card attacker : getAttackersLeft()) { killingBlockers = getKillingBlockers(attacker, getPossibleBlockers(attacker, getBlockersLeft(), combat), combat); if (killingBlockers.size() > 0 && CombatUtil.lifeInDanger(combat)) { Card blocker = CardFactoryUtil.AI_getWorstCreature(killingBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); getBlockersLeft().remove(blocker); } } setAttackersLeft(new CardList(currentAttackers.toArray())); return combat; } // Chump Blocks (should only be made if life is in danger) /** * <p>makeChumpBlocks.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat makeChumpBlocks(Combat combat) { CardList currentAttackers = new CardList(getAttackersLeft().toArray()); CardList chumpBlockers; for (Card attacker : getAttackersLeft()) { chumpBlockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); if (chumpBlockers.size() > 0 && CombatUtil.lifeInDanger(combat)) { Card blocker = CardFactoryUtil.AI_getWorstCreature(chumpBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); getBlockedButUnkilled().add(attacker); getBlockersLeft().remove(blocker); } } setAttackersLeft(new CardList(currentAttackers.toArray())); return combat; } //Reinforce blockers blocking attackers with trample (should only be made if life is in danger) /** * <p>reinforceBlockersAgainstTrample.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat reinforceBlockersAgainstTrample(Combat combat) { CardList chumpBlockers; CardList tramplingAttackers = getAttackers().getKeyword("Trample"); tramplingAttackers = tramplingAttackers.getKeywordsDontContain("Rampage"); //Don't make it worse tramplingAttackers = tramplingAttackers.getKeywordsDontContain("CARDNAME can't be blocked by more than one creature."); //TODO - should check here for a "rampage-like" trigger that replaced the keyword: // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." for (Card attacker : tramplingAttackers) { chumpBlockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); for (Card blocker : chumpBlockers) { //Add an additional blocker if the current blockers are not enough and the new one would suck some of the damage if (CombatUtil.getAttack(attacker) > CombatUtil.totalShieldDamage(attacker, combat.getBlockers(attacker)) && CombatUtil.shieldDamage(attacker, blocker) > 0 && CombatUtil.canBlock(attacker, blocker, combat) && CombatUtil.lifeInDanger(combat)) { combat.addBlocker(attacker, blocker); getBlockersLeft().remove(blocker); } } } return combat; } //Support blockers not destroying the attacker with more blockers to try to kill the attacker /** * <p>reinforceBlockersToKill.</p> * * @param combat a {@link forge.Combat} object. * @return a {@link forge.Combat} object. */ private static Combat reinforceBlockersToKill(Combat combat) { CardList safeBlockers; CardList blockers; CardList targetAttackers = getBlockedButUnkilled().getKeywordsDontContain("Rampage"); //Don't make it worse targetAttackers = targetAttackers.getKeywordsDontContain("CARDNAME can't be blocked by more than one creature."); //TODO - should check here for a "rampage-like" trigger that replaced the keyword: // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." for (Card attacker : targetAttackers) { blockers = getPossibleBlockers(attacker, getBlockersLeft(), combat); //Try to use safe blockers first safeBlockers = getSafeBlockers(attacker, blockers, combat); for (Card blocker : safeBlockers) { //Add an additional blocker if the current blockers are not enough and the new one would deal additional damage if (attacker.getKillDamage() > CombatUtil.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)) && CombatUtil.dealsDamageAsBlocker(attacker, blocker) > 0 && CombatUtil.canBlock(attacker, blocker, combat)) { combat.addBlocker(attacker, blocker); getBlockersLeft().remove(blocker); } blockers.remove(blocker); //Don't check them again next } //Try to add blockers that could be destroyed, but are worth less than the attacker //Don't use blockers without First Strike or Double Strike if attacker has it if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) { safeBlockers = blockers.getKeyword("First Strike"); safeBlockers.addAll(blockers.getKeyword("Double Strike")); } else safeBlockers = new CardList(blockers.toArray()); for (Card blocker : safeBlockers) { //Add an additional blocker if the current blockers are not enough and the new one would deal the remaining damage int currentDamage = CombatUtil.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)); int additionalDamage = CombatUtil.dealsDamageAsBlocker(attacker, blocker); if (attacker.getKillDamage() > currentDamage && !(attacker.getKillDamage() > currentDamage + additionalDamage) && CardFactoryUtil.evaluateCreature(blocker) + getDiff() < CardFactoryUtil.evaluateCreature(attacker) && CombatUtil.canBlock(attacker, blocker, combat)) { combat.addBlocker(attacker, blocker); getBlockersLeft().remove(blocker); } } } return combat; } /** * <p>resetBlockers.</p> * * @param combat a {@link forge.Combat} object. * @param possibleBlockers a {@link forge.CardList} object. * @return a {@link forge.Combat} object. */ private static Combat resetBlockers(Combat combat, CardList possibleBlockers) { CardList oldBlockers = combat.getAllBlockers(); for (Card blocker : oldBlockers) { combat.removeFromCombat(blocker); } setAttackersLeft(new CardList(getAttackers().toArray())); //keeps track of all currently unblocked attackers setBlockersLeft(new CardList(possibleBlockers.toArray())); //keeps track of all unassigned blockers setBlockedButUnkilled(new CardList()); //keeps track of all blocked attackers that currently wouldn't be destroyed return combat; } //Main function /** * <p>getBlockers.</p> * * @param originalCombat a {@link forge.Combat} object. * @param possibleBlockers a {@link forge.CardList} object. * @return a {@link forge.Combat} object. */ static public Combat getBlockers(Combat originalCombat, CardList possibleBlockers) { Combat combat = originalCombat; setAttackers(sortPotentialAttackers(combat)); if (getAttackers().size() == 0) return combat; setAttackersLeft(new CardList(getAttackers().toArray())); //keeps track of all currently unblocked attackers setBlockersLeft(new CardList(possibleBlockers.toArray())); //keeps track of all unassigned blockers setBlockedButUnkilled(new CardList()); //keeps track of all blocked attackers that currently wouldn't be destroyed CardList blockers; CardList chumpBlockers; setDiff(AllZone.getComputerPlayer().getLife() * 2 - 5); //This is the minimal gain for an unnecessary trade // remove all attackers that can't be blocked anyway for (Card a : getAttackers()) { if (!CombatUtil.canBeBlocked(a)) { getAttackersLeft().remove(a); } } // remove all blockers that can't block anyway for (Card b : possibleBlockers) { if (!CombatUtil.canBlock(b, combat)) getBlockersLeft().remove(b); } if (getAttackersLeft().size() == 0) return combat; //Begin with the weakest blockers CardListUtil.sortAttackLowFirst(getBlockersLeft()); //== 1. choose best blocks first == combat = makeGoodBlocks(combat); combat = makeGangBlocks(combat); if (CombatUtil.lifeInDanger(combat)) combat = makeTradeBlocks(combat); //choose necessary trade blocks if life is in danger if (CombatUtil.lifeInDanger(combat)) combat = makeChumpBlocks(combat); //choose necessary chump blocks if life is still in danger //Reinforce blockers blocking attackers with trample if life is still in danger if (CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersAgainstTrample(combat); //Support blockers not destroying the attacker with more blockers to try to kill the attacker if (!CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersToKill(combat); //== 2. If the AI life would still be in danger make a safer approach == if (CombatUtil.lifeInDanger(combat)) { combat = resetBlockers(combat, possibleBlockers); // reset every block assignment combat = makeTradeBlocks(combat); //choose necessary trade blocks if life is in danger combat = makeGoodBlocks(combat); if (CombatUtil.lifeInDanger(combat)) combat = makeChumpBlocks(combat); //choose necessary chump blocks if life is still in danger //Reinforce blockers blocking attackers with trample if life is still in danger if (CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersAgainstTrample(combat); combat = makeGangBlocks(combat); combat = reinforceBlockersToKill(combat); } //== 3. If the AI life would be in serious danger make an even safer approach == if (CombatUtil.lifeInSeriousDanger(combat)) { combat = resetBlockers(combat, possibleBlockers); // reset every block assignment combat = makeChumpBlocks(combat); //choose chump blocks if (CombatUtil.lifeInDanger(combat)) combat = makeTradeBlocks(combat); //choose necessary trade blocks if life is in danger if (!CombatUtil.lifeInDanger(combat)) combat = makeGoodBlocks(combat); //Reinforce blockers blocking attackers with trample if life is still in danger if (CombatUtil.lifeInDanger(combat)) combat = reinforceBlockersAgainstTrample(combat); combat = makeGangBlocks(combat); //Support blockers not destroying the attacker with more blockers to try to kill the attacker combat = reinforceBlockersToKill(combat); } // assign blockers that have to block chumpBlockers = getBlockersLeft().getKeyword("CARDNAME blocks each turn if able."); // if an attacker with lure attacks - all that can block for (Card blocker : getBlockersLeft()) { if (CombatUtil.canBlockAnAttackerWithLure(blocker, combat)) chumpBlockers.add(blocker); } if (!chumpBlockers.isEmpty()) { getAttackers().shuffle(); for (Card attacker : getAttackers()) { blockers = getPossibleBlockers(attacker, chumpBlockers, combat); for (Card blocker : blockers) { if (CombatUtil.canBlock(attacker, blocker, combat)) { combat.addBlocker(attacker, blocker); getBlockersLeft().remove(blocker); } } } } return combat; } }