diff --git a/Mage.Player.AI/build.xml b/Mage.Player.AI/build.xml
new file mode 100644
index 0000000000..78f55aa6f7
--- /dev/null
+++ b/Mage.Player.AI/build.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+ Builds, tests, and runs the project Mage.Player.AI.
+
+
+
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/Attackers.class b/Mage.Player.AI/build/classes/mage/player/ai/Attackers.class
new file mode 100644
index 0000000000..a1fad27a0a
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/Attackers.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$1.class b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$1.class
new file mode 100644
index 0000000000..58f7d6ae4a
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$1.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$2.class b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$2.class
new file mode 100644
index 0000000000..69df3a0c18
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer$2.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer.class b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer.class
new file mode 100644
index 0000000000..a2acb5bd99
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/ComputerPlayer.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/PermanentComparator.class b/Mage.Player.AI/build/classes/mage/player/ai/PermanentComparator.class
new file mode 100644
index 0000000000..d19d66fce9
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/PermanentComparator.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/PermanentEvaluator.class b/Mage.Player.AI/build/classes/mage/player/ai/PermanentEvaluator.class
new file mode 100644
index 0000000000..13c61712a5
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/PermanentEvaluator.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatGroupSimulator.class b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatGroupSimulator.class
new file mode 100644
index 0000000000..1a0768e0c5
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatGroupSimulator.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatSimulator.class b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatSimulator.class
new file mode 100644
index 0000000000..b22dcacfdc
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CombatSimulator.class differ
diff --git a/Mage.Player.AI/build/classes/mage/player/ai/simulators/CreatureSimulator.class b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CreatureSimulator.class
new file mode 100644
index 0000000000..082f6cacb9
Binary files /dev/null and b/Mage.Player.AI/build/classes/mage/player/ai/simulators/CreatureSimulator.class differ
diff --git a/Mage.Player.AI/dist/Mage.Player.AI.jar b/Mage.Player.AI/dist/Mage.Player.AI.jar
new file mode 100644
index 0000000000..630a4bfcf0
Binary files /dev/null and b/Mage.Player.AI/dist/Mage.Player.AI.jar differ
diff --git a/Mage.Player.AI/dist/README.TXT b/Mage.Player.AI/dist/README.TXT
new file mode 100644
index 0000000000..2b36151552
--- /dev/null
+++ b/Mage.Player.AI/dist/README.TXT
@@ -0,0 +1,33 @@
+========================
+BUILD OUTPUT DESCRIPTION
+========================
+
+When you build an Java application project that has a main class, the IDE
+automatically copies all of the JAR
+files on the projects classpath to your projects dist/lib folder. The IDE
+also adds each of the JAR files to the Class-Path element in the application
+JAR files manifest file (MANIFEST.MF).
+
+To run the project from the command line, go to the dist folder and
+type the following:
+
+java -jar "Mage.Player.AI.jar"
+
+To distribute this project, zip up the dist folder (including the lib folder)
+and distribute the ZIP file.
+
+Notes:
+
+* If two JAR files on the project classpath have the same name, only the first
+JAR file is copied to the lib folder.
+* Only JAR files are copied to the lib folder.
+If the classpath contains other types of files or folders, none of the
+classpath elements are copied to the lib folder. In such a case,
+you need to copy the classpath elements to the lib folder manually after the build.
+* If a library on the projects classpath also has a Class-Path element
+specified in the manifest,the content of the Class-Path element has to be on
+the projects runtime path.
+* To set a main class in a standard Java project, right-click the project node
+in the Projects window and choose Properties. Then click Run and enter the
+class name in the Main Class field. Alternatively, you can manually type the
+class name in the manifest Main-Class element.
diff --git a/Mage.Player.AI/dist/lib/Mage.jar b/Mage.Player.AI/dist/lib/Mage.jar
new file mode 100644
index 0000000000..595bedaed3
Binary files /dev/null and b/Mage.Player.AI/dist/lib/Mage.jar differ
diff --git a/Mage.Player.AI/manifest.mf b/Mage.Player.AI/manifest.mf
new file mode 100644
index 0000000000..1574df4a2d
--- /dev/null
+++ b/Mage.Player.AI/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/Mage.Player.AI/nbproject/build-impl.xml b/Mage.Player.AI/nbproject/build-impl.xml
new file mode 100644
index 0000000000..36dc8b1c15
--- /dev/null
+++ b/Mage.Player.AI/nbproject/build-impl.xml
@@ -0,0 +1,704 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.src.dir
+ Must set test.test.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+
+
+
+
+
+ java -cp "${run.classpath.with.dist.jar}" ${main.class}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Mage.Player.AI/nbproject/genfiles.properties b/Mage.Player.AI/nbproject/genfiles.properties
new file mode 100644
index 0000000000..f63fcf5a31
--- /dev/null
+++ b/Mage.Player.AI/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=3dcf0fdd
+build.xml.script.CRC32=b5a10489
+build.xml.stylesheet.CRC32=958a1d3e@1.26.2.45
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=3dcf0fdd
+nbproject/build-impl.xml.script.CRC32=8d227e35
+nbproject/build-impl.xml.stylesheet.CRC32=5c621a33@1.26.2.45
diff --git a/Mage.Player.AI/nbproject/private/private.properties b/Mage.Player.AI/nbproject/private/private.properties
new file mode 100644
index 0000000000..54759a32a5
--- /dev/null
+++ b/Mage.Player.AI/nbproject/private/private.properties
@@ -0,0 +1,3 @@
+file.reference.Mage.AI-test=C:\\Projects\\Mage\\Mage.Player.AI\\test
+jaxws.endorsed.dir=C:\\Program Files (x86)\\NetBeans 6.7.1\\java2\\modules\\ext\\jaxws21\\api:C:\\Program Files (x86)\\NetBeans 6.7.1\\ide11\\modules\\ext\\jaxb\\api
+user.properties.file=C:\\Users\\Bill\\.netbeans\\6.7\\build.properties
diff --git a/Mage.Player.AI/nbproject/private/private.xml b/Mage.Player.AI/nbproject/private/private.xml
new file mode 100644
index 0000000000..cc2c0e57c4
--- /dev/null
+++ b/Mage.Player.AI/nbproject/private/private.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Mage.Player.AI/nbproject/project.properties b/Mage.Player.AI/nbproject/project.properties
new file mode 100644
index 0000000000..09fa0e1aca
--- /dev/null
+++ b/Mage.Player.AI/nbproject/project.properties
@@ -0,0 +1,70 @@
+application.title=Mage.Player.AI
+application.vendor=BetaSteward_at_googlemail.com
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/Mage.Player.AI.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+excludes=
+file.reference.Mage.AI-src=src
+file.reference.Mage.AI-test=test
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${reference.Mage.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.source=1.6
+javac.target=1.6
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}:\
+ ${libs.junit.classpath}:\
+ ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+main.class=
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+project.license=bsd
+project.Mage=../Mage
+reference.Mage.jar=${project.Mage}/dist/Mage.jar
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.src.dir=src
+test.test.dir=test
diff --git a/Mage.Player.AI/nbproject/project.xml b/Mage.Player.AI/nbproject/project.xml
new file mode 100644
index 0000000000..7800336c1b
--- /dev/null
+++ b/Mage.Player.AI/nbproject/project.xml
@@ -0,0 +1,25 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ Mage.Player.AI
+
+
+
+
+
+
+
+
+
+ Mage
+ jar
+
+ jar
+ clean
+ jar
+
+
+
+
diff --git a/Mage.Player.AI/src/mage/player/ai/Attackers.java b/Mage.Player.AI/src/mage/player/ai/Attackers.java
new file mode 100644
index 0000000000..51da223cc9
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/Attackers.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeMap;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class Attackers extends TreeMap> {
+
+ public List getAttackers() {
+ List attackers = new ArrayList();
+ for (List l: this.values()) {
+ for (Permanent permanent: l) {
+ attackers.add(permanent);
+ }
+ }
+ return attackers;
+ }
+
+}
diff --git a/Mage.Player.AI/src/mage/player/ai/ComputerPlayer.java b/Mage.Player.AI/src/mage/player/ai/ComputerPlayer.java
new file mode 100644
index 0000000000..cfc8dcdced
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/ComputerPlayer.java
@@ -0,0 +1,764 @@
+/*
+* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification, are
+* permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice, this list of
+* conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright notice, this list
+* of conditions and the following disclaimer in the documentation and/or other materials
+* provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* The views and conclusions contained in the software and documentation are those of the
+* authors and should not be interpreted as representing official policies, either expressed
+* or implied, of BetaSteward_at_googlemail.com.
+*/
+
+package mage.player.ai;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.logging.Logger;
+import mage.Constants.CardType;
+import mage.Constants.Outcome;
+import mage.Constants.Zone;
+import mage.Mana;
+import mage.abilities.ActivatedAbility;
+import mage.abilities.TriggeredAbilities;
+import mage.abilities.TriggeredAbility;
+import mage.abilities.costs.mana.ColoredManaCost;
+import mage.abilities.costs.mana.GenericManaCost;
+import mage.abilities.costs.mana.HybridManaCost;
+import mage.abilities.costs.mana.ManaCost;
+import mage.abilities.costs.mana.ManaCosts;
+import mage.abilities.costs.mana.MonoHybridManaCost;
+import mage.abilities.costs.mana.VariableManaCost;
+import mage.abilities.effects.Effect;
+import mage.abilities.effects.ReplacementEffect;
+import mage.abilities.keyword.DoubleStrikeAbility;
+import mage.abilities.keyword.FirstStrikeAbility;
+import mage.abilities.keyword.TrampleAbility;
+import mage.abilities.mana.ManaAbility;
+import mage.abilities.mana.ManaOptions;
+import mage.player.ai.simulators.CombatGroupSimulator;
+import mage.player.ai.simulators.CombatSimulator;
+import mage.player.ai.simulators.CreatureSimulator;
+import mage.cards.Card;
+import mage.cards.Cards;
+import mage.choices.Choice;
+import mage.filter.common.FilterCreatureForAttack;
+import mage.filter.common.FilterCreatureForCombat;
+import mage.filter.common.FilterLandCard;
+import mage.filter.common.FilterNonlandCard;
+import mage.game.Game;
+import mage.game.GameState;
+import mage.game.combat.CombatGroup;
+import mage.game.permanent.Permanent;
+import mage.players.Player;
+import mage.players.PlayerImpl;
+import mage.target.Target;
+import mage.target.TargetCard;
+import mage.target.TargetPermanent;
+import mage.target.TargetPlayer;
+import mage.target.common.TargetDiscard;
+import mage.util.Copier;
+import mage.util.Logging;
+import mage.util.TreeNode;
+
+/**
+ *
+ * only suitable for two player games
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class ComputerPlayer extends PlayerImpl implements Player {
+
+ private final static Logger logger = Logging.getLogger(ComputerPlayer.class.getName());
+ private boolean abort = false;
+ private Map unplayable = new TreeMap();
+ private List playableNonInstant = new ArrayList();
+ private List playableInstant = new ArrayList();
+ private List playableAbilities = new ArrayList();
+
+ public ComputerPlayer(String name) {
+ super(name);
+ human = false;
+ }
+
+ @Override
+ public boolean chooseMulligan(Game game) {
+ logger.fine("chooseMulligan");
+ if (hand.size() < 6)
+ return false;
+ List lands = hand.getCards(new FilterLandCard());
+ if (lands.size() < 2 || lands.size() > hand.size() - 2)
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean chooseTarget(Outcome outcome, Target target, Game game) {
+ logger.fine("chooseTarget: " + outcome.toString() + ":" + target.toString());
+ UUID opponentId = game.getOpponents(playerId).get(0);
+ if (target instanceof TargetPlayer) {
+ if (outcome.isGood()) {
+ if (target.canTarget(playerId, game)) {
+ target.addTarget(playerId, game);
+ return true;
+ }
+ }
+ else {
+ if (target.canTarget(playerId, game)) {
+ target.addTarget(opponentId, game);
+ return true;
+ }
+ }
+ }
+ if (target instanceof TargetDiscard) {
+ findPlayables(game);
+ if (unplayable.size() > 0) {
+ for (int i = unplayable.size() - 1; i >= 0; i--) {
+ if (target.canTarget(unplayable.values().toArray(new Card[0])[i].getId(), game)) {
+ target.addTarget(unplayable.values().toArray(new Card[0])[i].getId(), game);
+ return true;
+ }
+ }
+ }
+ if (hand.size() > 0) {
+ if (target.canTarget(hand.keySet().toArray(new UUID[0])[0], game)) {
+ target.addTarget(hand.keySet().toArray(new UUID[0])[0], game);
+ return true;
+ }
+ }
+ }
+ if (target instanceof TargetPermanent) {
+ List targets;
+ if (outcome.isGood()) {
+ targets = threats(playerId, (TargetPermanent) target, game);
+ }
+ else {
+ targets = threats(opponentId, (TargetPermanent) target, game);
+ }
+ for (Permanent permanent: targets) {
+ if (target.canTarget(permanent.getId(), game)) {
+ target.addTarget(permanent.getId(), game);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void priority(Game game) {
+ logger.fine("priority");
+ if (game.getActivePlayerId().equals(playerId)) {
+ if (game.isMainPhase() && game.getStack().isEmpty()) {
+ playLand(game);
+ }
+ switch (game.getTurn().getStep()) {
+ case UPKEEP:
+ findPlayables(game);
+ break;
+ case DRAW:
+ logState(game);
+ case DECLARE_BLOCKERS:
+ playRemoval(game.getCombat().getAttackers(), game);
+ case PRECOMBAT_MAIN:
+ findPlayables(game);
+ if (playableAbilities.size() > 0) {
+ for (ActivatedAbility ability: playableAbilities) {
+ if (ability.canActivate(playerId, game)) {
+ if (ability.getEffects().hasOutcome(Outcome.PutLandInPlay)) {
+ if (this.activateAbility(ability, game))
+ return;
+ }
+ }
+ }
+ }
+ break;
+ case POSTCOMBAT_MAIN:
+ findPlayables(game);
+ if (game.getStack().isEmpty()) {
+ if (playableNonInstant.size() > 0) {
+ for (Card card: playableNonInstant) {
+ if (card.getSpellAbility().canActivate(playerId, game)) {
+ if (this.activateAbility(card.getSpellAbility(), game))
+ return;
+ }
+ }
+ }
+ if (playableAbilities.size() > 0) {
+ for (ActivatedAbility ability: playableAbilities) {
+ if (ability.canActivate(playerId, game)) {
+ if (this.activateAbility(ability, game))
+ return;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ else {
+ //respond to opponent events
+ }
+ this.passed = true;
+ }
+
+ private void playLand(Game game) {
+ logger.fine("playLand");
+ List lands = hand.getCards(new FilterLandCard());
+ while (lands.size() > 0 && this.landsPlayed < this.landsPerTurn) {
+ if (lands.size() == 1)
+ this.playLand(lands.get(0), game);
+ else {
+ playALand(lands, game);
+ }
+ }
+ }
+
+ private void playALand(List lands, Game game) {
+ logger.fine("playALand");
+ //play a land that will allow us to play an unplayable
+ for (Mana mana: unplayable.keySet()) {
+ for (Card card: lands) {
+ for (ManaAbility ability: card.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (ability.getNetMana().enough(mana)) {
+ this.playLand(card, game);
+ lands.remove(card);
+ return;
+ }
+ }
+ }
+ }
+ //play a land that will get us closer to playing an unplayable
+ for (Mana mana: unplayable.keySet()) {
+ for (Card card: lands) {
+ for (ManaAbility ability: card.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (mana.contains(ability.getNetMana())) {
+ this.playLand(card, game);
+ lands.remove(card);
+ return;
+ }
+ }
+ }
+ }
+ //play first available land
+ this.playLand(lands.get(0), game);
+ lands.remove(0);
+ }
+
+ private void findPlayables(Game game) {
+ playableInstant.clear();
+ playableNonInstant.clear();
+ unplayable.clear();
+ playableAbilities.clear();
+ List nonLands = hand.getCards(new FilterNonlandCard());
+ ManaOptions available = getManaAvailable(game);
+ available.addMana(manaPool.getMana());
+
+ for (Card card: nonLands) {
+ ManaOptions options = card.getManaCost().getOptions();
+ if (card.getManaCost().getVariableCosts().size() > 0) {
+ //don't use variable mana costs unless there is at least 3 extra mana for X
+ for (Mana option: options) {
+ option.add(Mana.ColorlessMana(3));
+ }
+ }
+ for (Mana mana: options) {
+ for (Mana avail: available) {
+ if (mana.enough(avail)) {
+ if (card.getCardType().contains(CardType.INSTANT))
+ playableInstant.add(card);
+ else
+ playableNonInstant.add(card);
+ }
+ else {
+ if (!playableInstant.contains(card) && !playableNonInstant.contains(card))
+ unplayable.put(mana.needed(avail), card);
+ }
+ }
+ }
+ }
+ for (Permanent permanent: game.getBattlefield().getActivePermanents(playerId)) {
+ for (ActivatedAbility ability: permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
+ if (!(ability instanceof ManaAbility) && ability.canActivate(playerId, game)) {
+ ManaOptions abilityOptions = ability.getManaCosts().getOptions();
+ if (ability.getManaCosts().getVariableCosts().size() > 0) {
+ //don't use variable mana costs unless there is at least 3 extra mana for X
+ for (Mana option: abilityOptions) {
+ option.add(Mana.ColorlessMana(3));
+ }
+ }
+ if (abilityOptions.size() == 0) {
+ playableAbilities.add(ability);
+ }
+ else {
+ for (Mana mana: abilityOptions) {
+ for (Mana avail: available) {
+ if (mana.enough(avail)) {
+ playableAbilities.add(ability);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ logger.fine("findPlayables: " + playableInstant.toString() + "---" + playableNonInstant.toString() + "---" + playableAbilities.toString() );
+ }
+
+ protected ManaOptions getManaAvailable(Game game) {
+ logger.fine("getManaAvailable");
+ List manaPerms = this.getAvailableManaProducers(game);
+
+ ManaOptions available = new ManaOptions();
+ for (Permanent perm: manaPerms) {
+ available.addMana(perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD));
+ }
+ return available;
+ }
+
+ @Override
+ public boolean playMana(ManaCost unpaid, Game game) {
+ logger.fine("playMana");
+ ManaCost cost;
+ List producers;
+ if (unpaid instanceof ManaCosts) {
+ cost = ((ManaCosts)unpaid).get(0);
+ producers = getSortedProducers((ManaCosts)unpaid, game);
+ }
+ else {
+ cost = unpaid;
+ producers = this.getAvailableManaProducers(game);
+ }
+ for (Permanent perm: producers) {
+ // pay all colored costs first
+ for (ManaAbility ability: perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (cost instanceof ColoredManaCost) {
+ if (cost.testPay(ability.getNetMana())) {
+ if (activateAbility(ability, game))
+ return true;
+ }
+ }
+ }
+ // then pay hybrid
+ for (ManaAbility ability: perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (cost instanceof HybridManaCost) {
+ if (cost.testPay(ability.getNetMana())) {
+ if (activateAbility(ability, game))
+ return true;
+ }
+ }
+ }
+ // then pay mono hybrid
+ for (ManaAbility ability: perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (cost instanceof MonoHybridManaCost) {
+ if (cost.testPay(ability.getNetMana())) {
+ if (activateAbility(ability, game))
+ return true;
+ }
+ }
+ }
+ // finally pay generic
+ for (ManaAbility ability: perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (cost instanceof GenericManaCost) {
+ if (cost.testPay(ability.getNetMana())) {
+ if (activateAbility(ability, game))
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private List getSortedProducers(ManaCosts unpaid, Game game) {
+ List unsorted = this.getAvailableManaProducers(game);
+ Map scored = new HashMap();
+ for (Permanent permanent: unsorted) {
+ int score = 0;
+ for (ManaCost cost: unpaid) {
+ for (ManaAbility ability: permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (cost.testPay(ability.getNetMana())) {
+ score++;
+ break;
+ }
+ }
+ }
+ scored.put(permanent, score);
+ }
+ return sortByValue(scored);
+ }
+
+ private List sortByValue(Map map) {
+ List> list = new LinkedList>(map.entrySet());
+ Collections.sort(list, new Comparator>() {
+ @Override
+ public int compare(Entry o1, Entry o2) {
+ return (o1.getValue().compareTo(o2.getValue()));
+ }
+ });
+ List result = new ArrayList();
+ for (Iterator it = list.iterator(); it.hasNext();) {
+ Entry entry = (Entry)it.next();
+ result.add(entry.getKey());
+ }
+ return result;
+ }
+
+ @Override
+ public boolean playXMana(VariableManaCost cost, Game game) {
+ logger.fine("playXMana");
+ //put everything into X
+ for (Permanent perm: this.getAvailableManaProducers(game)) {
+ for (ManaAbility ability: perm.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (activateAbility(ability, game))
+ return true;
+ }
+ }
+ cost.setPaid();
+ return true;
+ }
+
+ @Override
+ public void abort() {
+ abort = true;
+ }
+
+ @Override
+ public boolean chooseUse(Outcome outcome, String message, Game game) {
+ logger.fine("chooseUse");
+ //TODO: improve this
+ return outcome.isGood();
+ }
+
+ @Override
+ public boolean choose(Outcome outcome, Choice choice, Game game) {
+ logger.fine("choose");
+ //TODO: improve this
+ choice.setChoice(choice.getChoices().get(0));
+ return true;
+ }
+
+ @Override
+ public boolean searchCards(Cards cards, TargetCard target, Game game) {
+ logger.fine("searchCards");
+ //TODO: improve ths
+ //return first match
+ for (Card card: cards.getCards(target.getFilter())) {
+ target.addTarget(card.getId(), game);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void selectAttackers(Game game) {
+ logger.fine("selectAttackers");
+ UUID opponentId = game.getOpponents(playerId).get(0);
+ Attackers attackers = getAvailableAttackers(game);
+ List blockers = getOpponentBlockers(opponentId, game);
+ List actualAttackers = new ArrayList();
+ if (blockers.isEmpty()) {
+ actualAttackers = attackers.getAttackers();
+ }
+ else if (attackers.size() - blockers.size() >= game.getPlayer(opponentId).getLife()) {
+ actualAttackers = attackers.getAttackers();
+ }
+ else {
+ CombatSimulator combat = simulateAttack(attackers, blockers, opponentId, game);
+ if (combat.rating > 2) {
+ for (CombatGroupSimulator group: combat.groups) {
+ this.declareAttacker(group.attackers.get(0).id, group.defenderId, game);
+ }
+ }
+ }
+ for (Permanent attacker: actualAttackers) {
+ this.declareAttacker(attacker.getId(), opponentId, game);
+ }
+ return;
+ }
+
+ @Override
+ public void selectBlockers(Game game) {
+ logger.fine("selectBlockers");
+
+ List blockers = getAvailableBlockers(game);
+
+ CombatSimulator sim = simulateBlock(CombatSimulator.load(game), blockers, game);
+
+ List groups = game.getCombat().getGroups();
+ for (int i = 0; i< groups.size(); i++) {
+ for (CreatureSimulator creature: sim.groups.get(i).blockers) {
+ groups.get(i).addBlocker(creature.id, playerId, game);
+ }
+ }
+ }
+
+ @Override
+ public int chooseEffect(List rEffects, Game game) {
+ logger.fine("chooseEffect");
+ //TODO: implement this
+ return 0;
+ }
+
+ @Override
+ public TriggeredAbility chooseTriggeredAbility(TriggeredAbilities abilities, Game game) {
+ logger.fine("chooseTriggeredAbility");
+ //TODO: improve this
+ if (abilities.size() > 0)
+ return abilities.get(0);
+ return null;
+ }
+
+ @Override
+ public void assignDamage(int damage, List targets, UUID sourceId, Game game) {
+ logger.fine("assignDamage");
+ //TODO: improve this
+ game.getPermanent(targets.get(0)).damage(damage, sourceId, game);
+ }
+
+ @Override
+ public int getAmount(int min, int max, String message, Game game) {
+ logger.fine("getAmount");
+ //TODO: improve this
+ return min;
+ }
+
+ protected List getAvailableManaProducers(Game game) {
+ logger.fine("getAvailableManaProducers");
+ List result = new ArrayList();
+ for (Permanent permanent: game.getBattlefield().getActivePermanents(playerId)) {
+ for (ManaAbility ability: permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
+ if (ability.canActivate(playerId, game)) {
+ result.add(permanent);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ protected Attackers getAvailableAttackers(Game game) {
+ logger.fine("getAvailableAttackers");
+ FilterCreatureForAttack attackFilter = new FilterCreatureForAttack();
+ attackFilter.getControllerId().add(playerId);
+ Attackers attackers = new Attackers();
+ List creatures = game.getBattlefield().getActivePermanents(attackFilter);
+ for (Permanent creature: creatures) {
+ int potential = combatPotential(creature, game);
+ if (potential > 0) {
+ List l = attackers.get(potential);
+ if (l == null)
+ attackers.put(potential, l = new ArrayList());
+ l.add(creature);
+ }
+ }
+ return attackers;
+ }
+
+ protected int combatPotential(Permanent creature, Game game) {
+ logger.fine("combatPotential");
+ if (!creature.canAttack(game))
+ return 0;
+ int potential = creature.getPower().getValue();
+ potential += creature.getAbilities().getEvasionAbilities().size();
+ potential += creature.getAbilities().getProtectionAbilities().size();
+ potential += creature.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())?1:0;
+ potential += creature.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())?2:0;
+ potential += creature.getAbilities().containsKey(TrampleAbility.getInstance().getId())?1:0;
+ return potential;
+ }
+
+ protected List getAvailableBlockers(Game game) {
+ logger.fine("getAvailableBlockers");
+ FilterCreatureForCombat blockFilter = new FilterCreatureForCombat();
+ blockFilter.getControllerId().add(playerId);
+ List blockers = game.getBattlefield().getActivePermanents(blockFilter);
+ return blockers;
+ }
+
+ protected List getOpponentBlockers(UUID opponentId, Game game) {
+ logger.fine("getOpponentBlockers");
+ FilterCreatureForCombat blockFilter = new FilterCreatureForCombat();
+ blockFilter.getControllerId().add(opponentId);
+ List blockers = game.getBattlefield().getActivePermanents(blockFilter);
+ return blockers;
+ }
+
+ protected CombatSimulator simulateAttack(Attackers attackers, List blockers, UUID opponentId, Game game) {
+ logger.fine("simulateAttack");
+ List attackersList = attackers.getAttackers();
+ CombatSimulator best = new CombatSimulator();
+ int bestResult = 0;
+ //use binary digits to calculate powerset of attackers
+ int powerElements = (int) Math.pow(2, attackersList.size());
+ for (int i = 1; i < powerElements; i++) {
+ String binary = Integer.toBinaryString(i);
+ while(binary.length() < attackersList.size()) {
+ binary = "0" + binary;
+ }
+ List trialAttackers = new ArrayList();
+ for (int j = 0; j < attackersList.size(); j++) {
+ if (binary.charAt(j) == '1')
+ trialAttackers.add(attackersList.get(j));
+ }
+ CombatSimulator combat = new CombatSimulator();
+ for (Permanent permanent: trialAttackers) {
+ combat.groups.add(new CombatGroupSimulator(opponentId, Arrays.asList(permanent.getId()), new ArrayList(), game));
+ }
+ CombatSimulator test = simulateBlock(combat, blockers, game);
+ if (test.evaluate() > bestResult) {
+ best = test;
+ bestResult = test.evaluate();
+ }
+ }
+
+ return best;
+ }
+
+ protected CombatSimulator simulateBlock(CombatSimulator combat, List blockers, Game game) {
+ logger.fine("simulateBlock");
+
+ TreeNode simulations;
+
+ simulations = new TreeNode(combat);
+ addBlockSimulations(blockers, simulations, game);
+ combat.simulate();
+
+ return getWorstSimulation(simulations);
+
+ }
+
+ protected void addBlockSimulations(List blockers, TreeNode node, Game game) {
+ int numGroups = node.getData().groups.size();
+ Copier copier = new Copier();
+ for (Permanent blocker: blockers) {
+ List subList = remove(blockers, blocker);
+ for (int i = 0; i < numGroups; i++) {
+ if (node.getData().groups.get(i).canBlock(blocker, game)) {
+ CombatSimulator combat = copier.copy(node.getData());
+ combat.groups.get(i).blockers.add(new CreatureSimulator(blocker));
+ TreeNode child = new TreeNode(combat);
+ node.addChild(child);
+ addBlockSimulations(subList, child, game);
+ combat.simulate();
+ }
+ }
+ }
+ }
+
+ protected List remove(List source, Permanent element) {
+ List newList = new ArrayList();
+ for (Permanent permanent: source) {
+ if (!permanent.equals(element)) {
+ newList.add(permanent);
+ }
+ }
+ return newList;
+ }
+
+ protected CombatSimulator getBestSimulation(TreeNode simulations) {
+ CombatSimulator best = simulations.getData();
+ int bestResult = best.evaluate();
+ for (TreeNode node: simulations.getChildren()) {
+ CombatSimulator bestSub = getBestSimulation(node);
+ if (bestSub.evaluate() > bestResult) {
+ best = node.getData();
+ bestResult = best.evaluate();
+ }
+ }
+ return best;
+ }
+
+ protected CombatSimulator getWorstSimulation(TreeNode simulations) {
+ CombatSimulator worst = simulations.getData();
+ int worstResult = worst.evaluate();
+ for (TreeNode node: simulations.getChildren()) {
+ CombatSimulator worstSub = getWorstSimulation(node);
+ if (worstSub.evaluate() < worstResult) {
+ worst = node.getData();
+ worstResult = worst.evaluate();
+ }
+ }
+ return worst;
+ }
+
+ protected List threats(UUID playerId, TargetPermanent target, Game game) {
+ List threats = game.getBattlefield().getActivePermanents(target.getFilter());
+ Iterator it = threats.iterator();
+ while(it.hasNext()) {
+ Permanent permanent = it.next();
+ if (!permanent.getControllerId().equals(playerId)) {
+ it.remove();
+ }
+ }
+
+ Collections.sort(threats, new PermanentComparator(game));
+ return threats;
+ }
+
+ private void logState(Game game) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("computer player hand: ");
+ for (Card card: hand.values()) {
+ sb.append(card.getName()).append(",");
+ }
+ logger.fine(sb.toString());
+ }
+
+ private void playRemoval(List attackers, Game game) {
+ for (UUID attackerId: attackers) {
+ for (Card card: this.playableInstant) {
+ if (card.getSpellAbility().canActivate(playerId, game)) {
+ for (Effect effect: card.getSpellAbility().getEffects()) {
+ if (effect.getOutcome().equals(Outcome.DestroyPermanent) || effect.getOutcome().equals(Outcome.Damage)) {
+ if (card.getSpellAbility().getTargets().get(0).canTarget(attackerId, game)) {
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected int evaluateState(GameState state, Game game) {
+ int value = life;
+ Player opponent = game.getPlayer(game.getOpponents(playerId).get(0));
+ if (opponent.getLife() <= 0)
+ return Integer.MAX_VALUE;
+ value -= opponent.getLife();
+ for (Permanent permanent: state.getBattlefield().getAllPermanents()) {
+
+ }
+ return value;
+ }
+
+}
+
diff --git a/Mage.Player.AI/src/mage/player/ai/PermanentComparator.java b/Mage.Player.AI/src/mage/player/ai/PermanentComparator.java
new file mode 100644
index 0000000000..f052f0f54d
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/PermanentComparator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai;
+
+import java.util.Comparator;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class PermanentComparator implements Comparator {
+
+ private Game game;
+ private PermanentEvaluator evaluator = new PermanentEvaluator();
+
+ public PermanentComparator(Game game) {
+ this.game = game;
+ }
+
+ public int compare(Permanent o1, Permanent o2) {
+ return evaluator.evaluate(o1, game) - evaluator.evaluate(o2, game);
+ }
+
+}
diff --git a/Mage.Player.AI/src/mage/player/ai/PermanentEvaluator.java b/Mage.Player.AI/src/mage/player/ai/PermanentEvaluator.java
new file mode 100644
index 0000000000..13065110d4
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/PermanentEvaluator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import mage.Constants.CardType;
+import mage.Constants.Zone;
+import mage.abilities.keyword.DoubleStrikeAbility;
+import mage.abilities.keyword.FirstStrikeAbility;
+import mage.abilities.keyword.TrampleAbility;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class PermanentEvaluator {
+
+ private Map values = new HashMap();
+
+ public int evaluate(Permanent permanent, Game game) {
+ if (!values.containsKey(permanent.getId())) {
+ int value = 0;
+ if (permanent.getCardType().contains(CardType.CREATURE)) {
+ if (permanent.canAttack(game))
+ value += 2;
+ value += permanent.getPower().getValue();
+ value += permanent.getToughness().getValue();
+ value += permanent.getAbilities().getEvasionAbilities().size();
+ value += permanent.getAbilities().getProtectionAbilities().size();
+ value += permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())?1:0;
+ value += permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())?2:0;
+ value += permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId())?1:0;
+ }
+ value += permanent.getAbilities().getManaAbilities(Zone.BATTLEFIELD).size();
+ value += permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD).size();
+ values.put(permanent.getId(), value);
+ }
+ return values.get(permanent.getId());
+ }
+
+}
diff --git a/Mage.Player.AI/src/mage/player/ai/simulators/CombatGroupSimulator.java b/Mage.Player.AI/src/mage/player/ai/simulators/CombatGroupSimulator.java
new file mode 100644
index 0000000000..fbba88fab3
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/simulators/CombatGroupSimulator.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai.simulators;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class CombatGroupSimulator implements Serializable {
+ public List attackers = new ArrayList();
+ public List blockers = new ArrayList();
+ public UUID defenderId;
+ public boolean defenderIsPlaneswalker;
+ public int unblockedDamage;
+ private CreatureSimulator attacker;
+
+ public CombatGroupSimulator(UUID defenderId, List attackers, List blockers, Game game) {
+ this.defenderId = defenderId;
+ for (UUID attackerId: attackers) {
+ Permanent permanent = game.getPermanent(attackerId);
+ this.attackers.add(new CreatureSimulator(permanent));
+ }
+ for (UUID blockerId: blockers) {
+ Permanent permanent = game.getPermanent(blockerId);
+ this.blockers.add(new CreatureSimulator(permanent));
+ }
+ //NOTE: assumes no banding
+ attacker = this.attackers.get(0);
+ }
+
+ private boolean hasFirstOrDoubleStrike() {
+ for (CreatureSimulator creature: attackers) {
+ if (creature.hasDoubleStrike || creature.hasFirstStrike)
+ return true;
+ }
+ for (CreatureSimulator creature: blockers) {
+ if (creature.hasDoubleStrike || creature.hasFirstStrike)
+ return true;
+ }
+ return false;
+ }
+
+ public boolean canBlock(Permanent blocker, Game game) {
+ return blocker.canBlock(attacker.id, game);
+ }
+
+ public void simulateCombat() {
+ unblockedDamage = 0;
+
+ if (hasFirstOrDoubleStrike())
+ assignDamage(true);
+ assignDamage(false);
+ }
+
+ private void assignDamage(boolean first) {
+ if (blockers.size() == 0) {
+ if (canDamage(attacker, first))
+ unblockedDamage += attacker.power;
+ }
+ else if (blockers.size() == 1) {
+ CreatureSimulator blocker = blockers.get(0);
+ if (canDamage(attacker, first)) {
+ if (attacker.hasTrample) {
+ int lethalDamage = blocker.getLethalDamage();
+ if (attacker.power > lethalDamage) {
+ blocker.damage += lethalDamage;
+ unblockedDamage += attacker.power - lethalDamage;
+ }
+ else {
+ blocker.damage += attacker.power;
+ }
+ }
+ }
+ if (canDamage(blocker, first)) {
+ attacker.damage += blocker.power;
+ }
+ }
+ else {
+ int damage = attacker.power;
+ for (CreatureSimulator blocker: blockers) {
+ if (damage > 0 && canDamage(attacker, first)) {
+ int lethalDamage = blocker.getLethalDamage();
+ if (damage > lethalDamage) {
+ blocker.damage += lethalDamage;
+ damage -= lethalDamage;
+ }
+ else {
+ blocker.damage += damage;
+ damage = 0;
+ }
+ }
+ if (canDamage(blocker, first)) {
+ attacker.damage += blocker.power;
+ }
+ }
+ if (damage > 0) {
+ if (attacker.hasTrample) {
+ unblockedDamage += damage;
+ }
+ else {
+ blockers.get(0).damage += damage;
+ }
+ }
+ }
+ }
+
+ private boolean canDamage(CreatureSimulator creature, boolean first) {
+ if (first && (creature.hasFirstStrike || creature.hasDoubleStrike))
+ return true;
+ if (!first && (!creature.hasFirstStrike || creature.hasDoubleStrike))
+ return true;
+ return false;
+ }
+
+ /**
+ * returns 3 attacker survives blockers destroyed
+ * returns 2 both destroyed
+ * returns 1 both survive
+ * returns 0 attacker destroyed blockers survive
+ *
+ * @return int
+ */
+ public int evaluateCombat() {
+ int survivingBlockers = 0;
+ for (CreatureSimulator blocker: blockers) {
+ if (blocker.damage < blocker.toughness)
+ survivingBlockers++;
+ }
+ if (attacker.isDead()) {
+ if (survivingBlockers > 0) {
+ return 0;
+ }
+ return 2;
+ }
+ else {
+ if (survivingBlockers > 0) {
+ return 1;
+ }
+ return 3;
+ }
+ }
+}
diff --git a/Mage.Player.AI/src/mage/player/ai/simulators/CombatSimulator.java b/Mage.Player.AI/src/mage/player/ai/simulators/CombatSimulator.java
new file mode 100644
index 0000000000..5dc07676f6
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/simulators/CombatSimulator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai.simulators;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import mage.game.Game;
+import mage.game.combat.CombatGroup;
+import mage.game.permanent.Permanent;
+import mage.players.Player;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class CombatSimulator implements Serializable {
+
+ public List groups = new ArrayList();
+ public List defenders = new ArrayList();
+ public Map playersLife = new HashMap();
+ public Map planeswalkerLoyalty = new HashMap();
+ public UUID attackerId;
+ public int rating = 0;
+
+ public static CombatSimulator load(Game game) {
+ CombatSimulator simCombat = new CombatSimulator();
+ for (CombatGroup group: game.getCombat().getGroups()) {
+ simCombat.groups.add(new CombatGroupSimulator(group.getDefenderId(), group.getAttackers(), group.getBlockers(), game));
+ }
+ for (UUID defenderId: game.getCombat().getDefenders()) {
+ simCombat.defenders.add(defenderId);
+ Player player = game.getPlayer(defenderId);
+ if (player != null) {
+ simCombat.playersLife.put(defenderId, player.getLife());
+ }
+ else {
+ Permanent permanent = game.getPermanent(defenderId);
+ simCombat.planeswalkerLoyalty.put(defenderId, permanent.getLoyalty().getValue());
+ }
+ }
+ return simCombat;
+ }
+
+ public CombatSimulator() {}
+
+ public void clear() {
+ groups.clear();
+ defenders.clear();
+ attackerId = null;
+ }
+
+ public void simulate() {
+ for (CombatGroupSimulator group: groups) {
+ group.simulateCombat();
+ }
+ }
+
+ public int evaluate() {
+ Map damage = new HashMap();
+ int result = 0;
+ for (CombatGroupSimulator group: groups) {
+ if (!damage.containsKey(group.defenderId)) {
+ damage.put(group.defenderId, group.unblockedDamage);
+ }
+ else {
+ damage.put(group.defenderId, damage.get(group.defenderId) + group.unblockedDamage);
+ }
+ }
+ //check for lethal damage to player
+ for (Entry entry: playersLife.entrySet()) {
+ if (damage.containsKey(entry.getKey()) && entry.getValue() <= damage.get(entry.getKey())) {
+ //TODO: check for protection
+ //NOTE: not applicable for mulitplayer games
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ for (CombatGroupSimulator group: groups) {
+ result += group.evaluateCombat();
+ }
+
+ rating = result;
+ return result;
+ }
+}
diff --git a/Mage.Player.AI/src/mage/player/ai/simulators/CreatureSimulator.java b/Mage.Player.AI/src/mage/player/ai/simulators/CreatureSimulator.java
new file mode 100644
index 0000000000..7e5944aaa6
--- /dev/null
+++ b/Mage.Player.AI/src/mage/player/ai/simulators/CreatureSimulator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of BetaSteward_at_googlemail.com.
+ */
+
+package mage.player.ai.simulators;
+
+import java.io.Serializable;
+import java.util.UUID;
+import mage.abilities.keyword.DoubleStrikeAbility;
+import mage.abilities.keyword.FirstStrikeAbility;
+import mage.abilities.keyword.TrampleAbility;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author BetaSteward_at_googlemail.com
+ */
+public class CreatureSimulator implements Serializable {
+ public UUID id;
+ public int damage;
+ public int power;
+ public int toughness;
+ public boolean hasFirstStrike;
+ public boolean hasDoubleStrike;
+ public boolean hasTrample;
+
+ public CreatureSimulator(Permanent permanent) {
+ this.id = permanent.getId();
+ this.damage = permanent.getDamage();
+ this.power = permanent.getPower().getValue();
+ this.toughness = permanent.getToughness().getValue();
+ this.hasDoubleStrike = permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
+ this.hasFirstStrike = permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
+ this.hasTrample = permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId());
+ }
+
+ public boolean isDead() {
+ return damage >= toughness;
+ }
+
+ public int getLethalDamage() {
+ return toughness - damage;
+ }
+}