Make a helper class for evaluating a creature, so that it can be more easily customized through subclassing.

This commit is contained in:
Myrd
2015-02-07 19:39:40 +00:00
parent 8a96a479bb
commit 8e1feb1dc6
5 changed files with 218 additions and 205 deletions

View File

@@ -364,6 +364,9 @@ public class ComputerUtilCard {
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
}
};
private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator();
/**
* <p>
* 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<String, Void> out, String text) {
if (out != null && value != 0) {
out.apply(value + " via " + text);
}
return value;
}
private static int subValue(int value, Function<String, Void> out, String text) {
return -addValue(-value, out, text);
}
public static int evaluateCreatureDebug(final Card c, Function<String, Void> 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) {

View File

@@ -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);
}
}

View File

@@ -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<String> debugLines;
public static Function<String, Void> debugPrintFunction = new Function<String, Void>() {
@Override
public Void apply(String str) {
debugPrint(str);
return null;
}
};
public static void debugPrint(String str) {
if (debugPrint) {
System.out.println(str);

View File

@@ -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);
}
}
}