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; + } +}