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) {