diff --git a/.gitattributes b/.gitattributes
index a7cfb0f4b1e..64f09d5b1de 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -27,6 +27,7 @@ forge-ai/src/main/java/forge/ai/ComputerUtilCard.java -text
forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java -text
forge-ai/src/main/java/forge/ai/ComputerUtilCost.java -text
forge-ai/src/main/java/forge/ai/ComputerUtilMana.java -text
+forge-ai/src/main/java/forge/ai/CreatureEvaluator.java -text
forge-ai/src/main/java/forge/ai/GameState.java -text
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java -text
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java -text
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 784f5e020c4..1c9f04f3d6b 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -364,6 +364,9 @@ public class ComputerUtilCard {
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
}
};
+
+ private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
+
/**
*
* evaluateCreature.
@@ -374,200 +377,7 @@ public class ComputerUtilCard {
* @return a int.
*/
public static int evaluateCreature(final Card c) {
- return evaluateCreatureDebug(c, null);
- }
- private static int addValue(int value, Function out, String text) {
- if (out != null && value != 0) {
- out.apply(value + " via " + text);
- }
- return value;
- }
- private static int subValue(int value, Function out, String text) {
- return -addValue(-value, out, text);
- }
- public static int evaluateCreatureDebug(final Card c, Function out) {
- int value = 80;
- if (!c.isToken()) {
- value += addValue(80, out, "non-token"); // tokens should be worth less than actual cards
- }
- int power = c.getNetCombatDamage();
- final int toughness = c.getNetToughness();
- for (String keyword : c.getKeywords()) {
- if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
- || keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
- || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
- || keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
- power = 0;
- break;
- }
- }
- value += addValue(power * 15, out, "power");
- value += addValue(toughness * 10, out, "toughness");
- value += addValue(c.getCMC() * 5, out, "cmc");
-
- // Evasion keywords
- if (c.hasKeyword("Flying")) {
- value += addValue(power * 10, out, "flying");
- }
- if (c.hasKeyword("Horsemanship")) {
- value += addValue(power * 10, out, "horses");
- }
- if (c.hasKeyword("Unblockable")) {
- value += addValue(power * 10, out, "unblockable");
- } else {
- if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
- value += addValue(power * 6, out, "thorns");
- }
- if (c.hasKeyword("Fear")) {
- value += addValue(power * 6, out, "fear");
- }
- if (c.hasKeyword("Intimidate")) {
- value += addValue(power * 6, out, "intimidate");
- }
- if (c.hasStartOfKeyword("CantBeBlockedBy")) {
- value += addValue(power * 3, out, "block-restrict");
- }
- }
-
- // Other good keywords
- if (power > 0) {
- if (c.hasKeyword("Double Strike")) {
- value += addValue(10 + (power * 15), out, "ds");
- } else if (c.hasKeyword("First Strike")) {
- value += addValue(10 + (power * 5), out, "fs");
- }
- if (c.hasKeyword("Deathtouch")) {
- value += addValue(25, out, "dt");
- }
- if (c.hasKeyword("Lifelink")) {
- value += addValue(power * 10, out, "lifelink");
- }
- if (power > 1 && c.hasKeyword("Trample")) {
- value += addValue((power - 1) * 5, out, "trample");
- }
- if (c.hasKeyword("Vigilance")) {
- value += addValue((power * 5) + (toughness * 5), out, "vigilance");
- }
- if (c.hasKeyword("Wither")) {
- value += addValue(power * 10, out, "Wither");
- }
- if (c.hasKeyword("Infect")) {
- value += addValue(power * 15, out, "infect");
- }
- value += addValue(c.getKeywordMagnitude("Rampage"), out, "rampage");
- }
-
- value += addValue(c.getKeywordMagnitude("Bushido") * 16, out, "bushido");
- value += addValue(c.getAmountOfKeyword("Flanking") * 15, out, "flanking");
- value += addValue(c.getAmountOfKeyword("Exalted") * 15, out, "exalted");
- value += addValue(c.getKeywordMagnitude("Annihilator") * 50, out, "eldrazi");
- value += addValue(c.getKeywordMagnitude("Absorb") * 11, out, "absorb");
-
- // Defensive Keywords
- if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
- value += addValue(5, out, "reach");
- }
- if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
- value += addValue(3, out, "shadow-block");
- }
-
- // Protection
- if (c.hasKeyword("Indestructible")) {
- value += addValue(70, out, "darksteel");
- }
- if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
- value += addValue(60, out, "cho-manno");
- } else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
- value += addValue(50, out, "fogbank");
- }
- if (c.hasKeyword("Hexproof")) {
- value += addValue(35, out, "hexproof");
- } else if (c.hasKeyword("Shroud")) {
- value += addValue(30, out, "shroud");
- }
- if (c.hasStartOfKeyword("Protection")) {
- value += addValue(20, out, "protection");
- }
- if (c.hasStartOfKeyword("PreventAllDamageBy")) {
- value += addValue(10, out, "prevent-dmg");
- }
-
- // Bad keywords
- if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
- value -= subValue((power * 9) + 40, out, "defender");
- } else if (c.getSVar("SacrificeEndCombat").equals("True")) {
- value -= subValue(40, out, "sac-end");
- }
- if (c.hasKeyword("CARDNAME can't block.")) {
- value -= subValue(10, out, "cant-block");
- } else if (c.hasKeyword("CARDNAME attacks each turn if able.")
- || c.hasKeyword("CARDNAME attacks each combat if able.")) {
- value -= subValue(10, out, "must-attack");
- } else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
- value -= subValue(10, out, "must-attack-player");
- } else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
- value -= subValue(toughness * 5, out, "reverse-reach");
- }
-
- if (c.hasSVar("DestroyWhenDamaged")) {
- value -= subValue((toughness - 1) * 9, out, "dies-to-dmg");
- }
-
- if (c.hasKeyword("CARDNAME can't attack or block.")) {
- value = addValue(50 + (c.getCMC() * 5), out, "useless"); // reset everything - useless
- }
- if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
- if (c.isTapped()) {
- value = addValue(50 + (c.getCMC() * 5), out, "tapped-useless"); // reset everything - useless
- } else {
- value -= subValue(50, out, "doesnt-untap");
- }
- }
- if (c.hasSVar("EndOfTurnLeavePlay")) {
- value -= subValue(50, out, "eot-leaves");
- } else if (c.hasStartOfKeyword("Cumulative upkeep")) {
- value -= subValue(30, out, "cupkeep");
- } else if (c.hasStartOfKeyword("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
- value -= subValue(20, out, "sac-unless");
- } else if (c.hasStartOfKeyword("(Echo unpaid)")) {
- value -= subValue(10, out, "echo-unpaid");
- }
-
- if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
- value -= subValue(20, out, "upkeep-dmg");
- }
- if (c.hasStartOfKeyword("Fading")) {
- value -= subValue(20, out, "fading");
- }
- if (c.hasStartOfKeyword("Vanishing")) {
- value -= subValue(20, out, "vanishing");
- }
- if (c.getSVar("Targeting").equals("Dies")) {
- value -= subValue(25, out, "dies");
- }
-
- for (final SpellAbility sa : c.getSpellAbilities()) {
- if (sa.isAbility()) {
- value += addValue(10, out, "sa"+sa);
- }
- }
- if (!c.getManaAbilities().isEmpty()) {
- value += addValue(10, out, "manadork");
- }
-
- if (c.isUntapped()) {
- value += addValue(1, out, "untapped");
- }
-
- // paired creatures are more valuable because they grant a bonus to the other creature
- if (c.isPaired()) {
- value += addValue(14, out, "paired");
- }
-
- if (!c.getEncodedCards().isEmpty()) {
- value += addValue(24, out, "encoded");
- }
- return value;
+ return creatureEvaluator.evaluateCreature(c);
}
public static int evaluatePermanentList(final CardCollectionView list) {
diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
new file mode 100644
index 00000000000..0942c5f683d
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
@@ -0,0 +1,198 @@
+package forge.ai;
+
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+
+public class CreatureEvaluator {
+ public int evaluateCreature(final Card c) {
+ int value = 80;
+ if (!c.isToken()) {
+ value += addValue(80, "non-token"); // tokens should be worth less than actual cards
+ }
+ int power = c.getNetCombatDamage();
+ final int toughness = c.getNetToughness();
+ for (String keyword : c.getKeywords()) {
+ if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
+ || keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
+ || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
+ || keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
+ power = 0;
+ break;
+ }
+ }
+ value += addValue(power * 15, "power");
+ value += addValue(toughness * 10, "toughness");
+ value += addValue(c.getCMC() * 5, "cmc");
+
+ // Evasion keywords
+ if (c.hasKeyword("Flying")) {
+ value += addValue(power * 10, "flying");
+ }
+ if (c.hasKeyword("Horsemanship")) {
+ value += addValue(power * 10, "horses");
+ }
+ if (c.hasKeyword("Unblockable")) {
+ value += addValue(power * 10, "unblockable");
+ } else {
+ if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
+ value += addValue(power * 6, "thorns");
+ }
+ if (c.hasKeyword("Fear")) {
+ value += addValue(power * 6, "fear");
+ }
+ if (c.hasKeyword("Intimidate")) {
+ value += addValue(power * 6, "intimidate");
+ }
+ if (c.hasStartOfKeyword("CantBeBlockedBy")) {
+ value += addValue(power * 3, "block-restrict");
+ }
+ }
+
+ // Other good keywords
+ if (power > 0) {
+ if (c.hasKeyword("Double Strike")) {
+ value += addValue(10 + (power * 15), "ds");
+ } else if (c.hasKeyword("First Strike")) {
+ value += addValue(10 + (power * 5), "fs");
+ }
+ if (c.hasKeyword("Deathtouch")) {
+ value += addValue(25, "dt");
+ }
+ if (c.hasKeyword("Lifelink")) {
+ value += addValue(power * 10, "lifelink");
+ }
+ if (power > 1 && c.hasKeyword("Trample")) {
+ value += addValue((power - 1) * 5, "trample");
+ }
+ if (c.hasKeyword("Vigilance")) {
+ value += addValue((power * 5) + (toughness * 5), "vigilance");
+ }
+ if (c.hasKeyword("Wither")) {
+ value += addValue(power * 10, "Wither");
+ }
+ if (c.hasKeyword("Infect")) {
+ value += addValue(power * 15, "infect");
+ }
+ value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
+ }
+
+ value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
+ value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
+ value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
+ value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
+ value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
+
+ // Defensive Keywords
+ if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
+ value += addValue(5, "reach");
+ }
+ if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
+ value += addValue(3, "shadow-block");
+ }
+
+ // Protection
+ if (c.hasKeyword("Indestructible")) {
+ value += addValue(70, "darksteel");
+ }
+ if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
+ value += addValue(60, "cho-manno");
+ } else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
+ value += addValue(50, "fogbank");
+ }
+ if (c.hasKeyword("Hexproof")) {
+ value += addValue(35, "hexproof");
+ } else if (c.hasKeyword("Shroud")) {
+ value += addValue(30, "shroud");
+ }
+ if (c.hasStartOfKeyword("Protection")) {
+ value += addValue(20, "protection");
+ }
+ if (c.hasStartOfKeyword("PreventAllDamageBy")) {
+ value += addValue(10, "prevent-dmg");
+ }
+
+ // Bad keywords
+ if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
+ value -= subValue((power * 9) + 40, "defender");
+ } else if (c.getSVar("SacrificeEndCombat").equals("True")) {
+ value -= subValue(40, "sac-end");
+ }
+ if (c.hasKeyword("CARDNAME can't block.")) {
+ value -= subValue(10, "cant-block");
+ } else if (c.hasKeyword("CARDNAME attacks each turn if able.")
+ || c.hasKeyword("CARDNAME attacks each combat if able.")) {
+ value -= subValue(10, "must-attack");
+ } else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
+ value -= subValue(10, "must-attack-player");
+ } else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
+ value -= subValue(toughness * 5, "reverse-reach");
+ }
+
+ if (c.hasSVar("DestroyWhenDamaged")) {
+ value -= subValue((toughness - 1) * 9, "dies-to-dmg");
+ }
+
+ if (c.hasKeyword("CARDNAME can't attack or block.")) {
+ value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
+ }
+ if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
+ if (c.isTapped()) {
+ value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
+ } else {
+ value -= subValue(50, "doesnt-untap");
+ }
+ }
+ if (c.hasSVar("EndOfTurnLeavePlay")) {
+ value -= subValue(50, "eot-leaves");
+ } else if (c.hasStartOfKeyword("Cumulative upkeep")) {
+ value -= subValue(30, "cupkeep");
+ } else if (c.hasStartOfKeyword("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
+ value -= subValue(20, "sac-unless");
+ } else if (c.hasStartOfKeyword("(Echo unpaid)")) {
+ value -= subValue(10, "echo-unpaid");
+ }
+
+ if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
+ value -= subValue(20, "upkeep-dmg");
+ }
+ if (c.hasStartOfKeyword("Fading")) {
+ value -= subValue(20, "fading");
+ }
+ if (c.hasStartOfKeyword("Vanishing")) {
+ value -= subValue(20, "vanishing");
+ }
+ if (c.getSVar("Targeting").equals("Dies")) {
+ value -= subValue(25, "dies");
+ }
+
+ for (final SpellAbility sa : c.getSpellAbilities()) {
+ if (sa.isAbility()) {
+ value += addValue(10, "sa"+sa);
+ }
+ }
+ if (!c.getManaAbilities().isEmpty()) {
+ value += addValue(10, "manadork");
+ }
+
+ if (c.isUntapped()) {
+ value += addValue(1, "untapped");
+ }
+
+ // paired creatures are more valuable because they grant a bonus to the other creature
+ if (c.isPaired()) {
+ value += addValue(14, "paired");
+ }
+
+ if (!c.getEncodedCards().isEmpty()) {
+ value += addValue(24, "encoded");
+ }
+ return value;
+ }
+
+ protected int addValue(int value, String text) {
+ return value;
+ }
+ protected int subValue(int value, String text) {
+ return -addValue(-value, text);
+ }
+}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
index 27aa38a4e37..4c5630e7941 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
@@ -6,8 +6,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import com.google.common.base.Function;
-
import forge.ai.ComputerUtil;
import forge.ai.PlayerControllerAi;
import forge.game.Game;
@@ -101,13 +99,6 @@ public class GameSimulator {
public static boolean debugPrint;
public static ArrayList debugLines;
- public static Function debugPrintFunction = new Function() {
- @Override
- public Void apply(String str) {
- debugPrint(str);
- return null;
- }
- };
public static void debugPrint(String str) {
if (debugPrint) {
System.out.println(str);
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
index 622df8547ee..749bea552b8 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
@@ -1,6 +1,6 @@
package forge.ai.simulation;
-import forge.ai.ComputerUtilCard;
+import forge.ai.CreatureEvaluator;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
@@ -8,9 +8,12 @@ import forge.game.zone.ZoneType;
public class GameStateEvaluator {
private boolean debugging = false;
+ private SimulationCreatureEvaluator eval = new SimulationCreatureEvaluator();
+
public void setDebugging(boolean debugging) {
this.debugging = debugging;
}
+
public int getScoreForGameState(Game game, Player aiPlayer) {
if (game.isGameOver()) {
return game.getOutcome().getWinningPlayer() == aiPlayer ? Integer.MAX_VALUE : Integer.MIN_VALUE;
@@ -71,7 +74,7 @@ public class GameStateEvaluator {
protected int evalCard(Game game, Player aiPlayer, Card c) {
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
if (c.isCreature()) {
- return ComputerUtilCard.evaluateCreatureDebug(c, debugging ? GameSimulator.debugPrintFunction : null);
+ return eval.evaluateCreature(c);
} else if (c.isLand()) {
return 100;
} else if (c.isEnchantingCard()) {
@@ -84,4 +87,14 @@ public class GameStateEvaluator {
return 50 + 30 * c.getCMC();
}
}
+
+ private class SimulationCreatureEvaluator extends CreatureEvaluator {
+ @Override
+ protected int addValue(int value, String text) {
+ if (debugging && value != 0) {
+ GameSimulator.debugPrint(value + " via " + text);
+ }
+ return super.addValue(value, text);
+ }
+ }
}