diff --git a/.gitattributes b/.gitattributes
index 2fe032880b3..e375c6e132f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,6 +15,7 @@ forge-ai/.settings/org.eclipse.m2e.core.prefs -text
forge-ai/pom.xml -text
forge-ai/src/main/java/forge/ai/AiAttackController.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/AiBlockController.java svneol=native#text/plain
+forge-ai/src/main/java/forge/ai/AiCardMemory.java -text
forge-ai/src/main/java/forge/ai/AiController.java svneol=native#text/plain
forge-ai/src/main/java/forge/ai/AiCostDecision.java -text
forge-ai/src/main/java/forge/ai/AiPlayDecision.java -text
diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 86f386a3fdb..e40c8f97655 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -607,6 +607,16 @@ public class AiAttackController {
return;
}
+ // Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
+ AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
+ for (Card attacker : this.attackers) {
+ if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
+ combat.addAttacker(attacker, defender);
+ attackersLeft.remove(attacker);
+ }
+ }
+ aiMemory.clearRememberedAttackers(); // avoid "leaking" remembered cards over to the next turn
+
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = false;
diff --git a/forge-ai/src/main/java/forge/ai/AiCardMemory.java b/forge-ai/src/main/java/forge/ai/AiCardMemory.java
new file mode 100644
index 00000000000..a9eef71c99e
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/AiCardMemory.java
@@ -0,0 +1,344 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package forge.ai;
+
+import forge.game.card.Card;
+import forge.game.player.Player;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ *
+ * AiCardMemory class.
+ *
+ *
+ * A simple class that allows the AI to "memorize" different cards on the battlefield (and possibly in other zones
+ * too, for instance as revealed from the opponent's hand) and assign them to different memory sets in order to help
+ * make somewhat more "educated" decisions to attack with certain cards or play certain spell abilities. Each
+ * AiController has its own memory that is created when the AI player is spawned. The card memory is accessible
+ * via AiController.getCardMemory. AiCardMemory can memorize cards belonging to different players. For example,
+ * it's possible to distinctly store cards revealed by different players in a single AiCardMemory.
+ * Methods without the Player parameter operate on the "default" memory sets that belong directly to the owner
+ * of the card memory (the AI player itself).
+ *
+ * @author Forge
+ */
+public class AiCardMemory {
+
+ private HashMap> mapMandatoryAttackers = new HashMap>();
+ private HashMap> mapHeldManaSources = new HashMap>();
+ private HashMap> mapRevealedCards = new HashMap>();
+ private Player self = null;
+
+ /**
+ * Defines the memory set in which the card is remembered
+ * (which, in its turn, defines how the AI utilizes the information
+ * about remembered cards).
+ */
+ public enum MemorySet {
+ MANDATORY_ATTACKERS,
+ HELD_MANA_SOURCES, // stub, not linked to AI code yet
+ REVEALED_CARDS; // stub, not linked to AI code yet
+ }
+
+ public AiCardMemory(Player self) {
+ this.self = self;
+ }
+
+ private Set getMemorySet(Player p, MemorySet set) {
+ if (p == null) {
+ return null;
+ }
+
+ switch (set) {
+ case MANDATORY_ATTACKERS:
+ if (!mapMandatoryAttackers.containsKey(p)) {
+ mapMandatoryAttackers.put(p, new HashSet());
+ }
+ return mapMandatoryAttackers.get(p);
+ case HELD_MANA_SOURCES:
+ if (!mapHeldManaSources.containsKey(p)) {
+ mapHeldManaSources.put(p, new HashSet());
+ }
+ return mapHeldManaSources.get(p);
+ case REVEALED_CARDS:
+ if (!mapRevealedCards.containsKey(p)) {
+ mapRevealedCards.put(p, new HashSet());
+ }
+ return mapRevealedCards.get(p);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the given card was remembered in a certain memory set that stores cards
+ * memorized from a particular player.
+ *
+ * @param p
+ * player that was the controller of the card at the time the card was remembered
+ * @param c
+ * the card
+ * @param set the memory set that is to be checked
+ * @return true, if the card is remembered in the given memory set
+ */
+ public boolean isRememberedCard(Player p, Card c, MemorySet set) {
+ if (c == null || p == null) {
+ return false;
+ }
+
+ Set memorySet = getMemorySet(p, set);
+
+ return memorySet == null ? false : memorySet.contains(c);
+ }
+
+ /**
+ * Checks if the given card was remembered in a certain memory set for the default player (owner of this card memory).
+ *
+ * @param c
+ * the card
+ * @param set the memory set that is to be checked
+ * @return true, if the card is remembered in the given memory set
+ */
+ public boolean isRememberedCard(Card c, MemorySet set) {
+ return isRememberedCard(self, c, set);
+ }
+
+ /**
+ * Checks if at least one card of the given name was remembered in a certain memory set
+ * that stores cards memorized from a particular player.
+ *
+ * @param p
+ * player that was the controller of the card at the time the card was remembered
+ * @param cardName
+ * the card name
+ * @param set the memory set that is to be checked
+ * @return true, if at least one card with the given name is remembered in the given memory set
+ */
+ public boolean isRememberedCardByName(Player p, String cardName, MemorySet set) {
+ if (p == null) {
+ return false;
+ }
+
+ Set memorySet = getMemorySet(p, set);
+ Iterator it = memorySet.iterator();
+
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getName().equals(cardName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if at least one card of the given name was remembered in a certain memory set for the default player (owner of this card memory).
+ *
+ * @param cardName
+ * the card name
+ * @param set the memory set that is to be checked
+ * @return true, if at least one card with the given name is remembered in the given memory set
+ */
+ public boolean isRememberedCardByName(String cardName, MemorySet set) {
+ return isRememberedCardByName(self, cardName, set);
+ }
+
+ /**
+ * Remembers the given card in the given memory set that stores cards memorized from a particular player.
+ * @param p
+ * player that is the controller of the card at the time the card is remembered
+ * @param c
+ * the card
+ * @param set the memory set to remember the card in
+ * @return true, if the card is successfully stored in the given memory set
+ */
+ public boolean rememberCard(Player p, Card c, MemorySet set) {
+ if (c == null || p == null)
+ return false;
+
+ //System.out.println ("AiCardMemory: remembering card " + c.getName() + "(ID=" + c.getUniqueNumber() + ")" + " for player " + p.getName() + ", set = " + set.name() + ".");
+
+ getMemorySet(p, set).add(c);
+ return true;
+ }
+
+ /**
+ * Remembers the given card in the given memory set for the default player (owner of this card memory).
+ * @param c
+ * the card
+ * @param set the memory set to remember the card in
+ * @return true, if the card is successfully stored in the given memory set
+ */
+ public boolean rememberCard(Card c, MemorySet set) {
+ return rememberCard(self, c, set);
+ }
+
+ /**
+ * Forgets the given card in the given memory set that stores cards memorized from a particular player.
+ * @param p
+ * player that is the controller of the card at the time the card is remembered
+ * @param c
+ * the card
+ * @param set the memory set to forget the card in
+ * @return true, if the card was previously remembered in the given memory set and was successfully forgotten
+ */
+ public boolean forgetCard(Player p, Card c, MemorySet set) {
+ if (c == null || p == null) {
+ return false;
+ }
+ if (!isRememberedCard(p, c, set)) {
+ return false;
+ }
+
+ //System.out.println ("AiCardMemory: forgetting card " + c.getName() + "(ID=" + c.getUniqueNumber() + ")" + " for player " + p.getName() + ", set = " + set.name() + ".");
+
+ getMemorySet(p, set).remove(c);
+ return true;
+ }
+
+ /**
+ * Forgets the given card in the given memory set for the default player (owner of this card memory).
+ * @param c
+ * the card
+ * @param set the memory set to forget the card in
+ * @return true, if the card was previously remembered in the given memory set and was successfully forgotten
+ */
+ public boolean forgetCard(Card c, MemorySet set) {
+ return forgetCard(self, c, set);
+ }
+
+ /**
+ * Forgets a single card with the given name in the given memory set that stores cards memorized from a particular player.
+ * @param p
+ * player that is the controller of the card at the time the card is remembered
+ * @param cardName
+ * the card name
+ * @param set the memory set to forget the card in
+ * @return true, if at least one card with the given name was previously remembered in the given memory set and was successfully forgotten
+ */
+ public boolean forgetAnyCardWithName(Player p, String cardName, MemorySet set) {
+ if (p == null) {
+ return false;
+ }
+
+ Set memorySet = getMemorySet(p, set);
+ Iterator it = memorySet.iterator();
+
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getName().equals(cardName)) {
+ return forgetCard(p, c, set);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Forgets a single card with the given name in the given memory set for the default player (owner of this card memory).
+ *
+ * @param cardName
+ * the card name
+ * @param set the memory set to forget the card in
+ * @return true, if at least one card with the given name was previously remembered in the given memory set and was successfully forgotten
+ */
+ public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
+ return forgetAnyCardWithName(self, cardName, set);
+ }
+
+ /**
+ * Clears the "remembered attackers" memory set stored in this card memory for the given player.
+ * @param p
+ * player for whom the remembered attackers are to be cleared
+ */
+ public void clearRememberedAttackers(Player p) {
+ getMemorySet(p, MemorySet.MANDATORY_ATTACKERS).clear();
+ }
+
+ /**
+ * Clears the "remembered attackers" memory set for the default player (owner of this card memory).
+ */
+ public void clearRememberedAttackers() {
+ clearRememberedAttackers(self);
+ }
+
+ /**
+ * Clears the "remembered mana sources" memory set stored in this card memory for the given player.
+ * @param p
+ * player for whom the remembered attackers are to be cleared
+ */
+ public void clearRememberedManaSources(Player p) {
+ getMemorySet(p, MemorySet.HELD_MANA_SOURCES).clear();
+ }
+
+ /**
+ * Clears the "remembered mana sources" memory set for the default player (owner of this card memory).
+ */
+ public void clearRememberedManaSources() {
+ clearRememberedManaSources(self);
+ }
+
+ /**
+ * Clears the "remembered revealed cards" memory set stored in this card memory for the given player.
+ * @param p
+ * player for whom the remembered attackers are to be cleared
+ */
+ public void clearRememberedRevealedCards(Player p) {
+ getMemorySet(p, MemorySet.REVEALED_CARDS).clear();
+ }
+
+ /**
+ * Clears the "remembered revealed cards" memory set for the default player (owner of this card memory).
+ */
+ public void clearRememberedRevealedCards() {
+ clearRememberedRevealedCards(self);
+ }
+
+ /**
+ * Clears all memory sets stored in this card memory for the given player.
+ * @param p
+ * player for whom the remembered attackers are to be cleared
+ */
+ public void clearAllRemembered(Player p) {
+ clearRememberedAttackers(p);
+ clearRememberedManaSources(p);
+ clearRememberedRevealedCards(p);
+ }
+
+ /**
+ * Clears all memory sets stored for the default player (owner of this card memory).
+ */
+ public void clearAllRemembered() {
+ clearAllRemembered(self);
+ }
+
+ /**
+ * Clears all memory sets stored for all players in this card memory.
+ */
+ public void wipeMemory() {
+ mapMandatoryAttackers.clear();
+ mapHeldManaSources.clear();
+ mapRevealedCards.clear();
+ }
+}
\ No newline at end of file
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index cafb8ba1704..012a1e42bc9 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -94,6 +94,7 @@ public class AiController {
private final Player player;
private final Game game;
+ private final AiCardMemory memory;
public boolean bCheatShuffle;
public boolean canCheatShuffle() {
@@ -114,6 +115,10 @@ public class AiController {
return player;
}
+ public AiCardMemory getCardMemory() {
+ return memory;
+ }
+
/**
*
* Constructor for ComputerAI_General.
@@ -122,6 +127,7 @@ public class AiController {
public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer;
game = game0;
+ memory = new AiCardMemory(player);
}
/**
@@ -1146,18 +1152,18 @@ public class AiController {
SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards));
if( counter != null ) return counter;
- SpellAbility counterETB = chooseSpellAbilyToPlay(this.getPossibleETBCounters(), false);
+ SpellAbility counterETB = chooseSpellAbilityToPlay(this.getPossibleETBCounters(), false);
if( counterETB != null )
return counterETB;
}
- SpellAbility result = chooseSpellAbilyToPlay(getSpellAbilities(cards), true);
+ SpellAbility result = chooseSpellAbilityToPlay(getSpellAbilities(cards), true);
if( null == result)
return null;
return result;
}
- private SpellAbility chooseSpellAbilyToPlay(final ArrayList all, boolean skipCounter) {
+ private SpellAbility chooseSpellAbilityToPlay(final ArrayList all, boolean skipCounter) {
if ( all == null || all.isEmpty() )
return null;
diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
index 44e762941d5..dbbcc1fb173 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
@@ -19,8 +19,10 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilCard;
+import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -168,6 +170,10 @@ public class ControlGainAi extends SpellAbilityAi {
if (hasCreature) {
t = ComputerUtilCard.getBestCreatureAI(list);
+ if (lose != null && lose.contains("EOT")) {
+ // Remember to always attack with this creature since it'll bounce back to its owner at end of turn anyway
+ ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().rememberCard(t, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
+ }
} else if (hasArtifact) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (hasLand) {