From 8e1feb1dc694124f4c795ed3835e9ed5c0890c97 Mon Sep 17 00:00:00 2001 From: Myrd Date: Sat, 7 Feb 2015 19:39:40 +0000 Subject: [PATCH] Make a helper class for evaluating a creature, so that it can be more easily customized through subclassing. --- .gitattributes | 1 + .../main/java/forge/ai/ComputerUtilCard.java | 198 +----------------- .../main/java/forge/ai/CreatureEvaluator.java | 198 ++++++++++++++++++ .../forge/ai/simulation/GameSimulator.java | 9 - .../ai/simulation/GameStateEvaluator.java | 17 +- 5 files changed, 218 insertions(+), 205 deletions(-) create mode 100644 forge-ai/src/main/java/forge/ai/CreatureEvaluator.java 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); + } + } }