Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update CreatureEvaluator.java #6445

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 142 additions & 84 deletions forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package forge.ai;

import com.google.common.base.Function;

import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
Expand All @@ -17,6 +16,7 @@
import java.util.List;

public class CreatureEvaluator implements Function<Card, Integer> {

@Override
public Integer apply(Card c) {
return evaluateCreature(c);
Expand All @@ -25,46 +25,78 @@ public Integer apply(Card c) {
public int evaluateCreature(final Card c) {
return evaluateCreature(c, true, true);
}

public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
//Card shouldn't be null and AI shouldn't crash since this is just score
if (c == null)
return 0;
if (c == null) return 0;

int value = 80;

value += evaluateCardType(c);

if (considerPT) {
value += evaluatePowerAndToughness(c);
}

if (considerCMC) {
value += evaluateManaCost(c);
}

value += evaluateEvasionKeywords(c);
value += evaluateGoodKeywords(c);
value += evaluateDefensiveKeywords(c);
value += evaluateProtectionKeywords(c);
value += evaluateSpellAbilities(c);
value += evaluatePairedAndEncoded(c);
value -= evaluateBadKeywords(c);
value += evaluateCardSpecificModifiers(c);

return value;
}

private int evaluateCardType(Card c) {
int value = 0;
if (!c.isToken()) {
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
value += addValue(20, "non-token");
}
return value;
}

private int evaluatePowerAndToughness(Card c) {
int value = 0;
int power = c.getNetCombatDamage();
final int toughness = c.getNetToughness();
int toughness = c.getNetToughness();

// TODO replace with ReplacementEffect checks
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
}

if (considerPT) {
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness: " + toughness);
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness");

// because backside is always stronger the potential makes it better than a single faced card
if (c.hasKeyword(Keyword.DAYBOUND) && c.isDoubleFaced()) {
value += addValue(power * 10, "transforming");
}
}
if (considerCMC) {
value += addValue(c.getCMC() * 5, "cmc");
if (c.hasKeyword(Keyword.DAYBOUND) && c.isDoubleFaced()) {
value += addValue(power * 10, "transforming");
}

// Evasion keywords
return value;
}

private int evaluateManaCost(Card c) {
return addValue(c.getCMC() * 5, "cmc");
}

private int evaluateEvasionKeywords(Card c) {
int value = 0;
int power = c.getNetCombatDamage();

if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 10, "flying");
}
if (c.hasKeyword(Keyword.HORSEMANSHIP)) {
value += addValue(power * 10, "horses");
}

if (StaticAbilityCantAttackBlock.cantBlockBy(c, null)) {
value += addValue(power * 10, "unblockable");
} else {
Expand All @@ -86,7 +118,13 @@ public int evaluateCreature(final Card c, final boolean considerPT, final boolea
}
}

// Other good keywords
return value;
}

private int evaluateGoodKeywords(Card c) {
int value = 0;
int power = c.getNetCombatDamage();

if (power > 0) {
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
value += addValue(10 + (power * 15), "ds");
Expand All @@ -103,12 +141,11 @@ public int evaluateCreature(final Card c, final boolean considerPT, final boolea
value += addValue((power - 1) * 5, "trample");
}
if (c.hasKeyword(Keyword.VIGILANCE)) {
value += addValue((power * 5) + (toughness * 5), "vigilance");
value += addValue((power * 5) + (c.getNetToughness() * 5), "vigilance");
}
if (c.hasKeyword(Keyword.INFECT)) {
value += addValue(power * 15, "infect");
}
else if (c.hasKeyword(Keyword.WITHER)) {
} else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(power * 10, "wither");
}
value += addValue(c.getKeywordMagnitude(Keyword.TOXIC) * 5, "toxic");
Expand All @@ -119,7 +156,6 @@ else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");

// Keywords that may produce temporary or permanent buffs over time
if (c.hasKeyword(Keyword.OUTLAST)) {
value += addValue(10, "outlast");
}
Expand All @@ -129,15 +165,25 @@ else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(c.getAmountOfKeyword(Keyword.MELEE) * 18, "melee");
value += addValue(c.getAmountOfKeyword(Keyword.PROWESS) * 5, "prowess");

// Defensive Keywords
return value;
}

private int evaluateDefensiveKeywords(Card c) {
int value = 0;

if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.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
return value;
}

private int evaluateProtectionKeywords(Card c) {
int value = 0;

if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
value += addValue(70, "darksteel");
} else {
Expand All @@ -159,13 +205,48 @@ else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(20, "protection");
}

return value;
}

private int evaluateSpellAbilities(Card c) {
int value = 0;

for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
value += evaluateSpellAbility(sa);
}
}

// paired creatures are more valuable because they grant a bonus to the other creature
return value;
}

private int evaluateSpellAbility(SpellAbility sa) {
if (sa.getApi() == ApiType.Pump) {
if ("+X".equals(sa.getParam("NumAtt"))
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
int initPower = sa.getHostCard().getNetPower();
int pumpedPower = initPower;
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
if (energy > 0) {
int numActivations = energy / 3;
for (int i = 0; i < numActivations; i++) {
pumpedPower *= 2;
}
return (pumpedPower - initPower) * 15;
}
}
}
}

return 10;
}

private int evaluatePairedAndEncoded(Card c) {
int value = 0;

if (c.isPaired()) {
value += addValue(14, "paired");
}
Expand All @@ -178,34 +259,37 @@ else if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(30, "revive");
}

// Bad keywords
return value;
}

private int evaluateBadKeywords(Card c) {
int value = 0;

if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
value -= subValue((power * 9) + 40, "defender");
value += subValue((c.getNetCombatDamage() * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= subValue(40, "sac-end");
value += subValue(40, "sac-end");
}
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
value = addValue(50 + (c.getCMC() * 5), "useless");
} else if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(10, "cant-block");
value += subValue(10, "cant-block");
} else if (c.isGoaded()) {
value -= subValue(5, "goaded");
value += subValue(5, "goaded");
} else {
List<GameEntity> mAEnt = StaticAbilityMustAttack.entitiesMustAttack(c);
if (mAEnt.contains(c)) {
value -= subValue(10, "must-attack");
value += subValue(10, "must-attack");
} else if (!mAEnt.isEmpty()) {
value -= subValue(10, "must-attack-player");
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach");
}//*/
value += subValue(10, "must-attack-player");
}
}

if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
value += subValue((c.getNetToughness() - 1) * 9, "dies-to-dmg");
}
if (c.getSVar("Targeting").equals("Dies")) {
value -= subValue(25, "dies");
value += subValue(25, "dies");
}

if (c.isUntapped()) {
Expand All @@ -218,78 +302,52 @@ else if (c.hasKeyword(Keyword.WITHER)) {

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
value = addValue(50 + (c.getCMC() * 5), "tapped-useless");
} else {
value -= subValue(50, "doesnt-untap");
value += subValue(50, "doesnt-untap");
}
} else {
value -= subValue(10 * c.getCounters(CounterEnumType.STUN), "stunned");
value += subValue(10 * c.getCounters(CounterEnumType.STUN), "stunned");
}
if (c.hasSVar("EndOfTurnLeavePlay")) {
value -= subValue(50, "eot-leaves");
value += subValue(50, "eot-leaves");
} else if (c.hasKeyword(Keyword.CUMULATIVE_UPKEEP)) {
value -= subValue(30, "cupkeep");
value += subValue(30, "cupkeep");
} else if (c.hasStartOfKeyword("UpkeepCost")) {
value -= subValue(20, "sac-unless");
value += subValue(20, "sac-unless");
} else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid");
value += subValue(10, "echo-unpaid");
}
if (c.hasKeyword(Keyword.FADING)) {
value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.FADE))), "fading");
value += subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.FADE))), "fading");
}
if (c.hasKeyword(Keyword.VANISHING)) {
value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.TIME))), "vanishing");
value += subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.TIME))), "vanishing");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do believe this breaks and would just increase now...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't the - be moved inside the addValue call instead so the debug output in SimulationCreatureEvaluator will be correct?

}
// use scaling because the creature is only available halfway
if (c.hasKeyword(Keyword.PHASING)) {
value -= subValue(Math.max(20, value / 2), "phasing");
value += subValue(Math.max(20, value / 2), "phasing");
}

// TODO no longer a KW
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not remove any unaddressed TODO or otherwise useful comments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like a few other ones are still being removed

if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}

// card-specific evaluation modifier
if (c.hasSVar("AIEvaluationModifier")) {
value += AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
value += subValue(20, "upkeep-dmg");
}

return value;
}

private int evaluateSpellAbility(SpellAbility sa) {
// Pump abilities
if (sa.getApi() == ApiType.Pump) {
// Pump abilities that grant +X/+X to the card
if ("+X".equals(sa.getParam("NumAtt"))
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
// Electrostatic Pummeler, can be expanded for similar cards
int initPower = sa.getHostCard().getNetPower();
int pumpedPower = initPower;
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
if (energy > 0) {
int numActivations = energy / 3;
for (int i = 0; i < numActivations; i++) {
pumpedPower *= 2;
}
return (pumpedPower - initPower) * 15;
}
}
}
private int evaluateCardSpecificModifiers(Card c) {
if (c.hasSVar("AIEvaluationModifier")) {
return AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
}

// default value
return 10;
return 0;
}

protected int addValue(int value, String text) {
return value;
}

protected int subValue(int value, String text) {
return -addValue(-value, text);
return -addValue(value, text);
}
}