Combat instance lifespan limited to Combat phase (for the rest combat = null, checks will return 'not attacking', 'not blocking'), the very object is stored in PhaseHandler

Card: removed methods to test if card is attacking/blocking, because these properties are related to combat, not the card itself.
AiAttackController - no longer creates Combat. Instead it uses a provided instance and fills attackers there
ComputerUtilBlock.java became non-static class AiBlockController, also modifies the provided Combat instance
This commit is contained in:
Maxmtg
2013-06-24 09:33:19 +00:00
parent 394a322fbe
commit 74059bda82
54 changed files with 963 additions and 1541 deletions

2
.gitattributes vendored
View File

@@ -14465,11 +14465,11 @@ src/main/java/forge/game/Match.java -text
src/main/java/forge/game/PlanarDice.java -text src/main/java/forge/game/PlanarDice.java -text
src/main/java/forge/game/RegisteredPlayer.java -text src/main/java/forge/game/RegisteredPlayer.java -text
src/main/java/forge/game/ai/AiAttackController.java svneol=native#text/plain src/main/java/forge/game/ai/AiAttackController.java svneol=native#text/plain
src/main/java/forge/game/ai/AiBlockController.java svneol=native#text/plain
src/main/java/forge/game/ai/AiController.java svneol=native#text/plain src/main/java/forge/game/ai/AiController.java svneol=native#text/plain
src/main/java/forge/game/ai/AiProfileUtil.java -text src/main/java/forge/game/ai/AiProfileUtil.java -text
src/main/java/forge/game/ai/AiProps.java -text src/main/java/forge/game/ai/AiProps.java -text
src/main/java/forge/game/ai/ComputerUtil.java svneol=native#text/plain src/main/java/forge/game/ai/ComputerUtil.java svneol=native#text/plain
src/main/java/forge/game/ai/ComputerUtilBlock.java svneol=native#text/plain
src/main/java/forge/game/ai/ComputerUtilCard.java -text src/main/java/forge/game/ai/ComputerUtilCard.java -text
src/main/java/forge/game/ai/ComputerUtilCombat.java -text src/main/java/forge/game/ai/ComputerUtilCombat.java -text
src/main/java/forge/game/ai/ComputerUtilCost.java -text src/main/java/forge/game/ai/ComputerUtilCost.java -text

View File

@@ -5357,6 +5357,8 @@ public class Card extends GameEntity implements Comparable<Card> {
*/ */
@Override @Override
public boolean hasProperty(final String property, final Player sourceController, final Card source) { public boolean hasProperty(final String property, final Player sourceController, final Card source) {
final Game game = getGame();
final Combat combat = game.getCombat();
// by name can also have color names, so needs to happen before colors. // by name can also have color names, so needs to happen before colors.
if (property.startsWith("named")) { if (property.startsWith("named")) {
if (!this.getName().equals(property.substring(5))) { if (!this.getName().equals(property.substring(5))) {
@@ -5438,10 +5440,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (property.startsWith("DefenderCtrl")) { } else if (property.startsWith("DefenderCtrl")) {
if (!getGame().getPhaseHandler().inCombat()) { if (!game.getPhaseHandler().inCombat()) {
return false; return false;
} }
if (!getGame().getCombat().getDefendingPlayerRelatedTo(source).contains(this.getController())) { if (getGame().getCombat().getDefendingPlayerRelatedTo(source) != this.getController()) {
return false; return false;
} }
} else if (property.startsWith("EnchantedPlayerCtrl")) { } else if (property.startsWith("EnchantedPlayerCtrl")) {
@@ -5455,7 +5457,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} else if (property.startsWith("RememberedPlayerCtrl")) { } else if (property.startsWith("RememberedPlayerCtrl")) {
if (source.getRemembered().isEmpty()) { if (source.getRemembered().isEmpty()) {
final Card newCard = getGame().getCardState(source); final Card newCard = game.getCardState(source);
for (final Object o : newCard.getRemembered()) { for (final Object o : newCard.getRemembered()) {
if (o instanceof Player) { if (o instanceof Player) {
if (!this.getController().equals(o)) { if (!this.getController().equals(o)) {
@@ -5501,11 +5503,11 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
} else if (property.startsWith("ActivePlayerCtrl")) { } else if (property.startsWith("ActivePlayerCtrl")) {
if (!getGame().getPhaseHandler().isPlayerTurn(this.getController())) { if (!game.getPhaseHandler().isPlayerTurn(this.getController())) {
return false; return false;
} }
} else if (property.startsWith("NonActivePlayerCtrl")) { } else if (property.startsWith("NonActivePlayerCtrl")) {
if (getGame().getPhaseHandler().isPlayerTurn(this.getController())) { if (game.getPhaseHandler().isPlayerTurn(this.getController())) {
return false; return false;
} }
} else if (property.startsWith("YouOwn")) { } else if (property.startsWith("YouOwn")) {
@@ -5845,11 +5847,11 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (restriction.equals("MostProminentColor")) { } else if (restriction.equals("MostProminentColor")) {
byte mask = CardFactoryUtil.getMostProminentColors(getGame().getCardsIn(ZoneType.Battlefield)); byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield));
if( !CardUtil.getColors(this).hasAnyColor(mask)) if( !CardUtil.getColors(this).hasAnyColor(mask))
return false; return false;
} else if (restriction.equals("LastCastThisTurn")) { } else if (restriction.equals("LastCastThisTurn")) {
final List<Card> c = source.getGame().getStack().getCardsCastThisTurn(); final List<Card> c = game.getStack().getCardsCastThisTurn();
if (c.isEmpty() || !this.sharesColorWith(c.get(c.size() - 1))) { if (c.isEmpty() || !this.sharesColorWith(c.get(c.size() - 1))) {
return false; return false;
} }
@@ -5872,7 +5874,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
String color = props[1]; String color = props[1];
byte mostProm = CardFactoryUtil.getMostProminentColors(getGame().getCardsIn(ZoneType.Battlefield)); byte mostProm = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield));
return ColorSet.fromMask(mostProm).hasAnyColor(MagicColor.fromName(color)); return ColorSet.fromMask(mostProm).hasAnyColor(MagicColor.fromName(color));
} else if (property.startsWith("notSharesColorWith")) { } else if (property.startsWith("notSharesColorWith")) {
if (property.equals("notSharesColorWith")) { if (property.equals("notSharesColorWith")) {
@@ -5971,7 +5973,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} else if (restriction.equals("EachTopLibrary")) { } else if (restriction.equals("EachTopLibrary")) {
final List<Card> list = new ArrayList<Card>(); final List<Card> list = new ArrayList<Card>();
for (Player p : getGame().getPlayers()) { for (Player p : game.getPlayers()) {
final Card top = p.getCardsIn(ZoneType.Library).get(0); final Card top = p.getCardsIn(ZoneType.Library).get(0);
list.add(top); list.add(top);
} }
@@ -5998,14 +6000,14 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
return false; return false;
} else if (restriction.equals(ZoneType.Graveyard.toString())) { } else if (restriction.equals(ZoneType.Graveyard.toString())) {
for (final Card card : getGame().getCardsIn(ZoneType.Graveyard)) { for (final Card card : game.getCardsIn(ZoneType.Graveyard)) {
if (this.getName().equals(card.getName())) { if (this.getName().equals(card.getName())) {
return true; return true;
} }
} }
return false; return false;
} else if (restriction.equals(ZoneType.Battlefield.toString())) { } else if (restriction.equals(ZoneType.Battlefield.toString())) {
for (final Card card : getGame().getCardsIn(ZoneType.Battlefield)) { for (final Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (this.getName().equals(card.getName())) { if (this.getName().equals(card.getName())) {
return true; return true;
} }
@@ -6050,7 +6052,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
return false; return false;
} else if (restriction.equals("NonToken")) { } else if (restriction.equals("NonToken")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
Presets.NON_TOKEN); Presets.NON_TOKEN);
for (final Card card : list) { for (final Card card : list) {
if (this.getName().equals(card.getName())) { if (this.getName().equals(card.getName())) {
@@ -6208,11 +6210,11 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (property.startsWith("enteredBattlefieldThisTurn")) { } else if (property.startsWith("enteredBattlefieldThisTurn")) {
if (!(this.getTurnInZone() == getGame().getPhaseHandler().getTurn())) { if (!(this.getTurnInZone() == game.getPhaseHandler().getTurn())) {
return false; return false;
} }
} else if (property.startsWith("notEnteredBattlefieldThisTurn")) { } else if (property.startsWith("notEnteredBattlefieldThisTurn")) {
if (this.getTurnInZone() == getGame().getPhaseHandler().getTurn()) { if (this.getTurnInZone() == game.getPhaseHandler().getTurn()) {
return false; return false;
} }
} else if (property.startsWith("firstTurnControlled")) { } else if (property.startsWith("firstTurnControlled")) {
@@ -6288,7 +6290,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (property.startsWith("greatestPower")) { } else if (property.startsWith("greatestPower")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
for (final Card crd : list) { for (final Card crd : list) {
if (crd.getNetAttack() > this.getNetAttack()) { if (crd.getNetAttack() > this.getNetAttack()) {
return false; return false;
@@ -6302,21 +6304,21 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
} else if (property.startsWith("leastPower")) { } else if (property.startsWith("leastPower")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
for (final Card crd : list) { for (final Card crd : list) {
if (crd.getNetAttack() < this.getNetAttack()) { if (crd.getNetAttack() < this.getNetAttack()) {
return false; return false;
} }
} }
} else if (property.startsWith("leastToughness")) { } else if (property.startsWith("leastToughness")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
for (final Card crd : list) { for (final Card crd : list) {
if (crd.getNetDefense() < this.getNetDefense()) { if (crd.getNetDefense() < this.getNetDefense()) {
return false; return false;
} }
} }
} else if (property.startsWith("greatestCMC")) { } else if (property.startsWith("greatestCMC")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); final List<Card> list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
for (final Card crd : list) { for (final Card crd : list) {
if (crd.isSplitCard()) { if (crd.isSplitCard()) {
if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) { if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) {
@@ -6332,7 +6334,7 @@ public class Card extends GameEntity implements Comparable<Card> {
List<Card> list = new ArrayList<Card>(); List<Card> list = new ArrayList<Card>();
for (final Object o : source.getRemembered()) { for (final Object o : source.getRemembered()) {
if (o instanceof Card) { if (o instanceof Card) {
list.add(getGame().getCardState((Card) o)); list.add(game.getCardState((Card) o));
} }
} }
if (!list.contains(this)) { if (!list.contains(this)) {
@@ -6343,7 +6345,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (property.startsWith("lowestCMC")) { } else if (property.startsWith("lowestCMC")) {
final List<Card> list = getGame().getCardsIn(ZoneType.Battlefield); final List<Card> list = game.getCardsIn(ZoneType.Battlefield);
for (final Card crd : list) { for (final Card crd : list) {
if (!crd.isLand() && !crd.isImmutable()) { if (!crd.isLand() && !crd.isImmutable()) {
if (crd.isSplitCard()) { if (crd.isSplitCard()) {
@@ -6395,7 +6397,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return false; return false;
} }
} else if (property.startsWith("suspended")) { } else if (property.startsWith("suspended")) {
if (!this.hasSuspend() || !getGame().isCardExiled(this) if (!this.hasSuspend() || !game.isCardExiled(this)
|| !(this.getCounters(CounterType.getType("TIME")) >= 1)) { || !(this.getCounters(CounterType.getType("TIME")) >= 1)) {
return false; return false;
} }
@@ -6478,62 +6480,56 @@ public class Card extends GameEntity implements Comparable<Card> {
if (!Expressions.compare(actualnumber, comparator, number)) { if (!Expressions.compare(actualnumber, comparator, number)) {
return false; return false;
} }
} else if (property.startsWith("attacking")) {
if (property.equals("attacking")) {
if (!this.isAttacking()) {
return false;
}
} else if (property.equals("attackingYou")) {
if (!this.isAttacking(sourceController)) {
return false;
}
} }
// These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM)
else if (property.startsWith("attacking")) {
if ( null == combat ) return false;
if (property.equals("attacking")) return combat.isAttacking(this);
if (property.equals("attackingYou")) return combat.isAttacking(this, sourceController);
} else if (property.startsWith("notattacking")) { } else if (property.startsWith("notattacking")) {
if (this.isAttacking()) { return null == combat || !combat.isAttacking(this);
return false;
}
} else if (property.equals("attackedBySourceThisCombat")) { } else if (property.equals("attackedBySourceThisCombat")) {
final GameEntity defender = getGame().getCombat().getDefenderByAttacker(source); final GameEntity defender = game.getCombat().getDefenderByAttacker(source);
if (defender instanceof Card) { if (defender instanceof Card) {
if (!this.equals((Card) defender)) { if (!this.equals((Card) defender)) {
return false; return false;
} }
} }
} else if (property.equals("blocking")) { } else if (property.startsWith("blocking")) {
if (!this.isBlocking()) { if ( null == combat ) return false;
return false; String what = property.substring("blocking".length());
}
} else if (property.startsWith("blockingSource")) { if( StringUtils.isEmpty(what)) return combat.isBlocking(this);
if (!this.isBlocking(source)) { if (what.startsWith("Source")) return combat.isBlocking(this, source) ;
return false; if (what.startsWith("CreatureYouCtrl")) {
} for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES))
} else if (property.startsWith("blockingCreatureYouCtrl")) { if (combat.isBlocking(this, c))
final List<Card> list = CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
for (final Card c : list) {
if (this.isBlocking(c)) {
return true; return true;
}
return false; return false;
} }
} else if (property.startsWith("blockingRemembered")) { if (what.startsWith("Remembered")) {
for (final Object o : source.getRemembered()) { for (final Object o : source.getRemembered()) {
if (o instanceof Card) { if (o instanceof Card && combat.isBlocking(this, (Card) o)) {
final Card card = (Card) o;
if (this.isBlocking(card)) {
return true; return true;
} }
} }
}
return false; return false;
}
} else if (property.startsWith("notblocking")) {
return null == combat || !combat.isBlocking(this);
}
// Nex predicates refer to past combat and don't need a reference to actual combat
else if (property.equals("blocked")) {
return null != combat && combat.isBlocked(this);
} else if (property.startsWith("blockedBySource")) {
return null != combat && combat.isBlocking(source, this);
} else if (property.startsWith("isBlockedByRemembered")) { } else if (property.startsWith("isBlockedByRemembered")) {
if ( null == combat ) return false;
for (final Object o : source.getRemembered()) { for (final Object o : source.getRemembered()) {
if (o instanceof Card) { if (o instanceof Card && combat.isBlocking((Card) o, this)) {
final Card crd = (Card) o;
if (this.isBlockedBy(crd)) {
return true; return true;
} }
} }
}
return false; return false;
} else if (property.startsWith("blockedRemembered")) { } else if (property.startsWith("blockedRemembered")) {
Card rememberedcard; Card rememberedcard;
@@ -6555,20 +6551,8 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
} }
} else if (property.startsWith("notblocking")) {
if (this.isBlocking()) {
return false;
}
} else if (property.equals("blocked")) {
if (!this.isBlocked()) {
return false;
}
} else if (property.startsWith("blockedBySource")) {
if (!this.isBlockedBy(source)) {
return false;
}
} else if (property.startsWith("unblocked")) { } else if (property.startsWith("unblocked")) {
if (!getGame().getCombat().isUnblocked(this)) { if (!game.getCombat().isUnblocked(this)) {
return false; return false;
} }
} else if (property.equals("attackersBandedWith")) { } else if (property.equals("attackersBandedWith")) {
@@ -6576,9 +6560,7 @@ public class Card extends GameEntity implements Comparable<Card> {
// You don't band with yourself // You don't band with yourself
return false; return false;
} }
AttackingBand band = combat == null ? null : combat.getBandOfAttacker(source);
Combat combat = getGame().getCombat();
AttackingBand band = combat.getBandByAttacker(source);
if (band == null || !band.getAttackers().contains(this)) { if (band == null || !band.getAttackers().contains(this)) {
return false; return false;
} }
@@ -6889,82 +6871,6 @@ public class Card extends GameEntity implements Comparable<Card> {
this.usedToPayCost = b; this.usedToPayCost = b;
} }
/**
* <p>
* isAttacking.
* </p>
*
* @return a boolean.
*/
public final boolean isAttacking() {
return getGame().getCombat().isAttacking(this);
}
/**
* <p>
* isAttacking.
* </p>
* @param ge the GameEntity to check
* @return a boolean.
*/
public final boolean isAttacking(GameEntity ge) {
Combat combat = getGame().getCombat();
GameEntity defender = combat.getDefenderByAttacker(this);
if (!combat.isAttacking(this) || defender == null) {
return false;
}
return defender.equals(ge);
}
/**
* <p>
* isBlocking.
* </p>
*
* @return a boolean.
*/
public final boolean isBlocking() {
final List<Card> blockers = getGame().getCombat().getAllBlockers();
return blockers.contains(this);
}
/**
* <p>
* isBlocked.
* </p>
*
* @return a boolean.
*/
public final boolean isBlocked() {
return getGame().getCombat().isBlocked(this);
}
/**
* <p>
* isBlocking.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean isBlocking(final Card attacker) {
return getGame().getCombat().getAttackersBlockedBy(this).contains(attacker);
}
/**
* <p>
* isBlockedBy.
* </p>
*
* @param blocker
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean isBlockedBy(final Card blocker) {
return getGame().getCombat().getAttackersBlockedBy(blocker).contains(this);
}
// ///////////////////////// // /////////////////////////
// //
// Damage code // Damage code

View File

@@ -896,7 +896,7 @@ public class AbilityUtils {
players.add(p); players.add(p);
} }
} else if (defined.equals("DefendingPlayer")) { } else if (defined.equals("DefendingPlayer")) {
players.addAll(game.getCombat().getDefendingPlayerRelatedTo(card)); players.add(game.getCombat().getDefendingPlayerRelatedTo(card));
} else if (defined.equals("ChosenPlayer")) { } else if (defined.equals("ChosenPlayer")) {
final Player p = card.getChosenPlayer(); final Player p = card.getChosenPlayer();
if (!players.contains(p)) { if (!players.contains(p)) {

View File

@@ -60,9 +60,7 @@ public class AnimateAi extends SpellAbilityAi {
// don't use instant speed animate abilities outside humans // don't use instant speed animate abilities outside humans
// Combat_Declare_Attackers_InstantAbility step // Combat_Declare_Attackers_InstantAbility step
if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS) if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || game.getCombat().getAttackers().isEmpty()) {
|| (game.getCombat().getAttackers().isEmpty()))
&& game.getPhaseHandler().isPlayerTurn(opponent)) {
return false; return false;
} }

View File

@@ -817,7 +817,12 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate<Card>() { prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return containsUsefulKeyword(keywords, c, sa, pow); for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
}
return false;
} }
}); });
} }
@@ -991,25 +996,6 @@ public class AttachAi extends SpellAbilityAi {
return c; return c;
} }
/**
* Contains useful keyword.
*
* @param keywords
* the keywords
* @param card
* the card
* @param sa SpellAbility
* @return true, if successful
*/
private static boolean containsUsefulKeyword(final ArrayList<String> keywords, final Card card, final SpellAbility sa, final int powerBonus) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, card, sa, powerBonus)) {
return true;
}
}
return false;
}
/** /**
* Contains useful curse keyword. * Contains useful curse keyword.
* *
@@ -1040,7 +1026,10 @@ public class AttachAi extends SpellAbilityAi {
* @return true, if is useful keyword * @return true, if is useful keyword
*/ */
private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) { private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) {
final PhaseHandler ph = sa.getActivatingPlayer().getGame().getPhaseHandler(); final Player ai = sa.getActivatingPlayer();
final Player opponent = ai.getOpponent();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) { if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false; return false;
} }
@@ -1053,7 +1042,7 @@ public class AttachAi extends SpellAbilityAi {
if (evasive) { if (evasive) {
if (card.getNetCombatDamage() + powerBonus <= 0 if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card) || !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) { || !CombatUtil.canBeBlocked(card, opponent)) {
return false; return false;
} }
} else if (keyword.equals("Haste")) { } else if (keyword.equals("Haste")) {
@@ -1068,7 +1057,7 @@ public class AttachAi extends SpellAbilityAi {
return true; return true;
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) { } else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
if (card.getNetCombatDamage() + powerBonus <= 0 if (card.getNetCombatDamage() + powerBonus <= 0
|| ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card)) || ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true))) { && !CombatUtil.canBlock(card, true))) {
return false; return false;
} }
@@ -1084,17 +1073,17 @@ public class AttachAi extends SpellAbilityAi {
} else if (keyword.startsWith("Flanking")) { } else if (keyword.startsWith("Flanking")) {
if (card.getNetCombatDamage() + powerBonus <= 0 if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card) || !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) { || !CombatUtil.canBeBlocked(card, opponent)) {
return false; return false;
} }
} else if (keyword.startsWith("Bushido")) { } else if (keyword.startsWith("Bushido")) {
if ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card)) if ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true)) { && !CombatUtil.canBlock(card, true)) {
return false; return false;
} }
} else if (keyword.equals("Trample")) { } else if (keyword.equals("Trample")) {
if (card.getNetCombatDamage() + powerBonus <= 1 if (card.getNetCombatDamage() + powerBonus <= 1
|| !CombatUtil.canBeBlocked(card) || !CombatUtil.canBeBlocked(card, opponent)
|| !CombatUtil.canAttackNextTurn(card)) { || !CombatUtil.canAttackNextTurn(card)) {
return false; return false;
} }

View File

@@ -32,7 +32,7 @@ import forge.card.trigger.TriggerType;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilBlock; import forge.game.ai.AiBlockController;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
@@ -500,13 +500,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/ */
private static Card chooseCreature(final Player ai, List<Card> list) { private static Card chooseCreature(final Player ai, List<Card> list) {
// Creating a new combat for testing purposes. // Creating a new combat for testing purposes.
Combat combat = new Combat(); Combat combat = new Combat(ai.getOpponent());
combat.initiatePossibleDefenders(ai); for (Card att : ai.getOpponent().getCreaturesInPlay()) {
List<Card> attackers = ai.getOpponent().getCreaturesInPlay();
for (Card att : attackers) {
combat.addAttacker(att, ai); combat.addAttacker(att, ai);
} }
combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); AiBlockController block = new AiBlockController(ai);
block.assignBlockers(combat);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
// need something AI can cast now // need something AI can cast now
@@ -523,10 +522,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
// ************************************************************************************* // *************************************************************************************
// **************** Known Origin (Battlefield/Graveyard/Exile) // **************** Known Origin (Battlefield/Graveyard/Exile) *************************
// ************************* // ******* Known origin cards are chosen during casting of the spell (target) **********
// ******* Known origin cards are chosen during casting of the spell
// (target) **********
// ************************************************************************************* // *************************************************************************************
/** /**
@@ -690,6 +687,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
final AbilitySub abSub = sa.getSubAbility(); final AbilitySub abSub = sa.getSubAbility();
ApiType subApi = null; ApiType subApi = null;
@@ -702,7 +700,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
sa.resetTargets(); sa.resetTargets();
List<Card> list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source); List<Card> list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, source);
list = CardLists.getTargetableCards(list, sa); list = CardLists.getTargetableCards(list, sa);
if (sa.hasParam("AITgts")) { if (sa.hasParam("AITgts")) {
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source); list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
@@ -730,7 +728,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// check stack for something on the stack that will kill // check stack for something on the stack that will kill
// anything i control // anything i control
if (!ai.getGame().getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
final List<ITargetable> objects = ComputerUtil.predictThreatenedObjects(ai, sa); final List<ITargetable> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
final List<Card> threatenedTargets = new ArrayList<Card>(); final List<Card> threatenedTargets = new ArrayList<Card>();
@@ -748,12 +746,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
} }
// Save combatants // Save combatants
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
final List<Card> combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES); final List<Card> combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants); CardLists.sortByEvaluateCreature(combatants);
for (final Card c : combatants) { for (final Card c : combatants) {
if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c) && c.getOwner() == ai && !c.isToken()) { if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && c.getOwner() == ai && !c.isToken()) {
sa.getTargets().add(c); sa.getTargets().add(c);
return true; return true;
} }

View File

@@ -13,6 +13,7 @@ import forge.card.spellability.TargetRestrictions;
import forge.game.Game; import forge.game.Game;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -64,19 +65,18 @@ public class ChooseCardAi extends SpellAbilityAi {
} else if (sa.getParam("AILogic").equals("Never")) { } else if (sa.getParam("AILogic").equals("Never")) {
return false; return false;
} else if (sa.getParam("AILogic").equals("NeedsPrevention")) { } else if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (!game.getPhaseHandler().getPhase() .equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false; return false;
} }
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() { choices = CardLists.filter(choices, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false; return false;
} }
if (host.getName().equals("Forcefield")) { int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 1; return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0;
} }
}); });
if (choices.isEmpty()) { if (choices.isEmpty()) {
@@ -122,17 +122,16 @@ public class ChooseCardAi extends SpellAbilityAi {
} }
choice = ComputerUtilCard.getBestAI(options); choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("NeedsPrevention")) { } else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
List<Card> better = CardLists.filter(options, new Predicate<Card>() { List<Card> better = CardLists.filter(options, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
final Game game = ai.getGame(); if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) {
return false; return false;
} }
if (host.getName().equals("Forcefield")) { int ref = host.getName().equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 1; return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0;
} }
}); });
if (!better.isEmpty()) { if (!better.isEmpty()) {

View File

@@ -16,6 +16,7 @@ import forge.card.spellability.TargetRestrictions;
import forge.game.Game; import forge.game.Game;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -100,13 +101,14 @@ public class ChooseSourceAi extends SpellAbilityAi {
if (sa.hasParam("Choices")) { if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
} }
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() { choices = CardLists.filter(choices, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false; return false;
} }
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
} }
}); });
if (choices.isEmpty()) { if (choices.isEmpty()) {

View File

@@ -46,9 +46,7 @@ public class CloneAi extends SpellAbilityAi {
// don't use instant speed clone abilities outside humans // don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step // Combat_Declare_Attackers_InstantAbility step
if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) && !phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|| game.getCombat().getAttackers().isEmpty())
&& !phase.isPlayerTurn(ai)) {
return false; return false;
} }

View File

@@ -17,6 +17,7 @@ import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -28,6 +29,7 @@ public class DamagePreventAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getSourceCard(); final Card hostCard = sa.getSourceCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false; boolean chance = false;
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
@@ -69,14 +71,13 @@ public class DamagePreventAi extends SpellAbilityAi {
boolean flag = false; boolean flag = false;
for (final Object o : objects) { for (final Object o : objects) {
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c);
} else if (o instanceof Player) { } else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn // Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o; final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) { if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, game.getCombat()) && sa flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, game.getCombat()))); .isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
} }
} }
} }
@@ -120,8 +121,8 @@ public class DamagePreventAi extends SpellAbilityAi {
} // Protect combatants } // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, game.getCombat()) if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || sa.isAbility()) && (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility())
&& game.getPhaseHandler().isPlayerTurn(ai.getOpponent())) { && game.getPhaseHandler().isPlayerTurn(ai.getOpponent())) {
sa.getTargets().add(ai); sa.getTargets().add(ai);
chance = true; chance = true;
@@ -138,7 +139,7 @@ public class DamagePreventAi extends SpellAbilityAi {
CardLists.sortByEvaluateCreature(combatants); CardLists.sortByEvaluateCreature(combatants);
for (final Card c : combatants) { for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c); sa.getTargets().add(c);
chance = true; chance = true;
break; break;
@@ -177,8 +178,7 @@ public class DamagePreventAi extends SpellAbilityAi {
* a boolean. * a boolean.
* @return a boolean. * @return a boolean.
*/ */
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); sa.resetTargets();
// filter AIs battlefield by what I can target // filter AIs battlefield by what I can target
@@ -199,8 +199,9 @@ public class DamagePreventAi extends SpellAbilityAi {
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants); CardLists.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) { for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c); sa.getTargets().add(c);
return true; return true;
} }

View File

@@ -5,6 +5,8 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.Card; import forge.Card;
import forge.CardLists; import forge.CardLists;
@@ -14,8 +16,10 @@ import forge.card.cost.Cost;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellAbilityRestriction; import forge.card.spellability.SpellAbilityRestriction;
import forge.card.spellability.TargetRestrictions; import forge.card.spellability.TargetRestrictions;
import forge.game.Game;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -30,6 +34,7 @@ public class DebuffAi extends SpellAbilityAi {
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
// if there is no target and host card isn't in play, don't activate // if there is no target and host card isn't in play, don't activate
final Card source = sa.getSourceCard(); final Card source = sa.getSourceCard();
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) { if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
return false; return false;
} }
@@ -50,12 +55,12 @@ public class DebuffAi extends SpellAbilityAi {
} }
final SpellAbilityRestriction restrict = sa.getRestrictions(); final SpellAbilityRestriction restrict = sa.getRestrictions();
final PhaseHandler ph = ai.getGame().getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions // Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !ai.getGame().getStack().isEmpty()) { || !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the // Instant-speed pumps should not be cast outside of combat when the
// stack is empty // stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) { if (!SpellAbilityAi.isSorcerySpeed(sa)) {
@@ -70,29 +75,28 @@ public class DebuffAi extends SpellAbilityAi {
return false; return false;
} }
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
if (!cards.isEmpty()) {
cards = CardLists.filter(cards, new Predicate<Card>() { final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if ((c.getController().equals(sa.getActivatingPlayer())) || (!c.isBlocking() && !c.isAttacking())) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false; return false;
} }
// don't add duplicate negative keywords // don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & "))); return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
} }
}); });
}
if (cards.isEmpty()) {
return false;
}
} else { } else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>(), false); return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
} }
return true;
} }
@Override @Override
@@ -101,7 +105,7 @@ public class DebuffAi extends SpellAbilityAi {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
// here? // here?
} else { } else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>(), false); return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
} }
return true; return true;
@@ -130,7 +134,7 @@ public class DebuffAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets(); sa.resetTargets();
List<Card> list = getCurseCreatures(ai, sa, kws); List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard()); list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
// several uses here: // several uses here:

View File

@@ -71,10 +71,10 @@ public final class EncodeAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean) * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/ */
@Override @Override
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) { public Card chooseSingleCard(final Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
Card choice = null; Card choice = null;
final String logic = sa.getParam("AILogic"); // final String logic = sa.getParam("AILogic");
if (logic == null) { // if (logic == null) {
final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() { final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
@@ -84,7 +84,7 @@ public final class EncodeAi extends SpellAbilityAi {
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() { final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return !CombatUtil.canBeBlocked(c); return !CombatUtil.canBeBlocked(c, ai.getOpponent());
} }
}); });
if (!unblockables.isEmpty()) { if (!unblockables.isEmpty()) {
@@ -94,7 +94,7 @@ public final class EncodeAi extends SpellAbilityAi {
} else { } else {
choice = ComputerUtilCard.getBestAI(options); choice = ComputerUtilCard.getBestAI(options);
} }
} // }
return choice; return choice;
} }
} }

View File

@@ -63,8 +63,7 @@ public class LifeGainAi extends SpellAbilityAi {
return false; return false;
} }
boolean lifeCritical = life <= 5; boolean lifeCritical = life <= 5;
lifeCritical |= (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat lifeCritical |= game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
.lifeInDanger(ai, game.getCombat()));
if (abCost != null && !lifeCritical) { if (abCost != null && !lifeCritical) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) { if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {

View File

@@ -91,20 +91,21 @@ public class ProtectAi extends SpellAbilityAi {
return true; return true;
} }
if( combat != null ) {
// is the creature blocking and unable to destroy the attacker // is the creature blocking and unable to destroy the attacker
// or would be destroyed itself? // or would be destroyed itself?
if (c.isBlocking() if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
&& (ComputerUtilCombat.blockerWouldBeDestroyed(ai, c))) {
return true; return true;
} }
// is the creature in blocked and the blocker would survive // is the creature in blocked and the blocker would survive
// TODO Potential NPE here if no blockers are actually left // TODO Potential NPE here if no blockers are actually left
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& combat.isAttacking(c) && game.getCombat().isBlocked(c) && combat.isAttacking(c) && combat.isBlocked(c)
&& ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { && ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) {
return true; return true;
} }
}
return false; return false;
} }

View File

@@ -52,6 +52,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
*/ */
public boolean isUsefulCurseKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa) { public boolean isUsefulCurseKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
final Player human = ai.getOpponent(); final Player human = ai.getOpponent();
//int attack = getNumAttack(sa); //int attack = getNumAttack(sa);
@@ -115,19 +116,18 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} }
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.") } else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) { || keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || card.isBlocking()) if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1) || ph.getPhase().isBefore(PhaseType.MAIN1)
|| CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) { || CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) {
return false; return false;
} }
if (ph.isPlayerTurn(human) && (!card.isAttacking() if (ph.isPlayerTurn(human) && (combat == null || !combat.isAttacking(card) || card.getNetCombatDamage() <= 0)) {
|| card.getNetCombatDamage() <= 0)) {
return false; return false;
} }
} else if (keyword.endsWith("CARDNAME attacks each turn if able.")) { } else if (keyword.endsWith("CARDNAME attacks each turn if able.")) {
if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human) || !CombatUtil.canBeBlocked(card) if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human) || !CombatUtil.canBeBlocked(card, ai.getOpponent())
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) { || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false; return false;
} }
@@ -135,8 +135,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (card.getShield() > 0) { if (card.getShield() > 0) {
return true; return true;
} }
if (card.hasKeyword("If CARDNAME would be destroyed, regenerate it.") if (card.hasKeyword("If CARDNAME would be destroyed, regenerate it.") && combat != null
&& (card.isBlocked() || card.isBlocking())) { && (combat.isBlocked(card) || combat.isBlocking(card))) {
return true; return true;
} }
return false; return false;
@@ -158,6 +158,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
*/ */
public boolean isUsefulPumpKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa, final int attack) { public boolean isUsefulPumpKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa, final int attack) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ai.getOpponent(); final Player opp = ai.getOpponent();
//int defense = getNumDefense(sa); //int defense = getNumDefense(sa);
@@ -171,7 +172,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido")); final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido"));
// give evasive keywords to creatures that can or do attack // give evasive keywords to creatures that can or do attack
if (evasive) { if (evasive) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
@@ -187,7 +188,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return true; return true;
} }
Predicate<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach")); Predicate<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), || !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
@@ -202,7 +203,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { && ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true; return true;
} }
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), || CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
@@ -220,7 +221,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} else if (keyword.endsWith("Indestructible")) { } else if (keyword.endsWith("Indestructible")) {
return true; return true;
} else if (keyword.endsWith("Deathtouch")) { } else if (keyword.endsWith("Deathtouch")) {
Combat combat = game.getCombat();
if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) { if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
List<Card> attackers = combat.getAttackers(); List<Card> attackers = combat.getAttackers();
for (Card attacker : attackers) { for (Card attacker : attackers) {
@@ -241,34 +241,34 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} }
return false; return false;
} else if (combatRelevant) { } else if (combatRelevant) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| (opp.getCreaturesInPlay().size() < 1) || (opp.getCreaturesInPlay().size() < 1)
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
return false; return false;
} }
} else if (keyword.equals("Double Strike")) { } else if (keyword.equals("Double Strike")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false; return false;
} }
} else if (keyword.startsWith("Rampage")) { } else if (keyword.startsWith("Rampage")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) { || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) {
return false; return false;
} }
} else if (keyword.startsWith("Flanking")) { } else if (keyword.startsWith("Flanking")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), || CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
"Flanking").isEmpty()) { "Flanking").isEmpty()) {
return false; return false;
} }
} else if (keyword.startsWith("Trample")) { } else if (keyword.startsWith("Trample")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| !CombatUtil.canBeBlocked(card) || !CombatUtil.canBeBlocked(card, opp)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() + attack <= 1 || card.getNetCombatDamage() + attack <= 1
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
@@ -278,11 +278,11 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (card.getNetCombatDamage() <= 0) { if (card.getNetCombatDamage() <= 0) {
return false; return false;
} }
if (card.isBlocking()) { if (combat != null && combat.isBlocking(card)) {
return true; return true;
} }
if ((ph.isPlayerTurn(opp)) if ((ph.isPlayerTurn(opp))
|| !(CombatUtil.canAttack(card, opp) || card.isAttacking()) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false; return false;
} }
@@ -290,20 +290,12 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (card.getNetCombatDamage() <= 0) { if (card.getNetCombatDamage() <= 0) {
return false; return false;
} }
if (card.isBlocking()) { return combat != null && ( combat.isBlocking(card) || combat.isAttacking(card) && combat.isBlocked(card) );
return true;
}
if (card.isAttacking() && card.isBlocked()) {
return true;
}
return false;
} else if (keyword.equals("Lifelink")) { } else if (keyword.equals("Lifelink")) {
if (card.getNetCombatDamage() <= 0) { if (card.getNetCombatDamage() <= 0) {
return false; return false;
} }
if (!card.isBlocking() && !card.isAttacking()) { return combat != null && ( combat.isAttacking(card) || combat.isBlocking(card) );
return false;
}
} else if (keyword.equals("Vigilance")) { } else if (keyword.equals("Vigilance")) {
if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp) if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
@@ -341,7 +333,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false; return false;
} }
} else if (keyword.equals("Islandwalk")) { } else if (keyword.equals("Islandwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty() || CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
@@ -349,7 +341,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false; return false;
} }
} else if (keyword.equals("Swampwalk")) { } else if (keyword.equals("Swampwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty() || CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
@@ -357,7 +349,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false; return false;
} }
} else if (keyword.equals("Mountainwalk")) { } else if (keyword.equals("Mountainwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty() || CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
@@ -365,7 +357,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false; return false;
} }
} else if (keyword.equals("Forestwalk")) { } else if (keyword.equals("Forestwalk")) {
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card))
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| card.getNetCombatDamage() <= 0 || card.getNetCombatDamage() <= 0
|| CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty() || CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
@@ -385,8 +377,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack, protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack,
final List<String> keywords) { final List<String> keywords) {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final PhaseHandler phase = game.getPhaseHandler();
PhaseHandler phase = game.getPhaseHandler(); final Combat combat = phase.getCombat();
if (!c.canBeTargetedBy(sa)) { if (!c.canBeTargetedBy(sa)) {
return false; return false;
@@ -423,14 +415,15 @@ public abstract class PumpAiBase extends SpellAbilityAi {
// is the creature blocking and unable to destroy the attacker // is the creature blocking and unable to destroy the attacker
// or would be destroyed itself? // or would be destroyed itself?
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && c.isBlocking()) {
if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c)) { if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && combat.isBlocking(c)) {
if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
return true; return true;
} }
List<Card> blockedBy = game.getCombat().getAttackersBlockedBy(c); List<Card> blockedBy = combat.getAttackersBlockedBy(c);
// For now, Only care the first creature blocked by a card. // For now, Only care the first creature blocked by a card.
// TODO Add in better BlockAdditional support // TODO Add in better BlockAdditional support
if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(ai, blockedBy.get(0))) { if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(ai, blockedBy.get(0), combat)) {
return true; return true;
} }
} }
@@ -447,10 +440,11 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& combat.isBlocked(c) && combat.isBlocked(c)
&& combat.getBlockers(c) != null && combat.getBlockers(c) != null
&& !combat.getBlockers(c).isEmpty() && !combat.getBlockers(c).isEmpty()
&& !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { && !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) {
return true; return true;
} }
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS, ai.getOpponent()) && combat.isBlocking(c) && defense > 0 ) {
// if the life of the computer is in danger, try to pump blockers blocking Tramplers // if the life of the computer is in danger, try to pump blockers blocking Tramplers
List<Card> blockedBy = combat.getAttackersBlockedBy(c); List<Card> blockedBy = combat.getAttackersBlockedBy(c);
boolean attackerHasTrample = false; boolean attackerHasTrample = false;
@@ -458,12 +452,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
attackerHasTrample |= b.hasKeyword("Trample"); attackerHasTrample |= b.hasKeyword("Trample");
} }
if (phase.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) if (attackerHasTrample && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, combat)))
&& phase.isPlayerTurn(ai.getOpponent())
&& c.isBlocking()
&& defense > 0
&& attackerHasTrample
&& (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, game.getCombat()))) {
return true; return true;
} }
@@ -505,6 +494,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
protected List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List<String> keywords) { protected List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List<String> keywords) {
List<Card> list = ai.getOpponent().getCreaturesInPlay(); List<Card> list = ai.getOpponent().getCreaturesInPlay();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
list = CardLists.getTargetableCards(list, sa); list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty()) { if (list.isEmpty()) {
@@ -538,7 +528,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if (!c.isAttacking()) { if (combat == null || !combat.isAttacking(c)) {
return false; return false;
} }
if (c.getNetAttack() > 0 && ai.getLife() < 5) { if (c.getNetAttack() > 0 && ai.getLife() < 5) {

View File

@@ -15,6 +15,7 @@ import forge.game.Game;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.phase.Combat;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -30,6 +31,7 @@ public class PumpAllAi extends PumpAiBase {
String valid = ""; String valid = "";
final Card source = sa.getSourceCard(); final Card source = sa.getSourceCard();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
final int power = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa); final int power = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa); final int defense = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa);
@@ -86,13 +88,12 @@ public class PumpAllAi extends PumpAiBase {
} }
int totalPower = 0; int totalPower = 0;
for (Card c : human) { for (Card c : human) {
if (!c.isAttacking()) { if (combat == null || !combat.isAttacking(c)) {
continue; continue;
} }
totalPower += Math.min(c.getNetAttack(), power * -1); totalPower += Math.min(c.getNetAttack(), power * -1);
if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS && combat.isUnblocked(c)) {
&& game.getCombat().isUnblocked(c)) { if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), combat)) {
if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), game.getCombat())) {
return true; return true;
} }
totalPower += Math.min(c.getNetAttack(), power * -1); totalPower += Math.min(c.getNetAttack(), power * -1);
@@ -124,7 +125,7 @@ public class PumpAllAi extends PumpAiBase {
if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) { if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) {
return false; return false;
} }
if (phase.equals(PhaseType.COMBAT_DECLARE_ATTACKERS) && c.isAttacking()) { if (phase == PhaseType.COMBAT_DECLARE_ATTACKERS && combat.isAttacking(c)) {
return true; return true;
} }
if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) { if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) {

View File

@@ -34,6 +34,7 @@ import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -61,6 +62,7 @@ public class RegenerateAi extends SpellAbilityAi {
final Card hostCard = sa.getSourceCard(); final Card hostCard = sa.getSourceCard();
final Cost abCost = sa.getPayCosts(); final Cost abCost = sa.getPayCosts();
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false; boolean chance = false;
if (abCost != null) { if (abCost != null) {
@@ -98,7 +100,7 @@ public class RegenerateAi extends SpellAbilityAi {
for (final Card c : list) { for (final Card c : list) {
if (c.getShield() == 0) { if (c.getShield() == 0) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c); flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat);
} }
} }
@@ -142,7 +144,7 @@ public class RegenerateAi extends SpellAbilityAi {
CardLists.sortByEvaluateCreature(combatants); CardLists.sortByEvaluateCreature(combatants);
for (final Card c : combatants) { for (final Card c : combatants) {
if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c); sa.getTargets().add(c);
chance = true; chance = true;
break; break;
@@ -196,8 +198,9 @@ public class RegenerateAi extends SpellAbilityAi {
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
CardLists.sortByEvaluateCreature(combatants); CardLists.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) { for (final Card c : combatants) {
if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
sa.getTargets().add(c); sa.getTargets().add(c);
return true; return true;
} }

View File

@@ -13,6 +13,7 @@ import forge.game.Game;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -67,9 +68,9 @@ public class RegenerateAllAi extends SpellAbilityAi {
} else { } else {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
final List<Card> combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES); final List<Card> combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES);
final Combat combat = game.getCombat();
for (final Card c : combatants) { for (final Card c : combatants) {
if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
numSaved++; numSaved++;
} }
} }

View File

@@ -36,7 +36,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
for (final Card c : getTargetCards(sa)) { for (final Card c : getTargetCards(sa)) {
if ((tgt == null) || c.canBeTargetedBy(sa)) { if ((tgt == null) || c.canBeTargetedBy(sa)) {
game.getCombat().setBlocked(c); game.getCombat().setBlocked(c, true);
if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) { if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) {
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", c); runParams.put("Attacker", c);

View File

@@ -23,6 +23,7 @@ import forge.card.spellability.SpellAbilityStackInstance;
import forge.card.spellability.TargetRestrictions; import forge.card.spellability.TargetRestrictions;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.Game; import forge.game.Game;
import forge.game.phase.Combat;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -499,7 +500,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
List<GameEntity> defenders = game.getCombat().getDefenders(); List<GameEntity> defenders = game.getCombat().getDefenders();
if (!defenders.isEmpty()) { if (!defenders.isEmpty()) {
// Blockeres are already declared, set this to unblocked // Blockeres are already declared, set this to unblocked
game.getCombat().addAttacker(tgtC, defenders.get(0), false); game.getCombat().addAttacker(tgtC, defenders.get(0));
} }
} }
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) { if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
@@ -840,9 +841,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Attacking")) { if (sa.hasParam("Attacking")) {
final List<GameEntity> e = c.getController().getGame().getCombat().getDefenders(); final Combat combat = game.getCombat();
if ( null != combat ) {
final List<GameEntity> e = combat.getDefenders();
final GameEntity defender = e.size() == 1 ? e.get(0) : GuiChoose.one("Declare " + c, e); final GameEntity defender = e.size() == 1 ? e.get(0) : GuiChoose.one("Declare " + c, e);
game.getCombat().addAttacker(c, defender); combat.addAttacker(c, defender);
}
} }
movedCard = game.getAction().moveTo(c.getController().getZone(destination), c); movedCard = game.getAction().moveTo(c.getController().getZone(destination), c);

View File

@@ -100,7 +100,7 @@ public class ChooseColorEffect extends SpellAbilityEffect {
final List<Card> list = game.getCardsIn(ZoneType.Battlefield); final List<Card> list = game.getCardsIn(ZoneType.Battlefield);
chosen.add(ComputerUtilCard.getMostProminentColor(list)); chosen.add(ComputerUtilCard.getMostProminentColor(list));
} }
else if (logic.equals("MostProminentAttackers")) { else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers())); chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers()));
} }
else if (logic.equals("MostProminentKeywordInComputerDeck")) { else if (logic.equals("MostProminentKeywordInComputerDeck")) {

View File

@@ -18,6 +18,7 @@ import forge.card.spellability.TargetRestrictions;
import forge.game.Game; import forge.game.Game;
import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.phase.Combat;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
@@ -138,7 +139,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
sourcesToChooseFrom.remove(o); sourcesToChooseFrom.remove(o);
} else { } else {
if (sa.hasParam("AILogic") && sa.getParam("AILogic").equals("NeedsPrevention")) { if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
final Player ai = sa.getActivatingPlayer(); final Player ai = sa.getActivatingPlayer();
if (!game.getStack().isEmpty()) { if (!game.getStack().isEmpty()) {
Card choseCard = ChooseCardOnStack(sa, ai, game); Card choseCard = ChooseCardOnStack(sa, ai, game);
@@ -147,14 +148,15 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
} }
} }
final Combat combat = game.getCombat();
if (chosen.isEmpty()) { if (chosen.isEmpty()) {
permanentSources = CardLists.filter(permanentSources, new Predicate<Card>() { permanentSources = CardLists.filter(permanentSources, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false; return false;
} }
return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
} }
}); });
chosen.add(ComputerUtilCard.getBestCreatureAI(permanentSources)); chosen.add(ComputerUtilCard.getBestCreatureAI(permanentSources));

View File

@@ -248,9 +248,8 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
if (sa.hasParam("Tapped")) { if (sa.hasParam("Tapped")) {
copy.setTapped(true); copy.setTapped(true);
} }
if (sa.hasParam("CopyAttacking")) { if (sa.hasParam("CopyAttacking") && game.getPhaseHandler().inCombat()) {
final GameEntity defender = (GameEntity) AbilityUtils.getDefinedPlayers(hostCard, final GameEntity defender = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("CopyAttacking"), sa).get(0);
sa.getParam("CopyAttacking"), sa).get(0);
game.getCombat().addAttacker(copy, defender); game.getCombat().addAttacker(copy, defender);
} }
} }

View File

@@ -30,7 +30,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
game.getStack().clear(); game.getStack().clear();
// 2) All attacking and blocking creatures are removed from combat. // 2) All attacking and blocking creatures are removed from combat.
game.getCombat().reset(game.getPhaseHandler().getPlayerTurn()); game.getPhaseHandler().endCombat();
// 3) State-based actions are checked. No player gets priority, and no // 3) State-based actions are checked. No player gets priority, and no
// triggered abilities are put onto the stack. // triggered abilities are put onto the stack.

View File

@@ -31,11 +31,10 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
for (final Card c : getTargetCards(sa)) { for (final Card c : getTargetCards(sa)) {
if ((tgt == null) || c.canBeTargetedBy(sa)) { if ((tgt == null) || c.canBeTargetedBy(sa) && game.getPhaseHandler().inCombat()) {
game.getCombat().removeFromCombat(c); game.getPhaseHandler().getCombat().removeFromCombat(c);
} }
} }

View File

@@ -32,6 +32,7 @@ import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerHandler;
import forge.game.Game; import forge.game.Game;
import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTokenCreated;
import forge.game.phase.Combat;
import forge.game.player.Player; import forge.game.player.Player;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
import forge.item.PaperToken; import forge.item.PaperToken;
@@ -281,14 +282,16 @@ public class TokenEffect extends SpellAbilityEffect {
if (this.tokenTapped) { if (this.tokenTapped) {
c.setTapped(true); c.setTapped(true);
} }
if (this.tokenAttacking) { if (this.tokenAttacking && game.getPhaseHandler().inCombat()) {
final List<GameEntity> defs = c.getController().getGame().getCombat().getDefenders(); Combat combat = game.getPhaseHandler().getCombat();
final List<GameEntity> defs = combat.getDefenders();
final GameEntity defender;
if (c.getController().isHuman()) { if (c.getController().isHuman()) {
final GameEntity defender = defs.size() == 1 ? defs.get(0) : GuiChoose.one("Declare " + c, defs); defender = defs.size() == 1 ? defs.get(0) : GuiChoose.one("Declare " + c, defs);
game.getCombat().addAttacker(c, defender);
} else { } else {
game.getCombat().addAttacker(c, defs.get(0)); defender = defs.get(0);
} }
combat.addAttacker(c, defender);
} }
if (remember != null) { if (remember != null) {
game.getCardState(sa.getSourceCard()).addRemembered(c); game.getCardState(sa.getSourceCard()).addRemembered(c);

View File

@@ -71,7 +71,6 @@ public class Game {
private final StaticEffects staticEffects = new StaticEffects(); private final StaticEffects staticEffects = new StaticEffects();
private final TriggerHandler triggerHandler = new TriggerHandler(this); private final TriggerHandler triggerHandler = new TriggerHandler(this);
private final ReplacementHandler replacementHandler = new ReplacementHandler(this); private final ReplacementHandler replacementHandler = new ReplacementHandler(this);
private Combat combat = new Combat();
private final EventBus events = new EventBus(); private final EventBus events = new EventBus();
private final GameLog gameLog = new GameLog(); private final GameLog gameLog = new GameLog();
private final ColorChanger colorChanger = new ColorChanger(); private final ColorChanger colorChanger = new ColorChanger();
@@ -228,18 +227,10 @@ public class Game {
* @return the combat * @return the combat
*/ */
public final Combat getCombat() { public final Combat getCombat() {
return this.combat; return this.getPhaseHandler().getCombat();
} }
/**
* Sets the combat.
*
* @param combat0
* the combat to set
*/
public final void setCombat(final Combat combat0) {
this.combat = combat0;
}
/** /**
* Gets the game log. * Gets the game log.

View File

@@ -222,7 +222,7 @@ public class GameAction {
} }
if (zoneFrom != null) { if (zoneFrom != null) {
if (zoneFrom.is(ZoneType.Battlefield) && c.isCreature()) { if (zoneFrom.is(ZoneType.Battlefield) && c.isCreature() && game.getCombat() != null) {
game.getCombat().removeFromCombat(c); game.getCombat().removeFromCombat(c);
} }
zoneFrom.remove(c); zoneFrom.remove(c);
@@ -1348,7 +1348,8 @@ public class GameAction {
final boolean persist = (c.hasKeyword("Persist") && (c.getCounters(CounterType.M1M1) == 0)) && !c.isToken(); final boolean persist = (c.hasKeyword("Persist") && (c.getCounters(CounterType.M1M1) == 0)) && !c.isToken();
final boolean undying = (c.hasKeyword("Undying") && (c.getCounters(CounterType.P1P1) == 0)) && !c.isToken(); final boolean undying = (c.hasKeyword("Undying") && (c.getCounters(CounterType.P1P1) == 0)) && !c.isToken();
game.getCombat().removeFromCombat(c); if (game.getPhaseHandler().inCombat())
game.getPhaseHandler().getCombat().removeFromCombat(c);
final Card newCard = this.moveToGraveyard(c); final Card newCard = this.moveToGraveyard(c);

View File

@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.Card; import forge.Card;
import forge.CardLists; import forge.CardLists;
@@ -29,7 +30,6 @@ import forge.CounterType;
import forge.GameEntity; import forge.GameEntity;
import forge.card.trigger.Trigger; import forge.card.trigger.Trigger;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.Game;
import forge.game.phase.Combat; import forge.game.phase.Combat;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.player.Player; import forge.game.player.Player;
@@ -58,7 +58,6 @@ public class AiAttackController {
private List<Card> myList; // holds computer creatures private List<Card> myList; // holds computer creatures
private final Player ai; private final Player ai;
private final Player opponent;
private int aiAggression = 0; // added by Masher, how aggressive the ai is private int aiAggression = 0; // added by Masher, how aggressive the ai is
// attack will be depending on circumstances // attack will be depending on circumstances
@@ -74,11 +73,12 @@ public class AiAttackController {
* @param possibleBlockers * @param possibleBlockers
* a {@link forge.CardList} object. * a {@link forge.CardList} object.
*/ */
public AiAttackController(final Player ai, final Player opponent) { public AiAttackController(final Player ai) {
this.ai = ai; this.ai = ai;
this.opponent = opponent; Player opponent = ai.getOpponent();
this.oppList = opponent.getCreaturesInPlay(); this.oppList = Lists.newArrayList();
this.oppList.addAll(opponent.getCreaturesInPlay());
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
@@ -342,7 +342,7 @@ public class AiAttackController {
if (!CombatUtil.canAttackNextTurn(attacker)) { if (!CombatUtil.canAttackNextTurn(attacker)) {
continue; continue;
} }
if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker)) { if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) {
blockersLeft--; blockersLeft--;
continue; continue;
} }
@@ -442,7 +442,7 @@ public class AiAttackController {
* @param bAssault * @param bAssault
* a boolean. * a boolean.
*/ */
public final GameEntity chooseDefender(final Combat c, final Combat gameCombat, final boolean bAssault) { private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
final List<GameEntity> defs = c.getDefenders(); final List<GameEntity> defs = c.getDefenders();
if (defs.size() == 1) { if (defs.size() == 1) {
return defs.get(0); return defs.get(0);
@@ -450,8 +450,7 @@ public class AiAttackController {
final GameEntity entity = ai.getMustAttackEntity(); final GameEntity entity = ai.getMustAttackEntity();
if (null != entity) { if (null != entity) {
final List<GameEntity> defenders = gameCombat.getDefenders(); int n = defs.indexOf(entity);
int n = defenders.indexOf(entity);
if (-1 == n) { if (-1 == n) {
System.out.println("getMustAttackEntity() returned something not in defenders."); System.out.println("getMustAttackEntity() returned something not in defenders.");
return defs.get(0); return defs.get(0);
@@ -483,29 +482,22 @@ public class AiAttackController {
* *
* @return a {@link forge.game.phase.Combat} object. * @return a {@link forge.game.phase.Combat} object.
*/ */
public final Combat getAttackers() { public final void declareAttackers(final Combat combat) {
// if this method is called multiple times during a turn, // if this method is called multiple times during a turn,
// it will always return the same value // it will always return the same value
// randomInt is used so that the computer doesn't always // randomInt is used so that the computer doesn't always
// do the same thing on turn 3 if he had the same creatures in play // do the same thing on turn 3 if he had the same creatures in play
// I know this is a little confusing // I know this is a little confusing
Game game = ai.getGame();
random.setSeed(game.getPhaseHandler().getTurn() + AiAttackController.randomInt); random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt);
final Combat combat = new Combat();
combat.setAttackingPlayer(game.getCombat().getAttackingPlayer());
game.getCombat().initiatePossibleDefenders(opponent);
combat.setDefenders(game.getCombat().getDefenders());
if (this.attackers.isEmpty()) { if (this.attackers.isEmpty()) {
return combat; return;
} }
final boolean bAssault = this.doAssault(ai); final boolean bAssault = this.doAssault(ai);
// Determine who will be attacked // Determine who will be attacked
GameEntity defender = this.chooseDefender(combat, game.getCombat(), bAssault); GameEntity defender = this.chooseDefender(combat, bAssault);
List<Card> attackersLeft = new ArrayList<Card>(this.attackers); List<Card> attackersLeft = new ArrayList<Card>(this.attackers);
// Attackers that don't really have a choice // Attackers that don't really have a choice
for (final Card attacker : this.attackers) { for (final Card attacker : this.attackers) {
@@ -529,7 +521,7 @@ public class AiAttackController {
} }
} }
if (attackersLeft.isEmpty()) { if (attackersLeft.isEmpty()) {
return combat; return;
} }
if (bAssault) { if (bAssault) {
if ( LOG_AI_ATTACKS ) if ( LOG_AI_ATTACKS )
@@ -540,7 +532,7 @@ public class AiAttackController {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
return combat; return;
} }
// Exalted // Exalted
@@ -552,8 +544,7 @@ public class AiAttackController {
exalted = true; exalted = true;
break; break;
} }
if (c.getName().equals("Finest Hour") if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
&& game.getPhaseHandler().isFirstCombat()) {
exalted = true; exalted = true;
break; break;
} }
@@ -573,7 +564,7 @@ public class AiAttackController {
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) { if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
return combat; return;
} }
} }
} }
@@ -821,8 +812,6 @@ public class AiAttackController {
} }
} }
} }
return combat;
} // getAttackers() } // getAttackers()
/** /**

View File

@@ -41,162 +41,36 @@ import forge.game.player.Player;
* @author Forge * @author Forge
* @version $Id$ * @version $Id$
*/ */
public class ComputerUtilBlock { public class AiBlockController {
private final Player ai;
/** Constant <code>attackers</code>. */ /** Constant <code>attackers</code>. */
private static List<Card> attackers = new ArrayList<Card>(); // all attackers private List<Card> attackers = new ArrayList<Card>(); // all attackers
/** Constant <code>attackersLeft</code>. */ /** Constant <code>attackersLeft</code>. */
private static List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of private List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
// all currently // all currently
// unblocked // unblocked
// attackers // attackers
/** Constant <code>blockedButUnkilled</code>. */ /** Constant <code>blockedButUnkilled</code>. */
private static List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked private List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
// attackers // attackers
// that // that
// currently // currently
// wouldn't be // wouldn't be
// destroyed // destroyed
/** Constant <code>blockersLeft</code>. */ /** Constant <code>blockersLeft</code>. */
private static List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all private List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
// unassigned // unassigned
// blockers // blockers
/** Constant <code>diff=0</code>. */ private int diff = 0;
private static int diff = 0;
private static boolean lifeInDanger = false; private boolean lifeInDanger = false;
public AiBlockController(Player aiPlayer) {
/** this.ai = aiPlayer;
* <p>
* Getter for the field <code>attackers</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getAttackers() {
return ComputerUtilBlock.attackers;
}
/**
* <p>
* Setter for the field <code>attackers</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setAttackers(final List<Card> cardList) {
ComputerUtilBlock.attackers = (cardList);
}
/**
* <p>
* Getter for the field <code>attackersLeft</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getAttackersLeft() {
return ComputerUtilBlock.attackersLeft;
}
/**
* <p>
* Setter for the field <code>attackersLeft</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setAttackersLeft(final List<Card> cardList) {
ComputerUtilBlock.attackersLeft = (cardList);
}
/**
* <p>
* Getter for the field <code>blockedButUnkilled</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getBlockedButUnkilled() {
return ComputerUtilBlock.blockedButUnkilled;
}
/**
* <p>
* Setter for the field <code>blockedButUnkilled</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setBlockedButUnkilled(final List<Card> cardList) {
ComputerUtilBlock.blockedButUnkilled = (cardList);
}
/**
* <p>
* Getter for the field <code>blockersLeft</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getBlockersLeft() {
return ComputerUtilBlock.blockersLeft;
}
/**
* <p>
* Setter for the field <code>blockersLeft</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setBlockersLeft(final List<Card> cardList) {
ComputerUtilBlock.blockersLeft = (cardList);
}
/**
* <p>
* Getter for the field <code>diff</code>.
* </p>
*
* @return a int.
*/
private static int getDiff() {
return ComputerUtilBlock.diff;
}
/**
* <p>
* Setter for the field <code>diff</code>.
* </p>
*
* @param diff
* a int.
*/
private static void setDiff(final int diff) {
ComputerUtilBlock.diff = (diff);
} }
// finds the creatures able to block the attacker // finds the creatures able to block the attacker
/** private List<Card> getPossibleBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft, final boolean solo) {
* <p>
* getPossibleBlockers.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @param blockersLeft
* a {@link forge.CardList} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.CardList} object.
*/
private static List<Card> getPossibleBlockers(final Card attacker, final List<Card> blockersLeft, final Combat combat
, final boolean solo) {
final List<Card> blockers = new ArrayList<Card>(); final List<Card> blockers = new ArrayList<Card>();
for (final Card blocker : blockersLeft) { for (final Card blocker : blockersLeft) {
@@ -214,20 +88,7 @@ public class ComputerUtilBlock {
} }
// finds blockers that won't be destroyed // finds blockers that won't be destroyed
/** private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
* <p>
* getSafeBlockers.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @param blockersLeft
* a {@link forge.CardList} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.CardList} object.
*/
private static List<Card> getSafeBlockers(final Player ai, final Card attacker, final List<Card> blockersLeft, final Combat combat) {
final List<Card> blockers = new ArrayList<Card>(); final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) { for (final Card b : blockersLeft) {
@@ -240,20 +101,7 @@ public class ComputerUtilBlock {
} }
// finds blockers that destroy the attacker // finds blockers that destroy the attacker
/** private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
* <p>
* getKillingBlockers.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @param blockersLeft
* a {@link forge.CardList} object.
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.CardList} object.
*/
private static List<Card> getKillingBlockers(final Player ai, final Card attacker, final List<Card> blockersLeft, final Combat combat) {
final List<Card> blockers = new ArrayList<Card>(); final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) { for (final Card b : blockersLeft) {
@@ -267,7 +115,7 @@ public class ComputerUtilBlock {
public final static List<List<Card>> sortAttackerByDefender(Combat combat) { private List<List<Card>> sortAttackerByDefender(final Combat combat) {
List<GameEntity> defenders = combat.getDefenders(); List<GameEntity> defenders = combat.getDefenders();
final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size()); final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size());
for (GameEntity defender : defenders) { for (GameEntity defender : defenders) {
@@ -276,7 +124,7 @@ public class ComputerUtilBlock {
return attackers; return attackers;
} }
public static List<Card> sortPotentialAttackers(final Player ai, final Combat combat) { private List<Card> sortPotentialAttackers(final Combat combat) {
final List<List<Card>> attackerLists = sortAttackerByDefender(combat); final List<List<Card>> attackerLists = sortAttackerByDefender(combat);
final List<Card> sortedAttackers = new ArrayList<Card>(); final List<Card> sortedAttackers = new ArrayList<Card>();
final List<Card> firstAttacker = attackerLists.get(0); final List<Card> firstAttacker = attackerLists.get(0);
@@ -328,20 +176,11 @@ public class ComputerUtilBlock {
// ================================ // ================================
// Good Blocks means a good trade or no trade // Good Blocks means a good trade or no trade
/** private void makeGoodBlocks(final Combat combat) {
* <p>
* makeGoodBlocks.
* </p>
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat makeGoodBlocks(final Player ai, final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft()); List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue; continue;
@@ -349,26 +188,25 @@ public class ComputerUtilBlock {
Card blocker = null; Card blocker = null;
final List<Card> blockers = ComputerUtilBlock.getPossibleBlockers(attacker, final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
ComputerUtilBlock.getBlockersLeft(), combat, true);
final List<Card> safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat); final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
List<Card> killingBlockers; List<Card> killingBlockers;
if (safeBlockers.size() > 0) { if (safeBlockers.size() > 0) {
// 1.Blockers that can destroy the attacker but won't get // 1.Blockers that can destroy the attacker but won't get
// destroyed // destroyed
killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, safeBlockers, combat); killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
if (killingBlockers.size() > 0) { if (killingBlockers.size() > 0) {
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { } else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
ComputerUtilBlock.getBlockedButUnkilled().add(attacker); blockedButUnkilled.add(attacker);
} }
} // no safe blockers } // no safe blockers
else { else {
// 3.Blockers that can destroy the attacker and have an upside when dying // 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, blockers, combat); killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) { for (Card b : killingBlockers) {
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|| !b.getSVar("SacMe").equals("")) { || !b.getSVar("SacMe").equals("")) {
@@ -380,7 +218,7 @@ public class ComputerUtilBlock {
if (blocker == null && killingBlockers.size() > 0) { if (blocker == null && killingBlockers.size() > 0) {
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers); final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
if ((ComputerUtilCard.evaluateCreature(worst) + ComputerUtilBlock.getDiff()) < ComputerUtilCard if ((ComputerUtilCard.evaluateCreature(worst) + diff) < ComputerUtilCard
.evaluateCreature(attacker)) { .evaluateCreature(attacker)) {
blocker = worst; blocker = worst;
} }
@@ -391,8 +229,7 @@ public class ComputerUtilBlock {
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
} }
} }
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(currentAttackers)); attackersLeft = (new ArrayList<Card>(currentAttackers));
return combat;
} }
// Good Gang Blocks means a good trade or no trade // Good Gang Blocks means a good trade or no trade
@@ -407,14 +244,14 @@ public class ComputerUtilBlock {
*/ */
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private static Combat makeGangBlocks(final Player ai, final Combat combat) { private void makeGangBlocks(final Combat combat) {
List<Card> currentAttackers = CardLists.filter(ComputerUtilBlock.getAttackersLeft(), Predicates.not(rampagesOrNeedsManyToBlock)); List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> blockers; List<Card> blockers;
// Try to block an attacker without first strike with a gang of first strikers // Try to block an attacker without first strike with a gang of first strikers
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) { if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) {
blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
final List<Card> firstStrikeBlockers = new ArrayList<Card>(); final List<Card> firstStrikeBlockers = new ArrayList<Card>();
final List<Card> blockGang = new ArrayList<Card>(); final List<Card> blockGang = new ArrayList<Card>();
for (int i = 0; i < blockers.size(); i++) { for (int i = 0; i < blockers.size(); i++) {
@@ -448,12 +285,12 @@ public class ComputerUtilBlock {
} }
} }
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(currentAttackers)); attackersLeft = (new ArrayList<Card>(currentAttackers));
currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft()); currentAttackers = new ArrayList<Card>(attackersLeft);
// Try to block an attacker with two blockers of which only one will die // Try to block an attacker with two blockers of which only one will die
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { for (final Card attacker : attackersLeft) {
blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
List<Card> usableBlockers; List<Card> usableBlockers;
final List<Card> blockGang = new ArrayList<Card>(); final List<Card> blockGang = new ArrayList<Card>();
int absorbedDamage = 0; // The amount of damage needed to kill the int absorbedDamage = 0; // The amount of damage needed to kill the
@@ -471,12 +308,11 @@ public class ComputerUtilBlock {
&& !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) { && !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) {
return false; return false;
} }
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + ComputerUtilBlock.getDiff()) < ComputerUtilCard return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
.evaluateCreature(attacker);
} }
}); });
if (usableBlockers.size() < 2) { if (usableBlockers.size() < 2) {
return combat; return;
} }
final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers); final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers);
@@ -516,8 +352,7 @@ public class ComputerUtilBlock {
} }
} }
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(currentAttackers)); attackersLeft = (new ArrayList<Card>(currentAttackers));
return combat;
} }
// Bad Trade Blocks (should only be made if life is in danger) // Bad Trade Blocks (should only be made if life is in danger)
@@ -530,68 +365,56 @@ public class ComputerUtilBlock {
* a {@link forge.game.phase.Combat} object. * a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object. * @return a {@link forge.game.phase.Combat} object.
*/ */
private static Combat makeTradeBlocks(final Player ai, final Combat combat) { private void makeTradeBlocks(final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft()); List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
List<Card> killingBlockers; List<Card> killingBlockers;
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue; continue;
} }
killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true), killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
combat);
if ((killingBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) { if ((killingBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
currentAttackers.remove(attacker); currentAttackers.remove(attacker);
} }
} }
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(currentAttackers)); attackersLeft = (new ArrayList<Card>(currentAttackers));
return combat;
} }
// Chump Blocks (should only be made if life is in danger) // Chump Blocks (should only be made if life is in danger)
/** private void makeChumpBlocks(final Combat combat) {
* <p>
* makeChumpBlocks.
* </p>
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat makeChumpBlocks(final Player ai, final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft()); List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
Combat newCombat = makeChumpBlocks(ai, combat, currentAttackers); makeChumpBlocks(combat, currentAttackers);
if (ComputerUtilCombat.lifeInDanger(ai, newCombat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
return makeMultiChumpBlocks(ai, newCombat); makeMultiChumpBlocks(combat);
}
} }
return newCombat; private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
}
private static Combat makeChumpBlocks(final Player ai, final Combat combat, List<Card> attackers) {
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) { if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
return combat; return;
} }
Card attacker = attackers.get(0); Card attacker = attackers.get(0);
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
attackers.remove(0); attackers.remove(0);
return makeChumpBlocks(ai, combat, attackers); makeChumpBlocks(combat, attackers);
return;
} }
List<Card> chumpBlockers = ComputerUtilBlock List<Card> chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true);
if (!chumpBlockers.isEmpty()) { if (!chumpBlockers.isEmpty()) {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers); final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
@@ -607,44 +430,35 @@ public class ComputerUtilBlock {
&& !other.hasKeyword("Trample") && !other.hasKeyword("Trample")
&& CombatUtil.canBlock(other, blocker, combat)) { && CombatUtil.canBlock(other, blocker, combat)) {
combat.addBlocker(other, blocker); combat.addBlocker(other, blocker);
ComputerUtilBlock.getAttackersLeft().remove(other); attackersLeft.remove(other);
ComputerUtilBlock.getBlockedButUnkilled().add(other); blockedButUnkilled.add(other);
attackers.remove(other); attackers.remove(other);
return makeChumpBlocks(ai, combat, attackers); makeChumpBlocks(combat, attackers);
return;
} }
} }
} }
} }
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
ComputerUtilBlock.getAttackersLeft().remove(attacker); attackersLeft.remove(attacker);
ComputerUtilBlock.getBlockedButUnkilled().add(attacker); blockedButUnkilled.add(attacker);
} }
attackers.remove(0); attackers.remove(0);
return makeChumpBlocks(ai, combat, attackers); makeChumpBlocks(combat, attackers);
} }
/** // Block creatures with "can't be blocked except by two or more creatures"
* <p> private void makeMultiChumpBlocks(final Combat combat) {
* makeMultiChumpBlocks.
* </p>
*
* Block creatures with "can't be blocked except by two or more creatures"
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat makeMultiChumpBlocks(final Player ai, final Combat combat) {
List<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft()); List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : currentAttackers) { for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue; continue;
} }
List<Card> possibleBlockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true); List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size())) { if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size())) {
continue; continue;
} }
@@ -659,33 +473,21 @@ public class ComputerUtilBlock {
} }
} }
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size())) { if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size())) {
ComputerUtilBlock.getAttackersLeft().remove(attacker); attackersLeft.remove(attacker);
} else { } else {
for (Card blocker : usedBlockers) { for (Card blocker : usedBlockers) {
combat.removeBlockAssignment(attacker, blocker); combat.removeBlockAssignment(attacker, blocker);
} }
} }
} }
return combat;
} }
// Reinforce blockers blocking attackers with trample (should only be made /** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
// if life is in danger) private void reinforceBlockersAgainstTrample(final Combat combat) {
/**
* <p>
* reinforceBlockersAgainstTrample.
* </p>
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat reinforceBlockersAgainstTrample(final Player ai, final Combat combat) {
List<Card> chumpBlockers; List<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(ComputerUtilBlock.getAttackers(), "Trample"); List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced // TODO - should check here for a "rampage-like" trigger that replaced
@@ -699,8 +501,7 @@ public class ComputerUtilBlock {
continue; continue;
} }
chumpBlockers = ComputerUtilBlock chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false);
chumpBlockers.removeAll(combat.getBlockers(attacker)); chumpBlockers.removeAll(combat.getBlockers(attacker));
for (final Card blocker : chumpBlockers) { for (final Card blocker : chumpBlockers) {
// Add an additional blocker if the current blockers are not // Add an additional blocker if the current blockers are not
@@ -712,38 +513,26 @@ public class ComputerUtilBlock {
} }
} }
} }
return combat;
} }
// Support blockers not destroying the attacker with more blockers to try to /** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
// kill the attacker private void reinforceBlockersToKill(final Combat combat) {
/**
* <p>
* reinforceBlockersToKill.
* </p>
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat reinforceBlockersToKill(final Player ai, final Combat combat) {
List<Card> safeBlockers; List<Card> safeBlockers;
List<Card> blockers; List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(ComputerUtilBlock.getBlockedButUnkilled(), Predicates.not(rampagesOrNeedsManyToBlock)); List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced // TODO - should check here for a "rampage-like" trigger that replaced
// the keyword: // the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : targetAttackers) { for (final Card attacker : targetAttackers) {
blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
blockers.removeAll(combat.getBlockers(attacker)); blockers.removeAll(combat.getBlockers(attacker));
// Try to use safe blockers first // Try to use safe blockers first
safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat); safeBlockers = getSafeBlockers(combat, attacker, blockers);
for (final Card blocker : safeBlockers) { for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
@@ -777,198 +566,153 @@ public class ComputerUtilBlock {
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
if ((damageNeeded > currentDamage) if ((damageNeeded > currentDamage)
&& !(damageNeeded > (currentDamage + additionalDamage)) && !(damageNeeded > (currentDamage + additionalDamage))
&& ((ComputerUtilCard.evaluateCreature(blocker) + ComputerUtilBlock.getDiff()) < ComputerUtilCard && ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard
.evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) { .evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
ComputerUtilBlock.getBlockersLeft().remove(blocker); blockersLeft.remove(blocker);
}
} }
} }
} }
return combat; private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
}
/**
* <p>
* resetBlockers.
* </p>
*
* @param combat
* a {@link forge.game.phase.Combat} object.
* @param possibleBlockers
* a {@link forge.CardList} object.
* @return a {@link forge.game.phase.Combat} object.
*/
private static Combat resetBlockers(final Combat combat, final List<Card> possibleBlockers) {
final List<Card> oldBlockers = combat.getAllBlockers(); final List<Card> oldBlockers = combat.getAllBlockers();
for (final Card blocker : oldBlockers) { for (final Card blocker : oldBlockers) {
if ( blocker.getController() == ai ) // don't touch other player's blockers
combat.removeFromCombat(blocker); combat.removeFromCombat(blocker);
} }
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(ComputerUtilBlock.getAttackers())); // keeps attackersLeft = new ArrayList<Card>(attackers); // keeps track of all currently unblocked attackers
// track blockersLeft = new ArrayList<Card>(possibleBlockers); // keeps track of all unassigned blockers
// of all blockedButUnkilled = new ArrayList<Card>(); // keeps track of all blocked attackers that currently wouldn't be destroyed
// currently
// unblocked
// attackers
ComputerUtilBlock.setBlockersLeft(new ArrayList<Card>(possibleBlockers)); // keeps
// track of
// all
// unassigned
// blockers
ComputerUtilBlock.setBlockedButUnkilled(new ArrayList<Card>()); // keeps track
// of all
// blocked
// attackers that currently
// wouldn't be destroyed
return combat;
} }
// Main function /** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
/** public void assignBlockers(final Combat combat) {
* <p>
* getBlockers.
* </p>
*
* @param originalCombat
* a {@link forge.game.phase.Combat} object.
* @param possibleBlockers
* a {@link forge.CardList} object.
* @return a {@link forge.game.phase.Combat} object.
*/
public static Combat getBlockers(final Player ai, final Combat originalCombat, final List<Card> possibleBlockers) {
Combat combat = originalCombat; final List<Card> possibleBlockers = ai.getCreaturesInPlay();
ComputerUtilBlock.setAttackers(ComputerUtilBlock.sortPotentialAttackers(ai, combat)); attackers = sortPotentialAttackers(combat);
if (ComputerUtilBlock.getAttackers().size() == 0) { if (attackers.isEmpty()) {
return combat; return;
} }
// keeps track of all currently unblocked attackers clearBlockers(combat, possibleBlockers);
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(ComputerUtilBlock.getAttackers()));
// keeps track of all unassigned blockers
ComputerUtilBlock.setBlockersLeft(new ArrayList<Card>(possibleBlockers));
// keeps track of all blocked attackers that currently wouldn't be destroyed
ComputerUtilBlock.setBlockedButUnkilled(new ArrayList<Card>());
List<Card> blockers; List<Card> blockers;
List<Card> chumpBlockers; List<Card> chumpBlockers;
ComputerUtilBlock.setDiff((ai.getLife() * 2) - 5); // This is the minimal gain for an unnecessary trade diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
// remove all attackers that can't be blocked anyway // remove all attackers that can't be blocked anyway
for (final Card a : ComputerUtilBlock.getAttackers()) { for (final Card a : attackers) {
if (!CombatUtil.canBeBlocked(a)) { if (!CombatUtil.canBeBlocked(a, ai)) {
ComputerUtilBlock.getAttackersLeft().remove(a); attackersLeft.remove(a);
} }
} }
// remove all blockers that can't block anyway // remove all blockers that can't block anyway
for (final Card b : possibleBlockers) { for (final Card b : possibleBlockers) {
if (!CombatUtil.canBlock(b, combat)) { if (!CombatUtil.canBlock(b, combat)) {
ComputerUtilBlock.getBlockersLeft().remove(b); blockersLeft.remove(b);
} }
} }
if (ComputerUtilBlock.getAttackersLeft().size() == 0) { if (attackersLeft.isEmpty()) {
return combat; return;
} }
// Begin with the weakest blockers // Begin with the weakest blockers
CardLists.sortByPowerAsc(ComputerUtilBlock.getBlockersLeft()); CardLists.sortByPowerAsc(blockersLeft);
// == 1. choose best blocks first == // == 1. choose best blocks first ==
combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); makeGoodBlocks(combat);
combat = ComputerUtilBlock.makeGangBlocks(ai, combat); makeGangBlocks(combat);
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade blocks makeTradeBlocks(combat); // choose necessary trade blocks
} }
// if life is in danger // if life is in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose necessary chump blocks makeChumpBlocks(combat); // choose necessary chump blocks
} }
// if life is still in danger // if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still // Reinforce blockers blocking attackers with trample if life is still
// in danger // in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); reinforceBlockersAgainstTrample(combat);
} }
// Support blockers not destroying the attacker with more blockers to // Support blockers not destroying the attacker with more blockers to
// try to kill the attacker // try to kill the attacker
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); reinforceBlockersToKill(combat);
} }
// == 2. If the AI life would still be in danger make a safer approach // == 2. If the AI life would still be in danger make a safer approach ==
// ==
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
lifeInDanger = true; lifeInDanger = true;
combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset every block assignment clearBlockers(combat, possibleBlockers); // reset every block assignment
combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade blocks makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger // if life is in danger
combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); makeGoodBlocks(combat);
// choose necessary chump blocks if life is still in danger // choose necessary chump blocks if life is still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); makeChumpBlocks(combat);
} }
// Reinforce blockers blocking attackers with trample if life is // Reinforce blockers blocking attackers with trample if life is
// still in danger // still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); reinforceBlockersAgainstTrample(combat);
} }
combat = ComputerUtilBlock.makeGangBlocks(ai, combat); makeGangBlocks(combat);
combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); reinforceBlockersToKill(combat);
} }
// == 3. If the AI life would be in serious danger make an even safer approach == // == 3. If the AI life would be in serious danger make an even safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset every block assignment clearBlockers(combat, possibleBlockers); // reset every block assignment
combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose chump blocks makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) { if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade makeTradeBlocks(combat); // choose necessary trade
} }
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); makeGoodBlocks(combat);
} }
// Reinforce blockers blocking attackers with trample if life is // Reinforce blockers blocking attackers with trample if life is
// still in danger // still in danger
else { else {
combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); reinforceBlockersAgainstTrample(combat);
} }
combat = ComputerUtilBlock.makeGangBlocks(ai, combat); makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers // Support blockers not destroying the attacker with more blockers
// to try to kill the attacker // to try to kill the attacker
combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); reinforceBlockersToKill(combat);
} }
// assign blockers that have to block // assign blockers that have to block
chumpBlockers = CardLists.getKeyword(ComputerUtilBlock.getBlockersLeft(), "CARDNAME blocks each turn if able."); chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
// if an attacker with lure attacks - all that can block // if an attacker with lure attacks - all that can block
for (final Card blocker : ComputerUtilBlock.getBlockersLeft()) { for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) { if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
chumpBlockers.add(blocker); chumpBlockers.add(blocker);
} }
} }
if (!chumpBlockers.isEmpty()) { if (!chumpBlockers.isEmpty()) {
CardLists.shuffle(ComputerUtilBlock.getAttackers()); CardLists.shuffle(attackers);
for (final Card attacker : ComputerUtilBlock.getAttackers()) { for (final Card attacker : attackers) {
blockers = ComputerUtilBlock.getPossibleBlockers(attacker, chumpBlockers, combat, false); blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
for (final Card blocker : blockers) { for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilBlock.getBlockersLeft().contains(blocker) if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat) && (CombatUtil.mustBlockAnAttacker(blocker, combat)
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) { || blocker.hasKeyword("CARDNAME blocks each turn if able."))) {
combat.addBlocker(attacker, blocker); combat.addBlocker(attacker, blocker);
ComputerUtilBlock.getBlockersLeft().remove(blocker); blockersLeft.remove(blocker);
} }
} }
} }
} }
return combat;
} }
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) { public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {

View File

@@ -17,7 +17,6 @@
*/ */
package forge.game.ai; package forge.game.ai;
import java.security.InvalidParameterException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -46,7 +45,7 @@ import forge.card.spellability.SpellAbility;
import forge.card.spellability.SpellPermanent; import forge.card.spellability.SpellPermanent;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.Game; import forge.game.Game;
import forge.game.phase.CombatUtil; import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
@@ -647,7 +646,7 @@ public class AiController {
} }
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode); String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode);
throw new InvalidParameterException(exMsg); throw new IllegalArgumentException(exMsg);
} else } else
return api.getAi().confirmAction(player, sa, mode, message); return api.getAi().confirmAction(player, sa, mode, message);
@@ -741,19 +740,20 @@ public class AiController {
} }
} }
public void declateBlockers(Player defender) { // declares blockers for given defender in a given combat
final List<Card> blockers = defender.getCreaturesInPlay(); public void declareBlockersFor(Player defender, Combat combat) {
AiBlockController block = new AiBlockController(defender);
// When player != defender, AI should declare blockers for its benefit. // When player != defender, AI should declare blockers for its benefit.
game.setCombat(ComputerUtilBlock.getBlockers(defender, game.getCombat(), blockers)); block.assignBlockers(combat);
CombatUtil.orderMultipleCombatants(game.getCombat());
} }
public void declareAttackers(Player attacker) { public Combat declareAttackers(Player attacker, Combat combat) {
// 12/2/10(sol) the decision making here has moved to getAttackers() // 12/2/10(sol) the decision making here has moved to getAttackers()
game.setCombat(new AiAttackController(attacker, attacker.getOpponent()).getAttackers()); AiAttackController aiAtk = new AiAttackController(attacker);
aiAtk.declareAttackers(combat);
for (final Card element : game.getCombat().getAttackers()) { for (final Card element : combat.getAttackers()) {
// tapping of attackers happens after Propaganda is paid for // tapping of attackers happens after Propaganda is paid for
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker."); sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker.");
@@ -766,6 +766,7 @@ public class AiController {
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {
p.getController().autoPassCancel(); p.getController().autoPassCancel();
} }
return combat;
} }
private void playLands() { private void playLands() {

View File

@@ -676,21 +676,6 @@ public class ComputerUtil {
return returnList; return returnList;
} }
/**
* <p>
* getBlockers.
* </p>
*
* @return a {@link forge.game.phase.Combat} object.
*/
public static Combat getBlockers(final Player ai) {
final List<Card> blockers = ai.getCardsIn(ZoneType.Battlefield);
return ComputerUtilBlock.getBlockers(ai, ai.getGame().getCombat(), blockers);
}
/** /**
* <p> * <p>
* sacrificePermanents. * sacrificePermanents.
@@ -993,17 +978,18 @@ public class ComputerUtil {
boolean ret = true; boolean ret = true;
if (source.getManaCost().countX() > 0) { if (source.getManaCost().countX() > 0) {
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
return ret;
} else { } else {
// Otherwise, if life is possibly in danger, then this is fine. // Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat(); Combat combat = new Combat(ai.getOpponent());
combat.initiatePossibleDefenders(ai);
List<Card> attackers = ai.getOpponent().getCreaturesInPlay(); List<Card> attackers = ai.getOpponent().getCreaturesInPlay();
for (Card att : attackers) { for (Card att : attackers) {
if (CombatUtil.canAttackNextTurn(att)) { if (CombatUtil.canAttackNextTurn(att)) {
combat.addAttacker(att, att.getController().getOpponent()); combat.addAttacker(att, att.getController().getOpponent());
} }
} }
combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); AiBlockController aiBlock = new AiBlockController(ai);
aiBlock.assignBlockers(combat);
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
// Otherwise, return false. Do not play now. // Otherwise, return false. Do not play now.
ret = false; ret = false;

View File

@@ -28,6 +28,7 @@ import forge.card.spellability.SpellAbility;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.phase.Combat;
import forge.game.player.Player; import forge.game.player.Player;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -614,9 +615,10 @@ public class ComputerUtilCard {
* @return a boolean. * @return a boolean.
*/ */
public static boolean doesCreatureAttackAI(final Player ai, final Card card) { public static boolean doesCreatureAttackAI(final Player ai, final Card card) {
final List<Card> att = new AiAttackController(ai, ai.getOpponent()).getAttackers().getAttackers(); AiAttackController aiAtk = new AiAttackController(ai);
Combat combat = new Combat(ai);
return att.contains(card); aiAtk.declareAttackers(combat);
return combat.isAttacking(card);
} }
/** /**

View File

@@ -527,13 +527,13 @@ public class ComputerUtilCombat {
* a {@link forge.Card} object. * a {@link forge.Card} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant) { public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant, Combat combat) {
if (combatant.isAttacking()) { if (combat.isAttacking(combatant)) {
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, combatant); return ComputerUtilCombat.attackerWouldBeDestroyed(ai, combatant, combat);
} }
if (combatant.isBlocking()) { if (combat.isBlocking(combatant)) {
return ComputerUtilCombat.blockerWouldBeDestroyed(ai, combatant); return ComputerUtilCombat.blockerWouldBeDestroyed(ai, combatant, combat);
} }
return false; return false;
} }
@@ -549,13 +549,11 @@ public class ComputerUtilCombat {
* a {@link forge.Card} object. * a {@link forge.Card} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker) { public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker, Combat combat) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final List<Card> blockers = combat.getBlockers(attacker); final List<Card> blockers = combat.getBlockers(attacker);
for (final Card defender : blockers) { for (final Card defender : blockers) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, game.getCombat(), true) if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, true)
&& !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) { && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) {
return true; return true;
} }
@@ -600,7 +598,7 @@ public class ComputerUtilCombat {
TriggerType mode = trigger.getMode(); TriggerType mode = trigger.getMode();
if (mode == TriggerType.Attacks) { if (mode == TriggerType.Attacks) {
willTrigger = true; willTrigger = true;
if (attacker.isAttacking()) { if (combat.isAttacking(attacker)) {
return false; // The trigger should have triggered already return false; // The trigger should have triggered already
} }
if (trigParams.containsKey("ValidCard")) { if (trigParams.containsKey("ValidCard")) {
@@ -1510,14 +1508,13 @@ public class ComputerUtilCombat {
* a {@link forge.Card} object. * a {@link forge.Card} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker) { public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker, Combat combat) {
final Game game = ai.getGame();
// TODO THis function only checks if a single attacker at a time would destroy a blocker // TODO THis function only checks if a single attacker at a time would destroy a blocker
// This needs to expand to tally up damage // This needs to expand to tally up damage
final List<Card> attackers = game.getCombat().getAttackersBlockedBy(blocker); final List<Card> attackers = combat.getAttackersBlockedBy(blocker);
for (Card attacker : attackers) { for (Card attacker : attackers) {
if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, game.getCombat(), true) if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, true)
&& !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) { && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) {
return true; return true;
} }

View File

@@ -11,40 +11,27 @@ import forge.GameEntity;
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
* *
*/ */
public class AttackingBand implements Comparable<AttackingBand> { public class AttackingBand {
private List<Card> attackers = new ArrayList<Card>(); private List<Card> attackers = new ArrayList<Card>();
private List<Card> blockers = new ArrayList<Card>(); // private GameEntity defender = null;
private GameEntity defender = null; private boolean blocked = false; // even if all blockers were killed before FS or CD, band remains blocked
private Boolean blocked = null;
public AttackingBand(List<Card> band, GameEntity def) { public AttackingBand(List<Card> band, GameEntity def) {
attackers.addAll(band); attackers.addAll(band);
this.defender = def; // this.defender = def;
} }
public AttackingBand(Card card, GameEntity def) { public AttackingBand(Card card, GameEntity def) {
attackers.add(card); attackers.add(card);
this.defender = def; // this.defender = def;
} }
public List<Card> getAttackers() { return this.attackers; } public List<Card> getAttackers() { return this.attackers; }
public List<Card> getBlockers() { return this.blockers; } // public GameEntity getDefender() { return this.defender; }
public GameEntity getDefender() { return this.defender; }
public void addAttacker(Card card) { attackers.add(card); } public void addAttacker(Card card) { attackers.add(card); }
public void removeAttacker(Card card) { attackers.remove(card); } public void removeAttacker(Card card) { attackers.remove(card); }
public void addBlocker(Card card) { blockers.add(card); }
public void removeBlocker(Card card) { blockers.remove(card); }
public void setBlockers(List<Card> blockers) { this.blockers = blockers; }
public void setDefender(GameEntity def) { this.defender = def; }
public void setBlocked(boolean blocked) { this.blocked = blocked; }
public boolean getBlocked() { return this.blocked != null && this.blocked.booleanValue(); }
public void calculateBlockedState() { this.blocked = !this.blockers.isEmpty(); }
public static boolean isValidBand(List<Card> band, boolean shareDamage) { public static boolean isValidBand(List<Card> band, boolean shareDamage) {
if (band.isEmpty()) { if (band.isEmpty()) {
@@ -92,26 +79,29 @@ public class AttackingBand implements Comparable<AttackingBand> {
return isValidBand(newBand, false); return isValidBand(newBand, false);
} }
public boolean contains(Card c) {
return attackers.contains(c);
}
public boolean isBlocked() { return blocked; }
public void setBlocked(boolean value) { blocked = value; }
/**
* TODO: Write javadoc for this method.
* @return
*/
public boolean isEmpty() {
// TODO Auto-generated method stub
return attackers.isEmpty();
}
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object) * @see java.lang.Object#toString()
*/ */
@Override @Override
public int compareTo(AttackingBand o) { public String toString() {
if (o == null) { return String.format("%s %s", attackers.toString(), blocked ? ">||" : ">>>" );
return -1;
} }
List<Card> compareAttackers = o.getAttackers();
int sizeDiff = this.attackers.size() - compareAttackers.size();
if (sizeDiff > 0) {
return 1;
} else if (sizeDiff < 0) {
return -1;
} else if (sizeDiff == 0 && this.attackers.isEmpty()) {
return 0;
}
return this.attackers.get(0).compareTo(compareAttackers.get(0));
}
} }

View File

@@ -18,12 +18,11 @@
package forge.game.phase; package forge.game.phase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.Card; import forge.Card;
@@ -32,9 +31,11 @@ import forge.CardPredicates;
import forge.GameEntity; import forge.GameEntity;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.combat.AttackingBand; import forge.game.combat.AttackingBand;
import forge.game.event.GameEventBlockerAssigned;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.maps.CollectionSuppliers;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
/** /**
* <p> * <p>
@@ -45,166 +46,69 @@ import forge.game.zone.ZoneType;
* @version $Id$ * @version $Id$
*/ */
public class Combat { public class Combat {
// List of AttackingBands private final Player attackerPlayer;
private final List<AttackingBand> attackingBands = new ArrayList<AttackingBand>(); // Defenders, as they are attacked by hostile forces
// Attacker -> AttackingBand (Attackers can only be in a single band) private final MapOfLists<GameEntity, AttackingBand> attackedEntities = new HashMapOfLists<GameEntity, AttackingBand>(CollectionSuppliers.<AttackingBand>arrayLists());
private final Map<Card, AttackingBand> attackerToBandMap = new TreeMap<Card, AttackingBand>(); // Blockers to stop the hostile invaders
// Blocker -> AttackingBands (Blockers can block multiple bands/creatures private final MapOfLists<AttackingBand, Card> blockedBands = new HashMapOfLists<AttackingBand, Card>(CollectionSuppliers.<Card>arrayLists());
private final Map<Card, List<AttackingBand>> blockerToBandsMap = new TreeMap<Card, List<AttackingBand>>();
private final HashMap<Card, Integer> defendingDamageMap = new HashMap<Card, Integer>(); private final HashMap<Card, Integer> defendingDamageMap = new HashMap<Card, Integer>();
// Defenders are all Opposing Players + Planeswalker's Controller By Opposing Players
private Map<GameEntity, List<Card>> defenderMap = new HashMap<GameEntity, List<Card>>();
private Map<Card, List<Card>> blockerDamageAssignmentOrder = new TreeMap<Card, List<Card>>(); private Map<Card, List<Card>> orderBlockerDamageAssignment = new HashMap<Card, List<Card>>();
private Map<Card, List<Card>> attackerDamageAssignmentOrder = new TreeMap<Card, List<Card>>(); private Map<Card, List<Card>> orderAttackerDamageAssignment = new HashMap<Card, List<Card>>();
private Player attackingPlayer = null;
/** public Combat(Player attacker) {
* <p> attackerPlayer = attacker;
* reset.
* </p>
*/
public final void reset(Player playerTurn) {
this.resetAttackers();
this.defendingDamageMap.clear();
this.attackingPlayer = playerTurn;
this.initiatePossibleDefenders(playerTurn.getOpponents()); // Create keys for all possible attack targets
for (Player defender : attackerPlayer.getOpponents()) {
this.attackedEntities.ensureCollectionFor(defender);
List<Card> planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS);
for (final Card pw : planeswalkers) {
this.attackedEntities.ensureCollectionFor(pw);
} }
/**
* <p>
* initiatePossibleDefenders.
* </p>
*
* @param defender
* a {@link forge.game.player.Player} object.
*/
public final void initiatePossibleDefenders(final Iterable<Player> defenders) {
this.defenderMap.clear();
for (Player defender : defenders) {
fillDefenderMaps(defender);
} }
} }
public final void initiatePossibleDefenders(final Player defender) { public final Player getAttackingPlayer() {
this.defenderMap.clear(); return this.attackerPlayer;
fillDefenderMaps(defender);
} }
public final boolean isCombat() { public final boolean isCombat() {
return !attackingBands.isEmpty(); for(Collection<AttackingBand> abs : attackedEntities.values()) {
if(!abs.isEmpty())
return true;
}
return false;
} }
private void fillDefenderMaps(final Player defender) {
this.defenderMap.put(defender, new ArrayList<Card>());
List<Card> planeswalkers =
CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS);
for (final Card pw : planeswalkers) {
this.defenderMap.put(pw, new ArrayList<Card>());
}
}
/**
* <p>
* Getter for the field <code>defenders</code>.
* </p>
*
* @return a {@link java.util.ArrayList} object.
*/
public final List<GameEntity> getDefenders() { public final List<GameEntity> getDefenders() {
return new ArrayList<GameEntity>(this.defenderMap.keySet()); return Lists.newArrayList(attackedEntities.keySet());
} }
/**
* <p>
* Setter for the field <code>defenders</code>.
* </p>
*
* @param newDef
* a {@link java.util.ArrayList} object.
*/
public final void setDefenders(final List<GameEntity> newDef) {
this.defenderMap.clear();
for (GameEntity entity : newDef) {
this.defenderMap.put(entity, new ArrayList<Card>());
}
}
/**
* <p>
* getDefendingPlaneswalkers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<Player> getDefendingPlayers() { public final List<Player> getDefendingPlayers() {
final List<Player> defending = new ArrayList<Player>(); final List<Player> defending = new ArrayList<Player>();
for (final GameEntity o : attackedEntities.keySet()) {
for (final GameEntity o : this.defenderMap.keySet()) {
if (o instanceof Player) { if (o instanceof Player) {
defending.add((Player) o); defending.add((Player) o);
} }
} }
return defending; return defending;
} }
/**
* <p>
* getDefendingPlaneswalkers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<Card> getDefendingPlaneswalkers() { public final List<Card> getDefendingPlaneswalkers() {
final List<Card> pwDefending = new ArrayList<Card>(); final List<Card> pwDefending = new ArrayList<Card>();
for (final GameEntity o : attackedEntities.keySet()) {
for (final GameEntity o : this.defenderMap.keySet()) {
if (o instanceof Card) { if (o instanceof Card) {
pwDefending.add((Card) o); pwDefending.add((Card) o);
} }
} }
return pwDefending; return pwDefending;
} }
/** // Damage to whatever was protected there.
* <p>
* Setter for the field <code>attackingPlayer</code>.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
*/
public final void setAttackingPlayer(final Player player) {
this.attackingPlayer = player;
}
/**
* <p>
* Getter for the field <code>attackingPlayer</code>.
* </p>
*
* @return a {@link forge.game.player.Player} object.
*/
public final Player getAttackingPlayer() {
return this.attackingPlayer;
}
/**
* <p>
* addDefendingDamage.
* </p>
*
* @param n
* a int.
* @param source
* a {@link forge.Card} object.
*/
public final void addDefendingDamage(final int n, final Card source) { public final void addDefendingDamage(final int n, final Card source) {
final GameEntity ge = this.getDefenderByAttacker(source); final GameEntity ge = this.getDefenderByAttacker(source);
@@ -222,79 +126,58 @@ public class Combat {
} }
} }
public final List<Card> getAttackersOf(GameEntity defender) {
return defenderMap.get(defender);
}
public final List<AttackingBand> getAttackingBandsOf(GameEntity defender) { public final List<AttackingBand> getAttackingBandsOf(GameEntity defender) {
List<AttackingBand> bands = new ArrayList<AttackingBand>(); return Lists.newArrayList(attackedEntities.get(defender));
for(AttackingBand band : this.attackingBands) {
if (band.getDefender().equals(defender)) {
bands.add(band);
}
}
return bands;
} }
/** public final List<Card> getAttackersOf(GameEntity defender) {
* <p> List<Card> result = new ArrayList<Card>();
* isAttacking. for(AttackingBand v : attackedEntities.get(defender)) {
* </p> result.addAll(v.getAttackers());
*
* @param c
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean isAttacking(final Card c) {
return this.attackerToBandMap.containsKey(c);
} }
return result;
}
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) {
addAttacker(c, defender, band, null); Collection<AttackingBand> attackersOfDefender = attackedEntities.get(defender);
} if (attackersOfDefender == null) {
public final void addAttacker(final Card c, GameEntity defender, boolean blocked) {
addAttacker(c, defender, null, blocked);
}
public final void addAttacker(final Card c, GameEntity defender, AttackingBand band, Boolean blocked) {
if (!defenderMap.containsKey(defender)) {
System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); System.out.println("Trying to add Attacker " + c + " to missing defender " + defender);
return; return;
} }
if (band == null || !this.attackingBands.contains(band)) {
if (band == null || !attackersOfDefender.contains(band)) {
band = new AttackingBand(c, defender); band = new AttackingBand(c, defender);
if (blocked != null) { attackersOfDefender.add(band);
band.setBlocked(blocked.booleanValue());
}
this.attackingBands.add(band);
} else { } else {
band.addAttacker(c); band.addAttacker(c);
} }
// Attacker -> Defender and Defender -> Attacker map to Bands?
this.defenderMap.get(defender).add(c);
this.attackerToBandMap.put(c, band);
} }
/**
* <p>
* getDefenderByAttacker.
* </p>
*
* @param c
* a {@link forge.Card} object.
* @return a {@link java.lang.Object} object.
*/
public final GameEntity getDefenderByAttacker(final Card c) { public final GameEntity getDefenderByAttacker(final Card c) {
if (!this.attackerToBandMap.containsKey(c)) { for(Entry<GameEntity, Collection<AttackingBand>> e : attackedEntities.entrySet()) {
for(AttackingBand ab : e.getValue()) {
if ( ab.contains(c) )
return e.getKey();
}
}
return null; return null;
} }
return this.attackerToBandMap.get(c).getDefender();
private final GameEntity getDefenderByAttacker(final AttackingBand c) {
for(Entry<GameEntity, Collection<AttackingBand>> e : attackedEntities.entrySet()) {
for(AttackingBand ab : e.getValue()) {
if ( ab == c )
return e.getKey();
}
}
return null;
} }
public final Player getDefenderPlayerByAttacker(final Card c) { public final Player getDefenderPlayerByAttacker(final Card c) {
@@ -312,181 +195,112 @@ public class Combat {
return null; return null;
} }
public final GameEntity getDefendingEntity(final Card c) { public final AttackingBand getBandOfAttacker(final Card c) {
GameEntity defender = this.getDefenderByAttacker(c); for(Collection<AttackingBand> abs : attackedEntities.values()) {
if (this.defenderMap.containsKey(defender)) { for(AttackingBand ab : abs) {
return defender; if ( ab.contains(c) )
return ab;
}
} }
System.out.println("Attacker " + c + " missing defender " + defender);
return null; return null;
} }
public final AttackingBand getBandByAttacker(final Card c) {
return this.attackerToBandMap.get(c);
}
/**
* <p>
* resetAttackers.
* </p>
*/
public final void resetAttackers() {
this.attackingBands.clear();
this.blockerToBandsMap.clear();
this.attackerToBandMap.clear();
this.blockerDamageAssignmentOrder.clear();
this.attackerDamageAssignmentOrder.clear();
}
/**
* <p>
* getAttackers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<AttackingBand> getAttackingBands() { public final List<AttackingBand> getAttackingBands() {
return attackingBands; List<AttackingBand> result = Lists.newArrayList();
} // getAttackers() for(Collection<AttackingBand> abs : attackedEntities.values())
result.addAll(abs);
return result;
}
public final boolean isAttacking(final Card c) {
AttackingBand ab = getBandOfAttacker(c);
return ab != null;
}
/**
* TODO: Write javadoc for this method.
* @param card
* @param currentDefender
* @return
*/
public boolean isAttacking(Card card, GameEntity defender) {
for(Entry<GameEntity, Collection<AttackingBand>> ee : attackedEntities.entrySet())
for(AttackingBand ab : ee.getValue())
if (ab.contains(card))
return ee.getKey() == defender;
return false;
}
public final List<Card> getAttackers() { public final List<Card> getAttackers() {
List<Card> attackers = new ArrayList<Card>(); List<Card> result = Lists.newArrayList();
for(AttackingBand band : attackingBands) { for(Collection<AttackingBand> abs : attackedEntities.values())
attackers.addAll(band.getAttackers()); for(AttackingBand ab : abs)
} result.addAll(ab.getAttackers());
return attackers; return result;
}
public final boolean isBlocked(final Card attacker) {
return this.attackerToBandMap.get(attacker).getBlocked();
}
public final void setBlocked(final Card attacker) {
this.attackerToBandMap.get(attacker).setBlocked(true);
}
/**
* <p>
* addBlocker.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @param blocker
* a {@link forge.Card} object.
*/
public final void addBlocker(final Card attacker, final Card blocker) {
AttackingBand band = this.attackerToBandMap.get(attacker);
band.addBlocker(blocker);
if (!this.blockerToBandsMap.containsKey(blocker)) {
this.blockerToBandsMap.put(blocker, Lists.newArrayList(band));
} else {
this.blockerToBandsMap.get(blocker).add(band);
}
attacker.getGame().fireEvent(new GameEventBlockerAssigned());
}
public final void removeBlockAssignment(final Card attacker, final Card blocker) {
AttackingBand band = this.attackerToBandMap.get(attacker);
band.removeBlocker(blocker);
this.blockerToBandsMap.get(blocker).remove(attacker);
if (this.blockerToBandsMap.get(blocker).isEmpty()) {
this.blockerToBandsMap.remove(blocker);
}
}
/**
* <p>
* undoBlockingAssignment.
* </p>
*
* @param blocker
* a {@link forge.Card} object.
*/
public final void undoBlockingAssignment(final Card blocker) {
final List<AttackingBand> att = this.blockerToBandsMap.get(blocker);
for (final AttackingBand band : att) {
band.removeBlocker(blocker);
}
this.blockerToBandsMap.remove(blocker);
}
/**
* <p>
* getAllBlockers.
* </p>
*
* @return a {@link forge.CardList} object.
*/
public final List<Card> getAllBlockers() {
final List<Card> block = new ArrayList<Card>();
block.addAll(blockerToBandsMap.keySet());
return block;
}
public final List<Card> getBlockers(final AttackingBand band) {
List<Card> list = band.getBlockers();
if (list == null) {
return new ArrayList<Card>();
} else {
return new ArrayList<Card>(list);
}
} }
public final List<Card> getBlockers(final Card card) { public final List<Card> getBlockers(final Card card) {
return getBlockers(card, false);
}
public final List<Card> getBlockers(final Card card, boolean ordered) {
// If requesting the ordered blocking list pass true, directly. // If requesting the ordered blocking list pass true, directly.
List<Card> list = null; AttackingBand band = getBandOfAttacker(card);
if (ordered) { Collection<Card> blockers = blockedBands.get(band);
list = this.attackerDamageAssignmentOrder.containsKey(card) ? this.attackerDamageAssignmentOrder.get(card) : null; return blockers == null ? Lists.<Card>newArrayList() : Lists.newArrayList(blockers);
} else {
list = this.attackerToBandMap.containsKey(card) ? this.getBandByAttacker(card).getBlockers() : null;
} }
if (list == null) { public final boolean isBlocked(final Card attacker) {
return new ArrayList<Card>(); AttackingBand band = getBandOfAttacker(attacker);
} else { return band == null ? false : band.isBlocked();
return new ArrayList<Card>(list);
}
public final void setBlocked(final Card attacker, boolean value) {
getBandOfAttacker(attacker).setBlocked(value); // called by Curtain of Light, Dazzling Beauty, Trap Runner
}
public final void addBlocker(final Card attacker, final Card blocker) {
AttackingBand band = getBandOfAttacker(attacker);
blockedBands.add(band, blocker);
}
// remove blocked from specific attacker
public final void removeBlockAssignment(final Card attacker, final Card blocker) {
AttackingBand band = getBandOfAttacker(attacker);
Collection<Card> cc = blockedBands.get(band);
if( cc != null)
cc.remove(blocker);
}
// remove blocker from everywhere
public final void undoBlockingAssignment(final Card blocker) {
for(Collection<Card> blockers : blockedBands.values()) {
blockers.remove(blocker);
} }
} }
/** public final List<Card> getAllBlockers() {
* <p> List<Card> result = new ArrayList<Card>();
* getAttackerBlockedBy. for(Collection<Card> blockers : blockedBands.values()) {
* </p> if(!result.contains(blockers))
* result.addAll(blockers);
* @param blocker }
* a {@link forge.Card} object. return result;
* @return a {@link forge.Card} object. }
*/
public final List<Card> getBlockers(final AttackingBand band) {
Collection<Card> blockers = blockedBands.get(band);
return blockers == null ? Lists.<Card>newArrayList() : Lists.newArrayList(blockers);
}
public final List<Card> getAttackersBlockedBy(final Card blocker) { public final List<Card> getAttackersBlockedBy(final Card blocker) {
List<Card> blocked = new ArrayList<Card>(); List<Card> blocked = new ArrayList<Card>();
for(Entry<AttackingBand, Collection<Card>> s : blockedBands.entrySet()) {
if (blockerToBandsMap.containsKey(blocker)) { if (s.getValue().contains(blocker))
for(AttackingBand band : blockerToBandsMap.get(blocker)) { blocked.addAll(s.getKey().getAttackers());
blocked.addAll(band.getAttackers());
}
} }
return blocked; return blocked;
} }
/** public Player getDefendingPlayerRelatedTo(final Card source) {
* <p>
* getDefendingPlayer.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @return a {@link forge.Player} object.
*/
public List<Player> getDefendingPlayerRelatedTo(final Card source) {
List<Player> players = new ArrayList<Player>();
Card attacker = source; Card attacker = source;
if (source.isAura()) { if (source.isAura()) {
attacker = source.getEnchantingCard(); attacker = source.getEnchantingCard();
@@ -497,103 +311,101 @@ public class Combat {
} }
// return the corresponding defender // return the corresponding defender
Player defender = getDefenderPlayerByAttacker(attacker); return getDefenderPlayerByAttacker(attacker);
if (null != defender) {
players.add(defender);
return players;
}
// Can't figure out who it's related to... just return all???
// return all defending players
List<GameEntity> defenders = this.getDefenders();
for (GameEntity ge : defenders) {
if (ge instanceof Player) {
players.add((Player) ge);
}
}
return players;
} }
public void setAttackerDamageAssignmentOrder(final Card attacker, final List<Card> blockers) { public void setAttackerDamageAssignmentOrder(final Card attacker, final List<Card> blockers) {
this.attackerDamageAssignmentOrder.put(attacker, blockers); this.orderAttackerDamageAssignment.put(attacker, blockers);
} }
public void setBlockerDamageAssignmentOrder(final Card blocker, final List<Card> attackers) { public void setBlockerDamageAssignmentOrder(final Card blocker, final List<Card> attackers) {
this.blockerDamageAssignmentOrder.put(blocker, attackers); this.orderBlockerDamageAssignment.put(blocker, attackers);
} }
public final void removeFromCombat(final Card c) { // removes references to this attacker from all indices and orders
// is card an attacker? private void unregisterAttacker(final Card c, AttackingBand ab) {
if (this.attackerToBandMap.containsKey(c)) { orderAttackerDamageAssignment.remove(c);
// Soooo many maps to keep track of
AttackingBand band = this.attackerToBandMap.get(c);
band.removeAttacker(c);
this.attackerToBandMap.remove(c);
this.attackerDamageAssignmentOrder.remove(c);
List<Card> blockers = band.getBlockers(); Collection<Card> blockers = blockedBands.get(ab);
if ( blockers != null ) {
for (Card b : blockers) { for (Card b : blockers) {
if (band.getAttackers().isEmpty()) {
this.blockerToBandsMap.get(b).remove(c);
}
// Clear removed attacker from assignment order // Clear removed attacker from assignment order
if (this.blockerDamageAssignmentOrder.containsKey(b)) { if (this.orderBlockerDamageAssignment.containsKey(b)) {
this.blockerDamageAssignmentOrder.get(b).remove(c); this.orderBlockerDamageAssignment.get(b).remove(c);
}
}
}
return;
}
// removes references to this defender from all indices and orders
private void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) {
this.orderBlockerDamageAssignment.remove(c);
for(Card atk : bandBeingBlocked.getAttackers()) {
if (this.orderAttackerDamageAssignment.containsKey(atk)) {
this.orderAttackerDamageAssignment.get(atk).remove(c);
}
} }
} }
this.defenderMap.get(band.getDefender()).remove(c); // remove a combatant whose side is unknown
public final void removeFromCombat(final Card c) {
AttackingBand ab = getBandOfAttacker(c);
if (ab != null) {
unregisterAttacker(c, ab);
ab.removeAttacker(c);
// no need to remove empty bands
}
if (band.getAttackers().isEmpty() && band.getBlockers().isEmpty()) { // if not found in attackers, look for this card in blockers
this.getAttackingBands().remove(band); for(Entry<AttackingBand, Collection<Card>> be : blockedBands.entrySet()) {
} Collection<Card> blockers = be.getValue();
} else if (this.blockerToBandsMap.containsKey(c)) { // card is a blocker if(blockers.contains(c)) {
List<AttackingBand> attackers = this.blockerToBandsMap.get(c); unregisterDefender(c, be.getKey());
blockers.remove(c);
this.blockerToBandsMap.remove(c);
this.blockerDamageAssignmentOrder.remove(c);
for (AttackingBand a : attackers) {
a.removeBlocker(c);
for(Card atk : a.getAttackers()) {
if (this.attackerDamageAssignmentOrder.containsKey(atk)) {
this.attackerDamageAssignmentOrder.get(atk).remove(c);
}
}
} }
} }
} // removeFromCombat() } // removeFromCombat()
/**
* <p>
* verifyCreaturesInPlay.
* </p>
*/
public final void removeAbsentCombatants() { public final void removeAbsentCombatants() {
final List<Card> all = new ArrayList<Card>(); // iterate all attackers and remove them
for(AttackingBand band : this.getAttackingBands()) { for(Entry<GameEntity, Collection<AttackingBand>> ee : attackedEntities.entrySet()) {
all.addAll(band.getAttackers()); for(AttackingBand ab : ee.getValue()) {
List<Card> atk = ab.getAttackers();
for(int i = atk.size() - 1; i >= 0; i--) { // might remove items from collection, so no iterators
Card c = atk.get(i);
if ( !c.isInPlay() ) {
unregisterAttacker(c, ab);
ab.removeAttacker(c);
}
}
}
} }
all.addAll(this.getAllBlockers());
for (int i = 0; i < all.size(); i++) { Collection<Card> toRemove = Lists.newArrayList();
if (!all.get(i).isInPlay()) { for(Entry<AttackingBand, Collection<Card>> be : blockedBands.entrySet()) {
this.removeFromCombat(all.get(i)); toRemove.clear();
for( Card b : be.getValue()) {
if ( !b.isInPlay() ) {
unregisterDefender(b, be.getKey());
toRemove.add(b);
} }
} }
be.getValue().removeAll(toRemove);
}
} // verifyCreaturesInPlay() } // verifyCreaturesInPlay()
/**
* <p>
* setUnblocked.
* </p>
*/
public final void setUnblockedAttackers() {
final List<AttackingBand> attacking = this.getAttackingBands();
for (final AttackingBand band : attacking) {
band.calculateBlockedState();
if (!band.getBlocked()) { // Call this method right after turn-based action of declare blockers has been performed
for (Card attacker : band.getAttackers()) { public final void onBlockersDeclared() {
for(Collection<AttackingBand> abs : attackedEntities.values()) {
for(AttackingBand ab : abs) {
Collection<Card> blockers = blockedBands.get(ab);
boolean isBlocked = blockers != null && !blockers.isEmpty();
if (isBlocked)
ab.setBlocked(true);
else
for (Card attacker : ab.getAttackers()) {
// Run Unblocked Trigger // Run Unblocked Trigger
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", attacker); runParams.put("Attacker", attacker);
@@ -611,7 +423,7 @@ public class Combat {
for (final Card blocker : blockers) { for (final Card blocker : blockers) {
if (blocker.hasDoubleStrike() || blocker.hasFirstStrike() == firstStrikeDamage) { if (blocker.hasDoubleStrike() || blocker.hasFirstStrike() == firstStrikeDamage) {
List<Card> attackers = this.blockerDamageAssignmentOrder.get(blocker); List<Card> attackers = this.orderBlockerDamageAssignment.get(blocker);
final int damage = blocker.getNetCombatDamage(); final int damage = blocker.getNetCombatDamage();
@@ -619,9 +431,8 @@ public class Combat {
Player attackingPlayer = this.getAttackingPlayer(); Player attackingPlayer = this.getAttackingPlayer();
Player assigningPlayer = blocker.getController(); Player assigningPlayer = blocker.getController();
if (AttackingBand.isValidBand(attackers, true)) { if (AttackingBand.isValidBand(attackers, true))
assigningPlayer = attackingPlayer; assigningPlayer = attackingPlayer;
}
assignedDamage = true; assignedDamage = true;
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController());
@@ -654,28 +465,26 @@ public class Combat {
continue; continue;
} }
AttackingBand band = this.getBandByAttacker(attacker); AttackingBand band = this.getBandOfAttacker(attacker);
boolean trampler = attacker.hasKeyword("Trample"); boolean trampler = attacker.hasKeyword("Trample");
blockers = this.attackerDamageAssignmentOrder.get(attacker); blockers = this.orderAttackerDamageAssignment.get(attacker);
assignedDamage = true; assignedDamage = true;
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender
if (blockers == null || blockers.isEmpty()) { if (blockers == null || blockers.isEmpty()) {
if (trampler || !band.getBlocked()) { if (trampler || !band.isBlocked()) {
this.addDefendingDamage(damageDealt, attacker); this.addDefendingDamage(damageDealt, attacker);
} // No damage happens if blocked but no blockers left } // No damage happens if blocked but no blockers left
} else { } else {
GameEntity defender = band.getDefender(); GameEntity defender = getDefenderByAttacker(band);
Player assigningPlayer = this.getAttackingPlayer(); Player assigningPlayer = this.getAttackingPlayer();
// Defensive Formation is very similar to Banding with Blockers // Defensive Formation is very similar to Banding with Blockers
// It allows the defending player to assign damage instead of the attacking player // It allows the defending player to assign damage instead of the attacking player
if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) {
assigningPlayer = (Player)defender; assigningPlayer = (Player)defender;
} else { } else if ( AttackingBand.isValidBand(blockers, true)){
if (AttackingBand.isValidBand(blockers, true)) {
assigningPlayer = blockers.get(0).getController(); assigningPlayer = blockers.get(0).getController();
} }
}
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(attacker, blockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(attacker, blockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer);
for (Entry<Card, Integer> dt : map.entrySet()) { for (Entry<Card, Integer> dt : map.entrySet()) {
@@ -711,7 +520,7 @@ public class Combat {
final HashMap<GameEntity, List<Card>> wasDamaged = new HashMap<GameEntity, List<Card>>(); final HashMap<GameEntity, List<Card>> wasDamaged = new HashMap<GameEntity, List<Card>>();
for (final Entry<Card, Integer> entry : defMap.entrySet()) { for (final Entry<Card, Integer> entry : defMap.entrySet()) {
GameEntity defender = getDefendingEntity(entry.getKey()); GameEntity defender = getDefenderByAttacker(entry.getKey());
if (defender instanceof Player) { // player if (defender instanceof Player) { // player
if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) { if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) {
if (wasDamaged.containsKey(defender)) { if (wasDamaged.containsKey(defender)) {
@@ -787,7 +596,7 @@ public class Combat {
* @return a boolean. * @return a boolean.
*/ */
public final boolean isUnblocked(final Card att) { public final boolean isUnblocked(final Card att) {
return !this.attackerToBandMap.get(att).getBlocked(); return !isBlocked(att);
} }
/** /**
@@ -798,26 +607,42 @@ public class Combat {
* @return an array of {@link forge.Card} objects. * @return an array of {@link forge.Card} objects.
*/ */
public final List<Card> getUnblockedAttackers() { public final List<Card> getUnblockedAttackers() {
ArrayList<Card> unblocked = new ArrayList<Card>(); List<Card> unblocked = new ArrayList<Card>();
for (AttackingBand band : this.attackingBands) { for (Collection<AttackingBand> abs : attackedEntities.values())
if (!band.getBlocked()) { for (AttackingBand ab : abs)
unblocked.addAll(band.getAttackers()); if ( ab.isBlocked() )
} unblocked.addAll(ab.getAttackers());
}
return unblocked; return unblocked;
} }
public boolean isPlayerAttacked(Player priority) { public boolean isPlayerAttacked(Player who) {
for(GameEntity defender : defenderMap.keySet()) { for(Entry<GameEntity, Collection<AttackingBand>> ee : attackedEntities.entrySet() ) {
if ((defender instanceof Player && priority.equals(defender)) || GameEntity defender = ee.getKey();
(defender instanceof Card && priority.equals(((Card)defender).getController()))) { Card defenderAsCard = defender instanceof Card ? (Card)defender : null;
List<Card> attackers = defenderMap.get(defender); if ((null != defenderAsCard && defenderAsCard.getController() != who ) ||
if (attackers != null && !attackers.isEmpty()) (null == defenderAsCard && defender != who) )
continue; // defender is not related to player 'who'
for(AttackingBand ab : ee.getValue()) {
if ( !ab.isEmpty() )
return true; return true;
} }
} }
return false; return false;
} }
public boolean isBlocking(Card blocker) {
for (Collection<Card> blockers : blockedBands.values())
if (blockers.contains(blocker))
return true;
return false;
}
public boolean isBlocking(Card blocker, Card attacker) {
AttackingBand ab = getBandOfAttacker(attacker);
Collection<Card> blockers = blockedBands.get(ab);
return blockers != null && blockers.contains(blocker);
}
} // Class Combat } // Class Combat

View File

@@ -176,7 +176,7 @@ public class CombatUtil {
* a {@link forge.game.phase.Combat} object. * a {@link forge.game.phase.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean canBeBlocked(final Card attacker, final Combat combat) { public static boolean canBeBlocked(final Card attacker, final Combat combat, Player defendingPlayer) {
if (attacker == null) { if (attacker == null) {
return true; return true;
@@ -185,7 +185,7 @@ public class CombatUtil {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) {
return false; return false;
} }
return CombatUtil.canBeBlocked(attacker); return CombatUtil.canBeBlocked(attacker, defendingPlayer);
} }
// can the attacker be blocked at all? // can the attacker be blocked at all?
@@ -198,7 +198,7 @@ public class CombatUtil {
* a {@link forge.Card} object. * a {@link forge.Card} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean canBeBlocked(final Card attacker) { public static boolean canBeBlocked(final Card attacker, Player defender) {
if (attacker == null) { if (attacker == null) {
return true; return true;
@@ -209,7 +209,7 @@ public class CombatUtil {
} }
// Landwalk // Landwalk
if (isUnblockableFromLandwalk(attacker)) { if (isUnblockableFromLandwalk(attacker, defender)) {
return false; return false;
} }
@@ -217,7 +217,7 @@ public class CombatUtil {
} }
public static boolean isUnblockableFromLandwalk(final Card attacker) { public static boolean isUnblockableFromLandwalk(final Card attacker, Player defendingPlayer) {
//May be blocked as though it doesn't have landwalk. (Staff of the Ages) //May be blocked as though it doesn't have landwalk. (Staff of the Ages)
if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) { if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) {
return false; return false;
@@ -277,10 +277,6 @@ public class CombatUtil {
} }
String valid = StringUtils.join(walkTypes, ","); String valid = StringUtils.join(walkTypes, ",");
Player defendingPlayer = attacker.getController().getOpponent();
if (attacker.isAttacking()) {
defendingPlayer = defendingPlayer.getGame().getCombat().getDefendingPlayerRelatedTo(attacker).get(0);
}
List<Card> defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield); List<Card> defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield);
for (Card c : defendingLands) { for (Card c : defendingLands) {
if (c.isValid(valid.split(","), defendingPlayer, attacker)) { if (c.isValid(valid.split(","), defendingPlayer, attacker)) {
@@ -319,13 +315,9 @@ public class CombatUtil {
* @return true, if successful * @return true, if successful
*/ */
public static boolean canBeBlocked(final Card attacker, final List<Card> blockers) { public static boolean canBeBlocked(final Card attacker, final List<Card> blockers) {
if (!CombatUtil.canBeBlocked(attacker)) {
return false;
}
int blocks = 0; int blocks = 0;
for (final Card blocker : blockers) { for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker)) { if (CombatUtil.canBeBlocked(attacker, blocker.getController()) && CombatUtil.canBlock(attacker, blocker)) {
blocks++; blocks++;
} }
} }
@@ -366,32 +358,33 @@ public class CombatUtil {
* a {@link forge.game.phase.Combat} object. * a {@link forge.game.phase.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public static boolean finishedMandatoryBlocks(final Combat combat, final Player defending) { public static String validateBlocks(final Combat combat, final Player defending) {
final List<Card> blockers = defending.getCreaturesInPlay(); final List<Card> defendersArmy = defending.getCreaturesInPlay();
final List<Card> attackers = combat.getAttackers(); final List<Card> attackers = combat.getAttackers();
final List<Card> blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending);
// if a creature does not block but should, return false // if a creature does not block but should, return false
for (final Card blocker : blockers) { for (final Card blocker : defendersArmy) {
// lure effects // lure effects
if (!combat.getAllBlockers().contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) { if (!blockers.contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) {
return false; return String.format("%s must block an attacker, but has not been assigned to block any.", blocker);
} }
// "CARDNAME blocks each turn if able." // "CARDNAME blocks each turn if able."
if (!combat.getAllBlockers().contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) { if (!blockers.contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) {
for (final Card attacker : attackers) { for (final Card attacker : attackers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) { if (CombatUtil.canBlock(attacker, blocker, combat)) {
boolean must = true; boolean must = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
final List<Card> possibleBlockers = CardLists.filter(defending.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); final List<Card> possibleBlockers = Lists.newArrayList(defendersArmy);
possibleBlockers.remove(blocker); possibleBlockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, possibleBlockers)) { if (!CombatUtil.canBeBlocked(attacker, possibleBlockers)) {
must = false; must = false;
} }
} }
if (must) { if (must) {
return false; return String.format("%s must block each turn, but was not assigned to block any attacker now", blocker);
} }
} }
} }
@@ -399,20 +392,16 @@ public class CombatUtil {
} }
for (final Card attacker : attackers) { for (final Card attacker : attackers) {
int cntBlockers = combat.getBlockers(attacker).size();
// don't accept blocker amount for attackers with keyword defining valid blockers amount // don't accept blocker amount for attackers with keyword defining valid blockers amount
if (!canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size())) if (cntBlockers > 0 && !canAttackerBeBlockedWithAmount(attacker, cntBlockers))
return false; return String.format("%s cannot be blocked with %d creatures you've assigned", attacker, cntBlockers);
} }
return true; return null;
} }
public static void orderMultipleCombatants(final Combat combat) { public static void orderMultipleBlockers(final Combat combat) {
CombatUtil.orderMultipleBlockers(combat);
CombatUtil.orderBlockingMultipleAttackers(combat);
}
private static void orderMultipleBlockers(final Combat combat) {
// If there are multiple blockers, the Attacker declares the Assignment Order // If there are multiple blockers, the Attacker declares the Assignment Order
final Player player = combat.getAttackingPlayer(); final Player player = combat.getAttackingPlayer();
for (final AttackingBand band : combat.getAttackingBands()) { for (final AttackingBand band : combat.getAttackingBands()) {
@@ -435,7 +424,7 @@ public class CombatUtil {
} }
} }
private static void orderBlockingMultipleAttackers(final Combat combat) { public static void orderBlockingMultipleAttackers(final Combat combat) {
// If there are multiple blockers, the Attacker declares the Assignment Order // If there are multiple blockers, the Attacker declares the Assignment Order
for (final Card blocker : combat.getAllBlockers()) { for (final Card blocker : combat.getAllBlockers()) {
List<Card> attackers = combat.getAttackersBlockedBy(blocker); List<Card> attackers = combat.getAttackersBlockedBy(blocker);
@@ -485,8 +474,9 @@ public class CombatUtil {
} }
} }
final Player defender = blocker.getController();
for (final Card attacker : attackersWithLure) { for (final Card attacker : attackersWithLure) {
if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker)) { if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) {
boolean canBe = true; boolean canBe = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
@@ -503,7 +493,7 @@ public class CombatUtil {
if (blocker.getMustBlockCards() != null) { if (blocker.getMustBlockCards() != null) {
for (final Card attacker : blocker.getMustBlockCards()) { for (final Card attacker : blocker.getMustBlockCards()) {
if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker) if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)
&& combat.isAttacking(attacker)) { && combat.isAttacking(attacker)) {
boolean canBe = true; boolean canBe = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
@@ -546,7 +536,7 @@ public class CombatUtil {
if (!CombatUtil.canBlock(blocker, combat)) { if (!CombatUtil.canBlock(blocker, combat)) {
return false; return false;
} }
if (!CombatUtil.canBeBlocked(attacker, combat)) { if (!CombatUtil.canBeBlocked(attacker, combat, blocker.getController())) {
return false; return false;
} }
@@ -600,7 +590,7 @@ public class CombatUtil {
if (!CombatUtil.canBlock(blocker, nextTurn)) { if (!CombatUtil.canBlock(blocker, nextTurn)) {
return false; return false;
} }
if (!CombatUtil.canBeBlocked(attacker)) { if (!CombatUtil.canBeBlocked(attacker, blocker.getController())) {
return false; return false;
} }
@@ -684,6 +674,18 @@ public class CombatUtil {
return true; return true;
} // canBlock() } // canBlock()
public static void checkAttackOrBlockAlone(Combat combat) {
// Handles removing cards like Mogg Flunkies from combat if group attack
// didn't occur
for (Card c1 : combat.getAttackers()) {
if (c1.hasKeyword("CARDNAME can't attack or block alone.")) {
if (combat.getAttackers().size() < 2) {
combat.removeFromCombat(c1);
}
}
}
}
// can a creature attack given the combat state // can a creature attack given the combat state
/** /**
* <p> * <p>
@@ -762,6 +764,9 @@ public class CombatUtil {
} }
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) { public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) {
if( amount == 0 )
return false; // no block
List<String> restrictions = Lists.newArrayList(); List<String> restrictions = Lists.newArrayList();
for ( String kw : attacker.getKeyword() ) { for ( String kw : attacker.getKeyword() ) {
if ( kw.startsWith("CantBeBlockedByAmount") ) if ( kw.startsWith("CantBeBlockedByAmount") )
@@ -918,13 +923,13 @@ public class CombatUtil {
* @param bLast * @param bLast
* a boolean. * a boolean.
*/ */
public static boolean checkPropagandaEffects(final Game game, final Card c) { public static boolean checkPropagandaEffects(final Game game, final Card c, final Combat combat) {
Cost attackCost = new Cost(ManaCost.ZERO, true); Cost attackCost = new Cost(ManaCost.ZERO, true);
// Sort abilities to apply them in proper order // Sort abilities to apply them in proper order
for (Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { for (Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
final ArrayList<StaticAbility> staticAbilities = card.getStaticAbilities(); final ArrayList<StaticAbility> staticAbilities = card.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) { for (final StaticAbility stAb : staticAbilities) {
Cost additionalCost = stAb.getAttackCost(c, game.getCombat().getDefenderByAttacker(c)); Cost additionalCost = stAb.getAttackCost(c, combat.getDefenderByAttacker(c));
if ( null != additionalCost ) if ( null != additionalCost )
attackCost.add(additionalCost); attackCost.add(additionalCost);
} }
@@ -944,14 +949,14 @@ public class CombatUtil {
* @param c * @param c
* a {@link forge.Card} object. * a {@link forge.Card} object.
*/ */
public static void checkDeclareAttackers(final Game game, final Card c) { public static void checkDeclaredAttacker(final Game game, final Card c, final Combat combat) {
// Run triggers // Run triggers
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", c); runParams.put("Attacker", c);
final List<Card> otherAttackers = game.getCombat().getAttackers(); final List<Card> otherAttackers = combat.getAttackers();
otherAttackers.remove(c); otherAttackers.remove(c);
runParams.put("OtherAttackers", otherAttackers); runParams.put("OtherAttackers", otherAttackers);
runParams.put("Attacked", game.getCombat().getDefenderByAttacker(c)); runParams.put("Attacked", combat.getDefenderByAttacker(c));
game.getTriggerHandler().runTrigger(TriggerType.Attacks, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Attacks, runParams, false);
// Annihilator: // Annihilator:
@@ -965,7 +970,7 @@ public class CombatUtil {
@Override @Override
public void resolve() { public void resolve() {
this.api = ApiType.Sacrifice; this.api = ApiType.Sacrifice;
final Player opponent = game.getCombat().getDefendingPlayerRelatedTo(c).get(0); final Player opponent = combat.getDefendingPlayerRelatedTo(c);
//List<Card> list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this); //List<Card> list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this);
final List<Card> list = opponent.getCardsIn(ZoneType.Battlefield); final List<Card> list = opponent.getCardsIn(ZoneType.Battlefield);
List<Card> toSac = opponent.getController().choosePermanentsToSacrifice(this, a, a, list, "Card"); List<Card> toSac = opponent.getController().choosePermanentsToSacrifice(this, a, a, list, "Card");
@@ -1037,7 +1042,7 @@ public class CombatUtil {
* @param cl * @param cl
* a {@link forge.CardList} object. * a {@link forge.CardList} object.
*/ */
public static void checkDeclareBlockers(Game game, final List<Card> cl) { public static void checkDeclareBlockers(Game game, final List<Card> cl, Combat combat) {
for (final Card c : cl) { for (final Card c : cl) {
if (!c.getDamageHistory().getCreatureBlockedThisCombat()) { if (!c.getDamageHistory().getCreatureBlockedThisCombat()) {
for (final Ability ab : CardFactoryUtil.getBushidoEffects(c)) { for (final Ability ab : CardFactoryUtil.getBushidoEffects(c)) {
@@ -1046,7 +1051,7 @@ public class CombatUtil {
// Run triggers // Run triggers
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Blocker", c); runParams.put("Blocker", c);
final Card attacker = game.getCombat().getAttackersBlockedBy(c).get(0); final Card attacker = combat.getAttackersBlockedBy(c).get(0);
runParams.put("Attacker", attacker); runParams.put("Attacker", attacker);
game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false);
} }
@@ -1068,7 +1073,6 @@ public class CombatUtil {
* a {@link forge.Card} object. * a {@link forge.Card} object.
*/ */
public static void checkBlockedAttackers(final Game game, final Card a, final List<Card> blockers) { public static void checkBlockedAttackers(final Game game, final Card a, final List<Card> blockers) {
final Combat combat = game.getCombat();
if (blockers.isEmpty()) { if (blockers.isEmpty()) {
return; return;
} }
@@ -1095,7 +1099,7 @@ public class CombatUtil {
if (m.find()) { if (m.find()) {
final String[] k = keyword.split(" "); final String[] k = keyword.split(" ");
final int magnitude = Integer.valueOf(k[1]); final int magnitude = Integer.valueOf(k[1]);
final int numBlockers = combat.getBlockers(a).size(); final int numBlockers = blockers.size();
if (numBlockers > 1) { if (numBlockers > 1) {
CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers);
} }
@@ -1265,16 +1269,4 @@ public class CombatUtil {
} }
} }
public static void checkAttackOrBlockAlone(Combat combat) {
// Handles removing cards like Mogg Flunkies from combat if group attack
// didn't occur
for (Card c1 : combat.getAttackers()) {
if (c1.hasKeyword("CARDNAME can't attack or block alone.") && c1.isAttacking()) {
if (combat.getAttackers().size() < 2) {
combat.removeFromCombat(c1);
}
}
}
}
} // end class CombatUtil } // end class CombatUtil

View File

@@ -40,6 +40,7 @@ import forge.game.GameAge;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.event.GameEventAttackersDeclared; import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockerAssigned;
import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventBlockersDeclared;
import forge.game.event.GameEventPlayerPriority; import forge.game.event.GameEventPlayerPriority;
import forge.game.event.GameEventTurnBegan; import forge.game.event.GameEventTurnBegan;
@@ -91,7 +92,7 @@ public class PhaseHandler implements java.io.Serializable {
private Player pPlayerPriority = null; private Player pPlayerPriority = null;
private Player pFirstPriority = null; private Player pFirstPriority = null;
private boolean bCombat = false; private Combat combat = null;
private boolean bRepeatCleanup = false; private boolean bRepeatCleanup = false;
private Player playerDeclaresBlockers = null; private Player playerDeclaresBlockers = null;
@@ -184,9 +185,8 @@ public class PhaseHandler implements java.io.Serializable {
* *
* @return a boolean. * @return a boolean.
*/ */
public final boolean inCombat() { public final boolean inCombat() { return combat != null; }
return this.bCombat; public final Combat getCombat() { return this.combat; }
}
private void advanceToNextPhase() { private void advanceToNextPhase() {
PhaseType oldPhase = phase; PhaseType oldPhase = phase;
@@ -215,9 +215,6 @@ public class PhaseHandler implements java.io.Serializable {
this.turn++; this.turn++;
game.fireEvent(new GameEventTurnBegan(playerTurn, turn)); game.fireEvent(new GameEventTurnBegan(playerTurn, turn));
// Here's what happens on new turn, regardless of skipped phases
game.getCombat().reset(playerTurn);
// Tokens starting game in play should suffer from Sum. Sickness // Tokens starting game in play should suffer from Sum. Sickness
final List<Card> list = playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield); final List<Card> list = playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield);
for (final Card c : list) { for (final Card c : list) {
@@ -305,43 +302,45 @@ public class PhaseHandler implements java.io.Serializable {
break; break;
case COMBAT_DECLARE_ATTACKERS: case COMBAT_DECLARE_ATTACKERS:
this.bCombat = true; combat = new Combat(playerTurn);
game.getStack().freezeStack(); game.getStack().freezeStack();
declareAttackersTurnBasedActions(); declareAttackersTurnBasedAction();
game.getStack().unfreezeStack(); game.getStack().unfreezeStack();
this.bCombat = !game.getCombat().getAttackers().isEmpty(); if (combat != null && combat.getAttackers().isEmpty() )
givePriorityToPlayer = bCombat; combat = null;
givePriorityToPlayer = inCombat();
this.nCombatsThisTurn++; this.nCombatsThisTurn++;
break; break;
case COMBAT_DECLARE_BLOCKERS: case COMBAT_DECLARE_BLOCKERS:
game.getCombat().removeAbsentCombatants(); combat.removeAbsentCombatants();
game.getStack().freezeStack(); game.getStack().freezeStack();
declareBlockersTurnBaseActions(); declareBlockersTurnBasedAction();
game.getStack().unfreezeStack(); game.getStack().unfreezeStack();
break; break;
case COMBAT_FIRST_STRIKE_DAMAGE: case COMBAT_FIRST_STRIKE_DAMAGE:
game.getCombat().removeAbsentCombatants(); combat.removeAbsentCombatants();
// no first strikers, skip this step // no first strikers, skip this step
if (!game.getCombat().assignCombatDamage(true)) { if (!combat.assignCombatDamage(true)) {
this.givePriorityToPlayer = false; this.givePriorityToPlayer = false;
} else { } else {
game.getCombat().dealAssignedDamage(); combat.dealAssignedDamage();
game.getAction().checkStateEffects(); game.getAction().checkStateEffects();
} }
break; break;
case COMBAT_DAMAGE: case COMBAT_DAMAGE:
game.getCombat().removeAbsentCombatants(); combat.removeAbsentCombatants();
if (!game.getCombat().assignCombatDamage(false)) { if (!combat.assignCombatDamage(false)) {
this.givePriorityToPlayer = false; this.givePriorityToPlayer = false;
} else { } else {
game.getCombat().dealAssignedDamage(); combat.dealAssignedDamage();
game.getAction().checkStateEffects(); game.getAction().checkStateEffects();
} }
break; break;
@@ -445,9 +444,8 @@ public class PhaseHandler implements java.io.Serializable {
break; break;
case COMBAT_END: case COMBAT_END:
game.getCombat().reset(playerTurn); combat = null;
this.getPlayerTurn().resetAttackedThisCombat(); this.getPlayerTurn().resetAttackedThisCombat();
this.bCombat = false;
break; break;
case CLEANUP: case CLEANUP:
@@ -463,36 +461,36 @@ public class PhaseHandler implements java.io.Serializable {
} }
} }
private void declareAttackersTurnBasedActions() { private Combat declareAttackersTurnBasedAction() {
Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost() ? playerTurn : playerDeclaresAttackers; Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost() ? playerTurn : playerDeclaresAttackers;
whoDeclares.getController().declareAttackers(playerTurn); whoDeclares.getController().declareAttackers(playerTurn, combat);
if ( game.isGameOver() ) // they just like to close window at any moment if ( game.isGameOver() ) // they just like to close window at any moment
return; return null;
game.getCombat().removeAbsentCombatants(); combat.removeAbsentCombatants();
CombatUtil.checkAttackOrBlockAlone(game.getCombat()); CombatUtil.checkAttackOrBlockAlone(combat);
// TODO move propaganda to happen as the Attacker is Declared // TODO move propaganda to happen as the Attacker is Declared
for (final Card c2 : game.getCombat().getAttackers()) { for (final Card c2 : combat.getAttackers()) {
boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2); boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2, combat);
if ( canAttack ) { if ( canAttack ) {
if (!c2.hasKeyword("Vigilance")) if (!c2.hasKeyword("Vigilance"))
c2.tap(); c2.tap();
} else { } else {
game.getCombat().removeFromCombat(c2); combat.removeFromCombat(c2);
} }
} }
// Prepare and fire event 'attackers declared' // Prepare and fire event 'attackers declared'
MapOfLists<GameEntity, Card> attackersMap = new HashMapOfLists<GameEntity, Card>(CollectionSuppliers.<Card>arrayLists()); MapOfLists<GameEntity, Card> attackersMap = new HashMapOfLists<GameEntity, Card>(CollectionSuppliers.<Card>arrayLists());
for(GameEntity ge : game.getCombat().getDefenders()) attackersMap.addAll(ge, game.getCombat().getAttackersOf(ge)); for(GameEntity ge : combat.getDefenders()) attackersMap.addAll(ge, combat.getAttackersOf(ge));
game.fireEvent(new GameEventAttackersDeclared(playerTurn, attackersMap)); game.fireEvent(new GameEventAttackersDeclared(playerTurn, attackersMap));
// This Exalted handler should be converted to script // This Exalted handler should be converted to script
if (game.getCombat().getAttackers().size() == 1) { if (combat.getAttackers().size() == 1) {
final Player attackingPlayer = game.getCombat().getAttackingPlayer(); final Player attackingPlayer = combat.getAttackingPlayer();
final Card attacker = game.getCombat().getAttackers().get(0); final Card attacker = combat.getAttackers().get(0);
for (Card card : attackingPlayer.getCardsIn(ZoneType.Battlefield)) { for (Card card : attackingPlayer.getCardsIn(ZoneType.Battlefield)) {
int exaltedMagnitude = card.getKeywordAmount("Exalted"); int exaltedMagnitude = card.getKeywordAmount("Exalted");
if (exaltedMagnitude > 0) { if (exaltedMagnitude > 0) {
@@ -503,30 +501,34 @@ public class PhaseHandler implements java.io.Serializable {
// fire trigger // fire trigger
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attackers", game.getCombat().getAttackers()); runParams.put("Attackers", combat.getAttackers());
runParams.put("AttackingPlayer", game.getCombat().getAttackingPlayer()); runParams.put("AttackingPlayer", combat.getAttackingPlayer());
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
for (final Card c : game.getCombat().getAttackers()) { for (final Card c : combat.getAttackers()) {
CombatUtil.checkDeclareAttackers(game, c); CombatUtil.checkDeclaredAttacker(game, c, combat);
} }
return combat;
} }
private void declareBlockersTurnBaseActions() { private void declareBlockersTurnBasedAction() {
final Combat combat = game.getCombat();
Player p = playerTurn; Player p = playerTurn;
do { do {
p = game.getNextPlayerAfter(p); p = game.getNextPlayerAfter(p);
// Apply Odric's effect here // Apply Odric's effect here
Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers; Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers;
if ( combat.isPlayerAttacked(p) ) if ( combat.isPlayerAttacked(p) ) {
whoDeclaresBlockers.getController().declareBlockers(p); whoDeclaresBlockers.getController().declareBlockers(p, combat);
game.fireEvent(new GameEventBlockerAssigned()); //
}
if ( game.isGameOver() ) // they just like to close window at any moment if ( game.isGameOver() ) // they just like to close window at any moment
return; return;
} while(p != playerTurn); } while(p != playerTurn);
CombatUtil.orderMultipleBlockers(combat);
CombatUtil.orderBlockingMultipleAttackers(combat);
combat.removeAbsentCombatants(); combat.removeAbsentCombatants();
@@ -545,14 +547,14 @@ public class PhaseHandler implements java.io.Serializable {
} }
} }
for (Card c : filterList) { for (Card c : filterList) {
if (c.hasKeyword("CARDNAME can't attack or block alone.") && c.isBlocking()) { if (c.hasKeyword("CARDNAME can't attack or block alone.") && combat.isBlocking(c)) {
if (combat.getAllBlockers().size() < 2) { if (combat.getAllBlockers().size() < 2) {
combat.undoBlockingAssignment(c); combat.undoBlockingAssignment(c);
} }
} }
} }
combat.setUnblockedAttackers(); combat.onBlockersDeclared();
List<Card> list = combat.getAllBlockers(); List<Card> list = combat.getAllBlockers();
@@ -563,7 +565,7 @@ public class PhaseHandler implements java.io.Serializable {
} }
}); });
CombatUtil.checkDeclareBlockers(game, list); CombatUtil.checkDeclareBlockers(game, list, combat);
for (final Card a : combat.getAttackers()) { for (final Card a : combat.getAttackers()) {
CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a)); CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a));
@@ -571,10 +573,10 @@ public class PhaseHandler implements java.io.Serializable {
// map: defender => (many) attacker => (many) blocker // map: defender => (many) attacker => (many) blocker
Map<GameEntity, MapOfLists<Card, Card>> blockers = new HashMap<GameEntity, MapOfLists<Card,Card>>(); Map<GameEntity, MapOfLists<Card, Card>> blockers = new HashMap<GameEntity, MapOfLists<Card,Card>>();
for(GameEntity ge : game.getCombat().getDefenders()) { for(GameEntity ge : combat.getDefenders()) {
MapOfLists<Card, Card> protectThisDefender = new HashMapOfLists<Card, Card>(CollectionSuppliers.<Card>arrayLists()); MapOfLists<Card, Card> protectThisDefender = new HashMapOfLists<Card, Card>(CollectionSuppliers.<Card>arrayLists());
for(Card att : game.getCombat().getAttackersOf(ge)) { for(Card att : combat.getAttackersOf(ge)) {
protectThisDefender.addAll(att, game.getCombat().getBlockers(att)); protectThisDefender.addAll(att, combat.getBlockers(att));
} }
blockers.put(ge, protectThisDefender); blockers.put(ge, protectThisDefender);
} }
@@ -896,6 +898,7 @@ public class PhaseHandler implements java.io.Serializable {
setPlayerTurn(player0); setPlayerTurn(player0);
game.fireEvent(new GameEventTurnPhase(this.getPlayerTurn(), this.getPhase(), "")); game.fireEvent(new GameEventTurnPhase(this.getPlayerTurn(), this.getPhase(), ""));
combat = null; // not-null can be created only when declare attackers phase begins
} }
/** /**
@@ -904,6 +907,7 @@ public class PhaseHandler implements java.io.Serializable {
* @param phaseID the new phase state * @param phaseID the new phase state
*/ */
public final void endTurnByEffect() { public final void endTurnByEffect() {
this.combat = null;
this.phase = PhaseType.CLEANUP; this.phase = PhaseType.CLEANUP;
this.onPhaseBegin(); this.onPhaseBegin();
} }
@@ -947,5 +951,10 @@ public class PhaseHandler implements java.io.Serializable {
} }
public void endCombat() {
combat = null;
}
} }

View File

@@ -20,6 +20,7 @@ import forge.card.spellability.TargetChoices;
import forge.deck.Deck; import forge.deck.Deck;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -143,8 +144,8 @@ public abstract class PlayerController {
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question); public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer); public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
public abstract void declareAttackers(Player attacker); public abstract void declareAttackers(Player attacker, Combat combat);
public abstract void declareBlockers(Player defender); public abstract void declareBlockers(Player defender, Combat combat);
public abstract void takePriority(); public abstract void takePriority();
public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard); public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);

View File

@@ -32,9 +32,10 @@ import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.ai.AiController; import forge.game.ai.AiController;
import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtil;
import forge.game.ai.ComputerUtilBlock; import forge.game.ai.AiBlockController;
import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCombat;
import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilCost;
import forge.game.phase.Combat;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -166,12 +167,12 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public List<Card> orderBlockers(Card attacker, List<Card> blockers) { public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
return ComputerUtilBlock.orderBlockers(attacker, blockers); return AiBlockController.orderBlockers(attacker, blockers);
} }
@Override @Override
public List<Card> orderAttackers(Card blocker, List<Card> attackers) { public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
return ComputerUtilBlock.orderAttackers(blocker, attackers); return AiBlockController.orderAttackers(blocker, attackers);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -322,14 +323,14 @@ public class PlayerControllerAi extends PlayerController {
} }
@Override @Override
public void declareAttackers(Player attacker) { public void declareAttackers(Player attacker, Combat combat) {
brains.declareAttackers(attacker); brains.declareAttackers(attacker, combat);
} }
@Override @Override
public void declareBlockers(Player defender) { public void declareBlockers(Player defender, Combat combat) {
brains.declateBlockers(defender); brains.declareBlockersFor(defender, combat);
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.phase.Combat;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiChoose; import forge.gui.GuiChoose;
@@ -45,7 +46,6 @@ import forge.gui.input.InputPassPriority;
import forge.gui.input.InputPlayOrDraw; import forge.gui.input.InputPlayOrDraw;
import forge.gui.input.InputSelectCards; import forge.gui.input.InputSelectCards;
import forge.gui.input.InputSelectCardsFromList; import forge.gui.input.InputSelectCardsFromList;
import forge.gui.input.InputSynchronized;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
@@ -525,21 +525,19 @@ public class PlayerControllerHuman extends PlayerController {
} }
@Override @Override
public void declareAttackers(Player attacker) { public void declareAttackers(Player attacker, Combat combat) {
game.getCombat().initiatePossibleDefenders(attacker.getOpponents());
// This input should not modify combat object itself, but should return user choice // This input should not modify combat object itself, but should return user choice
InputSynchronized inpAttack = new InputAttack(attacker, player, game.getCombat()); InputAttack inpAttack = new InputAttack(attacker, player, combat);
Singletons.getControl().getInputQueue().setInputAndWait(inpAttack); Singletons.getControl().getInputQueue().setInputAndWait(inpAttack);
} }
@Override @Override
public void declareBlockers(Player defender) { public void declareBlockers(Player defender, Combat combat) {
// This input should not modify combat object itself, but should return user choice // This input should not modify combat object itself, but should return user choice
InputSynchronized inpBlock = new InputBlock(player, defender, game.getCombat()); InputBlock inpBlock = new InputBlock(player, defender, combat);
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock); Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
} }
@Override @Override
public void takePriority() { public void takePriority() {
PhaseType phase = game.getPhaseHandler().getPhase(); PhaseType phase = game.getPhaseHandler().getPhase();

View File

@@ -151,7 +151,7 @@ public final class GuiDisplayUtil {
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn); game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
game.getCombat().reset(game.getPhaseHandler().getPlayerTurn());
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
devSetupPlayerState(humanLife, humanCardTexts, human); devSetupPlayerState(humanLife, humanCardTexts, human);

View File

@@ -129,10 +129,10 @@ public class InputAttack extends InputSyncronizedBase {
return; return;
} }
if (card.isAttacking(currentDefender)) { if (combat.isAttacking(card, currentDefender)) {
// Activate band by selecting/deselecting a band member // Activate band by selecting/deselecting a band member
if (this.activeBand == null) { if (this.activeBand == null) {
this.activateBand(combat.getBandByAttacker(card)); this.activateBand(combat.getBandOfAttacker(card));
} else if (this.activeBand.getAttackers().contains(card)) { } else if (this.activeBand.getAttackers().contains(card)) {
this.activateBand(null); this.activateBand(null);
} else { // Join a band by selecting a non-active band member after activating a band } else { // Join a band by selecting a non-active band member after activating a band

View File

@@ -17,16 +17,15 @@
*/ */
package forge.gui.input; package forge.gui.input;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import forge.Card; import forge.Card;
import forge.Singletons;
import forge.game.phase.Combat; import forge.game.phase.Combat;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.GuiDialog;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.sound.SoundEffectType;
import forge.view.ButtonUtil; import forge.view.ButtonUtil;
/** /**
@@ -42,7 +41,7 @@ public class InputBlock extends InputSyncronizedBase {
private static final long serialVersionUID = 6120743598368928128L; private static final long serialVersionUID = 6120743598368928128L;
private Card currentAttacker = null; private Card currentAttacker = null;
private final HashMap<Card, List<Card>> allBlocking = new HashMap<Card, List<Card>>(); // some cards may block several creatures at a time. (ex: Two-Headed Dragon, Vanguard's Shield)
private final Combat combat; private final Combat combat;
private final Player defender; private final Player defender;
private final Player declarer; private final Player declarer;
@@ -57,10 +56,6 @@ public class InputBlock extends InputSyncronizedBase {
this.combat = combat; this.combat = combat;
} }
private final void removeFromAllBlocking(final Card c) {
this.allBlocking.remove(c);
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
protected final void showMessage() { protected final void showMessage() {
@@ -90,14 +85,14 @@ public class InputBlock extends InputSyncronizedBase {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public final void onOk() { public final void onOk() {
if (CombatUtil.finishedMandatoryBlocks(combat, defender)) { String blockErrors = CombatUtil.validateBlocks(combat, defender);
if( null == blockErrors ) {
// Done blocking // Done blocking
ButtonUtil.reset(); ButtonUtil.reset();
CombatUtil.orderMultipleCombatants(combat); setCurrentAttacker(null);
currentAttacker = null;
allBlocking.clear();
stop(); stop();
} else {
GuiDialog.message(blockErrors);
} }
} }
@@ -105,43 +100,48 @@ public class InputBlock extends InputSyncronizedBase {
@Override @Override
public final void onCardSelected(final Card card, boolean isMetaDown) { public final void onCardSelected(final Card card, boolean isMetaDown) {
if (isMetaDown) { if (isMetaDown && card.getController() == defender) {
if (card.getController() == defender ) {
combat.removeFromCombat(card); combat.removeFromCombat(card);
}
removeFromAllBlocking(card);
CMatchUI.SINGLETON_INSTANCE.showCombat(); CMatchUI.SINGLETON_INSTANCE.showCombat();
return; return;
} }
// is attacking? // is attacking?
boolean reminder = true; boolean isCorrectAction = false;
if (combat.getAttackers().contains(card)) { if (combat.isAttacking(card)) {
this.currentAttacker = card; setCurrentAttacker(card);
reminder = false; isCorrectAction = true;
} else { } else {
// Make sure this card is valid to even be a blocker // Make sure this card is valid to even be a blocker
if (this.currentAttacker != null && card.isCreature() && defender.getZone(ZoneType.Battlefield).contains(card)) { if (this.currentAttacker != null && card.isCreature() && defender.getZone(ZoneType.Battlefield).contains(card)) {
// Create a new blockedBy list if it doesn't exist isCorrectAction = CombatUtil.canBlock(this.currentAttacker, card, combat);
if (!this.allBlocking.containsKey(card)) { if ( isCorrectAction ) {
this.allBlocking.put(card, new ArrayList<Card>());
}
List<Card> attackersBlocked = this.allBlocking.get(card);
if (!attackersBlocked.contains(this.currentAttacker)
&& CombatUtil.canBlock(this.currentAttacker, card, combat)) {
attackersBlocked.add(this.currentAttacker);
combat.addBlocker(this.currentAttacker, card); combat.addBlocker(this.currentAttacker, card);
reminder = false; // This call is performed from GUI and is not intended to propagate to log or net.
// No need to use event bus then
Singletons.getControl().getSoundSystem().play(SoundEffectType.Block);
} }
} }
} }
if (reminder) { if (!isCorrectAction) {
flashIncorrectAction(); flashIncorrectAction();
} }
this.showMessage(); this.showMessage();
} // selectCard() } // selectCard()
private void setCurrentAttacker(Card card) {
currentAttacker = card;
Player attacker = null;
for(Card c : combat.getAttackers()) {
c.setUsedToPay(card == c);
if ( attacker == null )
attacker = c.getController();
}
// request redraw from here
attacker.getZone(ZoneType.Battlefield).updateObservers();
}
} }

View File

@@ -261,7 +261,7 @@ public enum CMatchUI {
public void showCombat() { public void showCombat() {
if ( CCombat.SINGLETON_INSTANCE.hasCombatToShow() ) { if (Singletons.getControl().getObservedGame().getPhaseHandler().inCombat()) {
SDisplayUtil.showTab(EDocID.REPORT_COMBAT.getDoc()); SDisplayUtil.showTab(EDocID.REPORT_COMBAT.getDoc());
} }
CCombat.SINGLETON_INSTANCE.update(); CCombat.SINGLETON_INSTANCE.update();

View File

@@ -339,12 +339,12 @@ public enum TargetingOverlay {
if (overlaystate == 0) { return; } if (overlaystate == 0) { return; }
// Arc drawing // Arc drawing
if( null != combat )
assembleArcs(combat); assembleArcs(combat);
if (arcs.isEmpty()) { return; } if (arcs.isEmpty()) { return; }
Graphics2D g2d = (Graphics2D) g; Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints.VALUE_ANTIALIAS_ON);
Color color = FSkin.getColor(FSkin.Colors.CLR_ACTIVE); Color color = FSkin.getColor(FSkin.Colors.CLR_ACTIVE);
for (Point[] p : arcs) { for (Point[] p : arcs) {

View File

@@ -9,7 +9,6 @@ import forge.game.Game;
import forge.game.combat.AttackingBand; import forge.game.combat.AttackingBand;
import forge.game.phase.Combat; import forge.game.phase.Combat;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.gui.framework.ICDoc; import forge.gui.framework.ICDoc;
import forge.gui.match.views.VCombat; import forge.gui.match.views.VCombat;
@@ -42,20 +41,14 @@ public enum CCombat implements ICDoc {
public void initialize() { public void initialize() {
} }
public final boolean hasCombatToShow() {
PhaseHandler pH = game.getPhaseHandler();
PhaseType ph = pH.getPhase();
return game.getCombat().isCombat() && ph.isAfter(PhaseType.COMBAT_BEGIN) && ph.isBefore(PhaseType.END_OF_TURN);
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.gui.framework.ICDoc#update() * @see forge.gui.framework.ICDoc#update()
*/ */
@Override @Override
public void update() { public void update() {
if (hasCombatToShow()) // display combat PhaseHandler pH = game.getPhaseHandler();
VCombat.SINGLETON_INSTANCE.updateCombat(game.getCombat().getAttackers().size(), getCombatDescription(game.getCombat())); if (pH.inCombat()) // display combat
VCombat.SINGLETON_INSTANCE.updateCombat(pH.getCombat().getAttackers().size(), getCombatDescription(pH.getCombat()));
else else
VCombat.SINGLETON_INSTANCE.updateCombat(0, ""); VCombat.SINGLETON_INSTANCE.updateCombat(0, "");
} }
@@ -99,7 +92,7 @@ public enum CCombat implements ICDoc {
if (isBand) { if (isBand) {
// Only print Band data if it's actually a band // Only print Band data if it's actually a band
display.append(" > BAND"); display.append(" > BAND");
if (band.getBlocked()) { if (band.isBlocked()) {
display.append(" (blocked)"); display.append(" (blocked)");
} }
display.append("\n"); display.append("\n");
@@ -110,14 +103,14 @@ public enum CCombat implements ICDoc {
display.append(combatantToString(c)).append("\n"); display.append(combatantToString(c)).append("\n");
} }
if (!isBand && band.getBlockers().isEmpty()) { List<Card> blockers = combat.getBlockers(band);
if (!isBand && blockers.isEmpty()) {
// if single creature is blocked, but no longer has blockers, tell the user! // if single creature is blocked, but no longer has blockers, tell the user!
if (band.getBlocked()) { if (band.isBlocked())
display.append(" (blocked) "); display.append(" (blocked)\n");
}
} }
for (final Card element : band.getBlockers()) { for (final Card element : blockers) {
display.append(" < ").append(combatantToString(element)).append("\n"); display.append(" < ").append(combatantToString(element)).append("\n");
} }
previousBand = isBand; previousBand = isBand;

View File

@@ -35,6 +35,7 @@ import forge.CardPredicates.Presets;
import forge.Command; import forge.Command;
import forge.deck.Deck; import forge.deck.Deck;
import forge.game.Game; import forge.game.Game;
import forge.game.phase.Combat;
import forge.game.phase.CombatUtil; import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -184,17 +185,19 @@ public enum CDock implements ICDoc {
public void alphaStrike() { public void alphaStrike() {
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
Player p = findAffectedPlayer(); final Player p = findAffectedPlayer();
if (ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, p)) { // ph.is(...) includes null check final Game game = p.getGame();
Combat combat = game.getCombat();
if (ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, p) && combat!= null) { // ph.is(...) includes null check
List<Player> defenders = p.getOpponents(); List<Player> defenders = p.getOpponents();
for (Card c : CardLists.filter(p.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) { for (Card c : CardLists.filter(p.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) {
if (c.isAttacking()) if (combat.isAttacking(c))
continue; continue;
for(Player defender : defenders) for(Player defender : defenders)
if( CombatUtil.canAttack(c, defender, game.getCombat())) { if( CombatUtil.canAttack(c, defender, combat)) {
game.getCombat().addAttacker(c, defender); combat.addAttacker(c, defender);
break; break;
} }
} }

View File

@@ -3,7 +3,6 @@ package forge.sound;
import forge.Card; import forge.Card;
import forge.Singletons; import forge.Singletons;
import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbility;
import forge.game.event.GameEventBlockerAssigned;
import forge.game.event.GameEventCardChangeZone; import forge.game.event.GameEventCardChangeZone;
import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardDamaged;
import forge.game.event.GameEventCardDestroyed; import forge.game.event.GameEventCardDestroyed;
@@ -32,8 +31,6 @@ import forge.game.zone.ZoneType;
*/ */
public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> { public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> {
public SoundEffectType visit(GameEventBlockerAssigned event) { return SoundEffectType.Block; }
public SoundEffectType visit(GameEventCardDamaged event) { return SoundEffectType.Damage; } public SoundEffectType visit(GameEventCardDamaged event) { return SoundEffectType.Damage; }
public SoundEffectType visit(GameEventCardDestroyed event) { return SoundEffectType.Destroy; } public SoundEffectType visit(GameEventCardDestroyed event) { return SoundEffectType.Destroy; }
public SoundEffectType visit(GameEventCardEquipped event) { return SoundEffectType.Equip; } public SoundEffectType visit(GameEventCardEquipped event) { return SoundEffectType.Equip; }

View File

@@ -28,7 +28,7 @@ public class EnumMapOfLists<K extends Enum<K>, V> extends EnumMap<K, Collection<
this.factory = factory; this.factory = factory;
} }
private Collection<V> ensureCollectionFor(K key) { public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key); Collection<V> value = get(key);
if ( value == null ) { if ( value == null ) {
value = factory.get(); value = factory.get();

View File

@@ -32,7 +32,7 @@ public class HashMapOfLists<K, V> extends HashMap<K, Collection<V>> implements M
private static final long serialVersionUID = 3029089910183132930L; private static final long serialVersionUID = 3029089910183132930L;
private Collection<V> ensureCollectionFor(K key) { public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key); Collection<V> value = get(key);
if ( value == null ) { if ( value == null ) {
value = factory.get(); value = factory.get();

View File

@@ -6,4 +6,5 @@ import java.util.Map;
public interface MapOfLists<K, V> extends Map<K, Collection<V>> { public interface MapOfLists<K, V> extends Map<K, Collection<V>> {
void add(K key, V element); void add(K key, V element);
void addAll(K key, Collection<V> element); void addAll(K key, Collection<V> element);
Collection<V> ensureCollectionFor(K key);
} }

View File

@@ -33,7 +33,7 @@ public class TreeMapOfLists<K, V> extends TreeMap<K, Collection<V>> implements M
this.factory = factory; this.factory = factory;
} }
private Collection<V> ensureCollectionFor(K key) { public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key); Collection<V> value = get(key);
if ( value == null ) { if ( value == null ) {
value = factory.get(); value = factory.get();

View File

@@ -42,6 +42,7 @@ import forge.ImageCache;
import forge.Singletons; import forge.Singletons;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.phase.Combat;
import forge.gui.CardContainer; import forge.gui.CardContainer;
import forge.gui.toolbox.CardFaceSymbols; import forge.gui.toolbox.CardFaceSymbols;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
@@ -407,9 +408,11 @@ public class CardPanel extends JPanel implements CardContainer {
final int stateXSymbols = (this.cardXOffset + (this.cardWidth / 2)) - 16; final int stateXSymbols = (this.cardXOffset + (this.cardWidth / 2)) - 16;
final int ySymbols = (this.cardYOffset + this.cardHeight) - (this.cardHeight / 8) - 16; final int ySymbols = (this.cardYOffset + this.cardHeight) - (this.cardHeight / 8) - 16;
// int yOff = (cardHeight/4) + 2; // int yOff = (cardHeight/4) + 2;
if (card.isAttacking()) { Combat combat = card.getGame().getCombat();
if( combat != null ) {
if ( combat.isAttacking(card))
CardFaceSymbols.drawSymbol("attack", g, combatXSymbols, ySymbols); CardFaceSymbols.drawSymbol("attack", g, combatXSymbols, ySymbols);
} else if (card.isBlocking()) { if ( combat.isBlocking(card))
CardFaceSymbols.drawSymbol("defend", g, combatXSymbols, ySymbols); CardFaceSymbols.drawSymbol("defend", g, combatXSymbols, ySymbols);
} }