Skip to content

Commit e6d6f5e

Browse files
fix masstest deathrattle corruption and corrupt self-transform bugs
AddDeathrattleSpell overwrote extracted deathrattle CARD args with the parent spell CARD arg causing ChangeHeroSpell to get the wrong card id (e.g. minion_majordomo_executus instead of hero_ragnaros). TransformInHandSpell now skips cards with BEING_PLAYED to prevent a Corrupt trigger from transforming a card mid-play when cost modifiers inflate its cost above base. Also adds heuristic trainer improvements and thread interrupt handling in GameContext.resume and vertx-jooq-fork gradle migration.
1 parent 8473250 commit e6d6f5e

14 files changed

Lines changed: 554 additions & 141 deletions

File tree

TODO.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# TODO
2+
3+
## MassTest bugs
4+
5+
### StealCardSpell NPE (~1-2% of MassTest runs)
6+
7+
`NullPointerException: Cannot invoke "Zones.name()" because "destination" is null`
8+
at `GameLogic.stealCard()` line 3215.
9+
10+
Call chain: `StealCardSpell.onCast``ConditionalSpell` → battlecry resolution.
11+
12+
The `destination` zone is null when `stealCard` is called on a target whose
13+
destination zone cannot be determined (e.g. stealing a card from an entity that
14+
has already been removed or is in an unexpected zone).
15+
16+
Fix: determine why `stealCard` receives a null destination and either guard
17+
against the invalid state or prevent the spell from targeting removed entities.

spellsource-game/src/main/java/net/demilich/metastone/game/GameContext.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,9 @@ protected Player getNonActivePlayer() {
14161416
public void resume() {
14171417
currentContext.set(this);
14181418
while (!updateAndGetGameOver()) {
1419+
if (Thread.currentThread().isInterrupted()) {
1420+
break;
1421+
}
14191422
startTurn(getActivePlayerId());
14201423
while (takeActionInTurn()) {
14211424
if (Thread.currentThread().isInterrupted()) {
@@ -1426,7 +1429,9 @@ public void resume() {
14261429
break;
14271430
}
14281431
}
1429-
endGame();
1432+
if (!Thread.currentThread().isInterrupted()) {
1433+
endGame();
1434+
}
14301435
}
14311436

14321437
/**

spellsource-game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/FeatureVector.java

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,53 +12,160 @@ public class FeatureVector implements Cloneable, Serializable {
1212

1313
public static FeatureVector getDefault() {
1414
FeatureVector defaultVector = new FeatureVector();
15+
// Threat level modifiers (own perspective: being threatened is bad)
1516
defaultVector.set(WeightedFeature.RED_MODIFIER, -50);
1617
defaultVector.set(WeightedFeature.YELLOW_MODIFIER, -10);
18+
// Health
1719
defaultVector.set(WeightedFeature.OWN_HP_FACTOR, 1);
1820
defaultVector.set(WeightedFeature.OPPONENT_HP_FACTOR, -1);
21+
// Curses in hand are bad
1922
defaultVector.set(WeightedFeature.CURSED_FACTOR, -25);
23+
// Card advantage
2024
defaultVector.set(WeightedFeature.OWN_CARD_COUNT, 3);
2125
defaultVector.set(WeightedFeature.OPPONENT_CARD_COUNT, -3);
26+
// Own minion keyword modifiers (positive = good for me)
2227
defaultVector.set(WeightedFeature.MINION_INTRINSIC_VALUE, 1);
2328
defaultVector.set(WeightedFeature.MINION_ATTACK_FACTOR, 1);
2429
defaultVector.set(WeightedFeature.MINION_HP_FACTOR, 1);
2530
defaultVector.set(WeightedFeature.MINION_RED_TAUNT_MODIFIER, 8);
2631
defaultVector.set(WeightedFeature.MINION_YELLOW_TAUNT_MODIFIER, 4);
2732
defaultVector.set(WeightedFeature.MINION_DEFAULT_TAUNT_MODIFIER, 2);
28-
defaultVector.set(WeightedFeature.MINION_WINDFURY_MODIFIER, 1.5);
29-
defaultVector.set(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER, 1.5);
30-
defaultVector.set(WeightedFeature.MINION_SPELL_POWER_MODIFIER, 1);
31-
defaultVector.set(WeightedFeature.MINION_STEALTHED_MODIFIER, 1);
32-
defaultVector.set(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, 1.5);
33+
defaultVector.set(WeightedFeature.MINION_WINDFURY_MODIFIER, 6);
34+
defaultVector.set(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER, 5);
35+
defaultVector.set(WeightedFeature.MINION_SPELL_POWER_MODIFIER, 3);
36+
defaultVector.set(WeightedFeature.MINION_STEALTHED_MODIFIER, 3);
37+
defaultVector.set(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, 3);
38+
defaultVector.set(WeightedFeature.MINION_POISONOUS_MODIFIER, 5);
39+
defaultVector.set(WeightedFeature.MINION_LIFESTEAL_MODIFIER, 3);
40+
defaultVector.set(WeightedFeature.MINION_REBORN_MODIFIER, 4);
41+
defaultVector.set(WeightedFeature.MINION_FROZEN_MODIFIER, -4);
42+
defaultVector.set(WeightedFeature.MINION_DEATHRATTLE_MODIFIER, 2);
43+
defaultVector.set(WeightedFeature.MINION_RUSH_MODIFIER, 2);
44+
defaultVector.set(WeightedFeature.MINION_IMMUNE_MODIFIER, 8);
45+
defaultVector.set(WeightedFeature.MINION_CANNOT_ATTACK_MODIFIER, -5);
46+
// Opponent minion keyword modifiers (positive = bad for me, negative = good for me)
47+
defaultVector.set(WeightedFeature.OPPONENT_MINION_INTRINSIC_VALUE, -1);
48+
defaultVector.set(WeightedFeature.OPPONENT_MINION_ATTACK_FACTOR, -1);
49+
defaultVector.set(WeightedFeature.OPPONENT_MINION_HP_FACTOR, -1);
50+
defaultVector.set(WeightedFeature.OPPONENT_MINION_RED_TAUNT_MODIFIER, -4);
51+
defaultVector.set(WeightedFeature.OPPONENT_MINION_YELLOW_TAUNT_MODIFIER, -2);
52+
defaultVector.set(WeightedFeature.OPPONENT_MINION_DEFAULT_TAUNT_MODIFIER, -2);
53+
defaultVector.set(WeightedFeature.OPPONENT_MINION_WINDFURY_MODIFIER, -6);
54+
defaultVector.set(WeightedFeature.OPPONENT_MINION_DIVINE_SHIELD_MODIFIER, -5);
55+
defaultVector.set(WeightedFeature.OPPONENT_MINION_SPELL_POWER_MODIFIER, -3);
56+
defaultVector.set(WeightedFeature.OPPONENT_MINION_STEALTHED_MODIFIER, -4);
57+
defaultVector.set(WeightedFeature.OPPONENT_MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, -4);
58+
defaultVector.set(WeightedFeature.OPPONENT_MINION_POISONOUS_MODIFIER, -3);
59+
defaultVector.set(WeightedFeature.OPPONENT_MINION_LIFESTEAL_MODIFIER, -2);
60+
defaultVector.set(WeightedFeature.OPPONENT_MINION_REBORN_MODIFIER, -4);
61+
defaultVector.set(WeightedFeature.OPPONENT_MINION_FROZEN_MODIFIER, 3);
62+
defaultVector.set(WeightedFeature.OPPONENT_MINION_DEATHRATTLE_MODIFIER, -2);
63+
defaultVector.set(WeightedFeature.OPPONENT_MINION_RUSH_MODIFIER, 0);
64+
defaultVector.set(WeightedFeature.OPPONENT_MINION_IMMUNE_MODIFIER, -10);
65+
defaultVector.set(WeightedFeature.OPPONENT_MINION_CANNOT_ATTACK_MODIFIER, 3);
66+
// Hard removal in hand is good
3367
defaultVector.set(WeightedFeature.HARD_REMOVAL_VALUE, 2);
68+
// Quests
3469
defaultVector.set(WeightedFeature.QUEST_COUNTER_VALUE, 3);
3570
defaultVector.set(WeightedFeature.QUEST_REWARD_VALUE, 9);
71+
// Mana crystals
3672
defaultVector.set(WeightedFeature.EMPTY_MANA_CRYSTAL_VALUE, 6.5);
37-
defaultVector.set(WeightedFeature.OPPOSING_EMPTY_MANA_CRYSTAL_VALUE, -16);
73+
defaultVector.set(WeightedFeature.OPPOSING_EMPTY_MANA_CRYSTAL_VALUE, -6.5);
74+
// Roasted cards
3875
defaultVector.set(WeightedFeature.OWN_ROASTED_VALUE, -15);
39-
defaultVector.set(WeightedFeature.OPPONENT_ROASTED_VALUE, 31);
76+
defaultVector.set(WeightedFeature.OPPONENT_ROASTED_VALUE, 15);
77+
// Armor
4078
defaultVector.set(WeightedFeature.OWN_ARMOR_FACTOR, 0.5);
41-
defaultVector.set(WeightedFeature.WEAPON_VALUE, 1);
79+
defaultVector.set(WeightedFeature.OPPONENT_ARMOR_FACTOR, -0.5);
80+
// Weapons
81+
defaultVector.set(WeightedFeature.WEAPON_VALUE, 3);
82+
defaultVector.set(WeightedFeature.OPPONENT_WEAPON_VALUE, -3);
83+
// Deck size
4284
defaultVector.set(WeightedFeature.OWN_DECK_COUNT, 0.1);
4385
defaultVector.set(WeightedFeature.OPPONENT_DECK_COUNT, -0.1);
86+
// Secrets
4487
defaultVector.set(WeightedFeature.OWN_SECRET_COUNT, 3);
4588
defaultVector.set(WeightedFeature.OPPONENT_SECRET_COUNT, -3);
46-
defaultVector.set(WeightedFeature.MINION_POISONOUS_MODIFIER, 5);
47-
defaultVector.set(WeightedFeature.MINION_LIFESTEAL_MODIFIER, 2);
48-
defaultVector.set(WeightedFeature.MINION_REBORN_MODIFIER, 3);
49-
defaultVector.set(WeightedFeature.MINION_FROZEN_MODIFIER, -3);
50-
defaultVector.set(WeightedFeature.MINION_DEATHRATTLE_MODIFIER, 2);
51-
defaultVector.set(WeightedFeature.MINION_RUSH_MODIFIER, 1);
52-
defaultVector.set(WeightedFeature.MINION_IMMUNE_MODIFIER, 8);
53-
defaultVector.set(WeightedFeature.MINION_CANNOT_ATTACK_MODIFIER, -5);
89+
// Overload
5490
defaultVector.set(WeightedFeature.LOCKED_MANA_VALUE, -5);
91+
defaultVector.set(WeightedFeature.OPPONENT_LOCKED_MANA_VALUE, 5);
92+
// Corpses
5593
defaultVector.set(WeightedFeature.CORPSE_COUNT_VALUE, 0.5);
94+
// Board width
5695
defaultVector.set(WeightedFeature.OWN_MINION_COUNT, 1);
96+
defaultVector.set(WeightedFeature.OPPONENT_MINION_COUNT, -1);
5797
return defaultVector;
5898
}
5999

60100
public static FeatureVector getFittest() {
61-
return getDefault();
101+
FeatureVector v = new FeatureVector();
102+
// Trained values from IPOP-CMA-ES (85.8% win rate vs 8-member HoF, island 4, 65-dim)
103+
v.set(WeightedFeature.RED_MODIFIER, -66.553);
104+
v.set(WeightedFeature.YELLOW_MODIFIER, 36.460);
105+
v.set(WeightedFeature.OWN_HP_FACTOR, -15.873);
106+
v.set(WeightedFeature.OPPONENT_HP_FACTOR, -95.412);
107+
v.set(WeightedFeature.OWN_CARD_COUNT, 60.154);
108+
v.set(WeightedFeature.OPPONENT_CARD_COUNT, 64.930);
109+
v.set(WeightedFeature.MINION_INTRINSIC_VALUE, 3.774);
110+
v.set(WeightedFeature.MINION_ATTACK_FACTOR, 8.600);
111+
v.set(WeightedFeature.MINION_HP_FACTOR, 22.118);
112+
v.set(WeightedFeature.MINION_RED_TAUNT_MODIFIER, -57.455);
113+
v.set(WeightedFeature.MINION_YELLOW_TAUNT_MODIFIER, 25.576);
114+
v.set(WeightedFeature.MINION_DEFAULT_TAUNT_MODIFIER, 19.269);
115+
v.set(WeightedFeature.MINION_WINDFURY_MODIFIER, -50.487);
116+
v.set(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER, 57.090);
117+
v.set(WeightedFeature.MINION_SPELL_POWER_MODIFIER, 19.895);
118+
v.set(WeightedFeature.MINION_STEALTHED_MODIFIER, 55.751);
119+
v.set(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, 5.678);
120+
v.set(WeightedFeature.CURSED_FACTOR, -26.327);
121+
v.set(WeightedFeature.HARD_REMOVAL_VALUE, 3.029);
122+
v.set(WeightedFeature.QUEST_COUNTER_VALUE, -2.945);
123+
v.set(WeightedFeature.EMPTY_MANA_CRYSTAL_VALUE, 56.014);
124+
v.set(WeightedFeature.OPPOSING_EMPTY_MANA_CRYSTAL_VALUE, 20.392);
125+
v.set(WeightedFeature.QUEST_REWARD_VALUE, -71.150);
126+
v.set(WeightedFeature.OWN_ROASTED_VALUE, 13.107);
127+
v.set(WeightedFeature.OPPONENT_ROASTED_VALUE, -58.768);
128+
v.set(WeightedFeature.OWN_ARMOR_FACTOR, 42.708);
129+
v.set(WeightedFeature.WEAPON_VALUE, -58.292);
130+
v.set(WeightedFeature.OWN_DECK_COUNT, 35.336);
131+
v.set(WeightedFeature.OPPONENT_DECK_COUNT, -84.377);
132+
v.set(WeightedFeature.OWN_SECRET_COUNT, 49.013);
133+
v.set(WeightedFeature.OPPONENT_SECRET_COUNT, 73.650);
134+
v.set(WeightedFeature.MINION_POISONOUS_MODIFIER, 17.395);
135+
v.set(WeightedFeature.MINION_LIFESTEAL_MODIFIER, 48.507);
136+
v.set(WeightedFeature.MINION_REBORN_MODIFIER, -22.470);
137+
v.set(WeightedFeature.MINION_FROZEN_MODIFIER, -27.136);
138+
v.set(WeightedFeature.MINION_DEATHRATTLE_MODIFIER, -20.385);
139+
v.set(WeightedFeature.MINION_RUSH_MODIFIER, -20.831);
140+
v.set(WeightedFeature.MINION_IMMUNE_MODIFIER, -62.493);
141+
v.set(WeightedFeature.MINION_CANNOT_ATTACK_MODIFIER, 31.279);
142+
v.set(WeightedFeature.LOCKED_MANA_VALUE, 43.195);
143+
v.set(WeightedFeature.CORPSE_COUNT_VALUE, 18.548);
144+
v.set(WeightedFeature.OWN_MINION_COUNT, 78.758);
145+
v.set(WeightedFeature.OPPONENT_MINION_INTRINSIC_VALUE, -94.884);
146+
v.set(WeightedFeature.OPPONENT_MINION_ATTACK_FACTOR, -71.455);
147+
v.set(WeightedFeature.OPPONENT_MINION_HP_FACTOR, -10.597);
148+
v.set(WeightedFeature.OPPONENT_MINION_RED_TAUNT_MODIFIER, -53.569);
149+
v.set(WeightedFeature.OPPONENT_MINION_YELLOW_TAUNT_MODIFIER, -27.547);
150+
v.set(WeightedFeature.OPPONENT_MINION_DEFAULT_TAUNT_MODIFIER, -39.826);
151+
v.set(WeightedFeature.OPPONENT_MINION_WINDFURY_MODIFIER, -51.586);
152+
v.set(WeightedFeature.OPPONENT_MINION_DIVINE_SHIELD_MODIFIER, 35.722);
153+
v.set(WeightedFeature.OPPONENT_MINION_SPELL_POWER_MODIFIER, -68.364);
154+
v.set(WeightedFeature.OPPONENT_MINION_STEALTHED_MODIFIER, 28.510);
155+
v.set(WeightedFeature.OPPONENT_MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, -69.403);
156+
v.set(WeightedFeature.OPPONENT_MINION_POISONOUS_MODIFIER, 16.061);
157+
v.set(WeightedFeature.OPPONENT_MINION_LIFESTEAL_MODIFIER, -49.679);
158+
v.set(WeightedFeature.OPPONENT_MINION_REBORN_MODIFIER, -93.791);
159+
v.set(WeightedFeature.OPPONENT_MINION_FROZEN_MODIFIER, 25.979);
160+
v.set(WeightedFeature.OPPONENT_MINION_DEATHRATTLE_MODIFIER, 11.306);
161+
v.set(WeightedFeature.OPPONENT_MINION_RUSH_MODIFIER, 37.503);
162+
v.set(WeightedFeature.OPPONENT_MINION_IMMUNE_MODIFIER, 51.299);
163+
v.set(WeightedFeature.OPPONENT_MINION_CANNOT_ATTACK_MODIFIER, -27.345);
164+
v.set(WeightedFeature.OPPONENT_MINION_COUNT, 66.374);
165+
v.set(WeightedFeature.OPPONENT_WEAPON_VALUE, 30.829);
166+
v.set(WeightedFeature.OPPONENT_ARMOR_FACTOR, -11.655);
167+
v.set(WeightedFeature.OPPONENT_LOCKED_MANA_VALUE, -16.063);
168+
return v;
62169
}
63170

64171
private final Map<WeightedFeature, Double> values = new EnumMap<WeightedFeature, Double>(WeightedFeature.class);

spellsource-game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/ThreatBasedHeuristic.java

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public ThreatBasedHeuristic(FeatureVector vector) {
8686
this.weights = vector;
8787
}
8888

89-
private double calculateMinionScore(Minion minion, ThreatLevel threatLevel) {
89+
private double calculateOwnMinionScore(Minion minion, ThreatLevel threatLevel) {
9090
double minionScore = weights.get(WeightedFeature.MINION_INTRINSIC_VALUE);
9191
minionScore += weights.get(WeightedFeature.MINION_ATTACK_FACTOR)
9292
* (minion.getAttack() - minion.getAttributeValue(Attribute.TEMPORARY_ATTACK_BONUS));
@@ -157,6 +157,77 @@ private double calculateMinionScore(Minion minion, ThreatLevel threatLevel) {
157157
return minionScore;
158158
}
159159

160+
private double calculateOpponentMinionScore(Minion minion, ThreatLevel threatLevel) {
161+
double minionScore = weights.get(WeightedFeature.OPPONENT_MINION_INTRINSIC_VALUE);
162+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_ATTACK_FACTOR)
163+
* (minion.getAttack() - minion.getAttributeValue(Attribute.TEMPORARY_ATTACK_BONUS));
164+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_HP_FACTOR) * minion.getHp();
165+
166+
if (minion.hasAttribute(Attribute.TAUNT) || minion.hasAttribute(Attribute.AURA_TAUNT)) {
167+
switch (threatLevel) {
168+
case RED:
169+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_RED_TAUNT_MODIFIER);
170+
break;
171+
case YELLOW:
172+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_YELLOW_TAUNT_MODIFIER);
173+
break;
174+
default:
175+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_DEFAULT_TAUNT_MODIFIER);
176+
break;
177+
}
178+
}
179+
180+
if (minion.hasAttribute(Attribute.WINDFURY) || minion.hasAttribute(Attribute.AURA_WINDFURY)) {
181+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_WINDFURY_MODIFIER);
182+
} else if (minion.hasAttribute(Attribute.MEGA_WINDFURY)) {
183+
minionScore += 2 * weights.get(WeightedFeature.OPPONENT_MINION_WINDFURY_MODIFIER);
184+
}
185+
186+
if (minion.hasAttribute(Attribute.DIVINE_SHIELD)) {
187+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_DIVINE_SHIELD_MODIFIER);
188+
}
189+
if (minion.hasAttribute(Attribute.SPELL_DAMAGE)) {
190+
minionScore += minion.getAttributeValue(Attribute.SPELL_DAMAGE) * weights.get(WeightedFeature.OPPONENT_MINION_SPELL_POWER_MODIFIER);
191+
}
192+
if (minion.hasAttribute(Attribute.AURA_SPELL_DAMAGE)) {
193+
minionScore += minion.getAttributeValue(Attribute.AURA_SPELL_DAMAGE) * weights.get(WeightedFeature.OPPONENT_MINION_SPELL_POWER_MODIFIER);
194+
}
195+
196+
if (minion.hasAttribute(Attribute.STEALTH) || minion.hasAttribute(Attribute.AURA_STEALTH)) {
197+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_STEALTHED_MODIFIER);
198+
}
199+
if (minion.hasAttribute(Attribute.UNTARGETABLE_BY_SPELLS)) {
200+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_UNTARGETABLE_BY_SPELLS_MODIFIER);
201+
}
202+
203+
if (minion.hasAttribute(Attribute.POISONOUS) || minion.hasAttribute(Attribute.AURA_POISONOUS)) {
204+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_POISONOUS_MODIFIER);
205+
}
206+
if (minion.hasAttribute(Attribute.LIFESTEAL) || minion.hasAttribute(Attribute.AURA_LIFESTEAL)) {
207+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_LIFESTEAL_MODIFIER);
208+
}
209+
if (minion.hasAttribute(Attribute.REBORN)) {
210+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_REBORN_MODIFIER);
211+
}
212+
if (minion.hasAttribute(Attribute.FROZEN)) {
213+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_FROZEN_MODIFIER);
214+
}
215+
if (minion.hasAttribute(Attribute.DEATHRATTLES)) {
216+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_DEATHRATTLE_MODIFIER);
217+
}
218+
if (minion.hasAttribute(Attribute.RUSH) || minion.hasAttribute(Attribute.AURA_RUSH)) {
219+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_RUSH_MODIFIER);
220+
}
221+
if (minion.hasAttribute(Attribute.IMMUNE) || minion.hasAttribute(Attribute.AURA_IMMUNE)) {
222+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_IMMUNE_MODIFIER);
223+
}
224+
if (minion.hasAttribute(Attribute.CANNOT_ATTACK) || minion.hasAttribute(Attribute.AURA_CANNOT_ATTACK)) {
225+
minionScore += weights.get(WeightedFeature.OPPONENT_MINION_CANNOT_ATTACK_MODIFIER);
226+
}
227+
228+
return minionScore;
229+
}
230+
160231
@Override
161232
public double getScore(GameContext context, int playerId) {
162233
Player player = context.getPlayer(playerId);
@@ -196,11 +267,11 @@ public double getScore(GameContext context, int playerId) {
196267
score += opponent.getHand().getCount() * weights.get(WeightedFeature.OPPONENT_CARD_COUNT);
197268

198269
for (Minion minion : player.getMinions()) {
199-
score += calculateMinionScore(minion, threatLevel);
270+
score += calculateOwnMinionScore(minion, threatLevel);
200271
}
201272

202273
for (Minion minion : opponent.getMinions()) {
203-
score -= calculateMinionScore(minion, threatLevel);
274+
score += calculateOpponentMinionScore(minion, threatLevel);
204275
}
205276

206277
int questCount = player.getQuests().size();
@@ -232,15 +303,16 @@ public double getScore(GameContext context, int playerId) {
232303

233304
// Armor
234305
score += player.getHero().getArmor() * weights.get(WeightedFeature.OWN_ARMOR_FACTOR);
306+
score += opponent.getHero().getArmor() * weights.get(WeightedFeature.OPPONENT_ARMOR_FACTOR);
235307

236-
// Weapons: damage * durability as a single value
308+
// Weapons
237309
if (!player.getWeaponZone().isEmpty()) {
238310
Weapon ownWeapon = player.getWeaponZone().get(0);
239311
score += ownWeapon.getWeaponDamage() * ownWeapon.getDurability() * weights.get(WeightedFeature.WEAPON_VALUE);
240312
}
241313
if (!opponent.getWeaponZone().isEmpty()) {
242314
Weapon oppWeapon = opponent.getWeaponZone().get(0);
243-
score -= oppWeapon.getWeaponDamage() * oppWeapon.getDurability() * weights.get(WeightedFeature.WEAPON_VALUE);
315+
score += oppWeapon.getWeaponDamage() * oppWeapon.getDurability() * weights.get(WeightedFeature.OPPONENT_WEAPON_VALUE);
244316
}
245317

246318
// Deck size
@@ -253,6 +325,7 @@ public double getScore(GameContext context, int playerId) {
253325

254326
// Locked mana (overload)
255327
score += player.getLockedMana() * weights.get(WeightedFeature.LOCKED_MANA_VALUE);
328+
score += opponent.getLockedMana() * weights.get(WeightedFeature.OPPONENT_LOCKED_MANA_VALUE);
256329

257330
// Corpse count (graveyard minion count for Death Knight)
258331
long ownCorpses = player.getGraveyard().stream()
@@ -262,6 +335,7 @@ public double getScore(GameContext context, int playerId) {
262335

263336
// Board width
264337
score += player.getMinions().size() * weights.get(WeightedFeature.OWN_MINION_COUNT);
338+
score += opponent.getMinions().size() * weights.get(WeightedFeature.OPPONENT_MINION_COUNT);
265339

266340
return score;
267341
}

spellsource-game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/WeightedFeature.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,28 @@ public enum WeightedFeature {
4646
MINION_CANNOT_ATTACK_MODIFIER,
4747
LOCKED_MANA_VALUE,
4848
CORPSE_COUNT_VALUE,
49-
OWN_MINION_COUNT
49+
OWN_MINION_COUNT,
50+
OPPONENT_MINION_INTRINSIC_VALUE,
51+
OPPONENT_MINION_ATTACK_FACTOR,
52+
OPPONENT_MINION_HP_FACTOR,
53+
OPPONENT_MINION_RED_TAUNT_MODIFIER,
54+
OPPONENT_MINION_YELLOW_TAUNT_MODIFIER,
55+
OPPONENT_MINION_DEFAULT_TAUNT_MODIFIER,
56+
OPPONENT_MINION_WINDFURY_MODIFIER,
57+
OPPONENT_MINION_DIVINE_SHIELD_MODIFIER,
58+
OPPONENT_MINION_SPELL_POWER_MODIFIER,
59+
OPPONENT_MINION_STEALTHED_MODIFIER,
60+
OPPONENT_MINION_UNTARGETABLE_BY_SPELLS_MODIFIER,
61+
OPPONENT_MINION_POISONOUS_MODIFIER,
62+
OPPONENT_MINION_LIFESTEAL_MODIFIER,
63+
OPPONENT_MINION_REBORN_MODIFIER,
64+
OPPONENT_MINION_FROZEN_MODIFIER,
65+
OPPONENT_MINION_DEATHRATTLE_MODIFIER,
66+
OPPONENT_MINION_RUSH_MODIFIER,
67+
OPPONENT_MINION_IMMUNE_MODIFIER,
68+
OPPONENT_MINION_CANNOT_ATTACK_MODIFIER,
69+
OPPONENT_MINION_COUNT,
70+
OPPONENT_WEAPON_VALUE,
71+
OPPONENT_ARMOR_FACTOR,
72+
OPPONENT_LOCKED_MANA_VALUE
5073
}

0 commit comments

Comments
 (0)