poke_engine/genx/
evaluate.rs

1use super::abilities::Abilities;
2use super::items::Items;
3use super::state::PokemonVolatileStatus;
4use crate::choices::MoveCategory;
5use crate::state::{Pokemon, PokemonStatus, Side, State};
6
7const POKEMON_ALIVE: f32 = 30.0;
8const POKEMON_HP: f32 = 100.0;
9const USED_TERA: f32 = -75.0;
10
11const POKEMON_ATTACK_BOOST: f32 = 30.0;
12const POKEMON_DEFENSE_BOOST: f32 = 15.0;
13const POKEMON_SPECIAL_ATTACK_BOOST: f32 = 30.0;
14const POKEMON_SPECIAL_DEFENSE_BOOST: f32 = 15.0;
15const POKEMON_SPEED_BOOST: f32 = 30.0;
16
17const POKEMON_BOOST_MULTIPLIER_6: f32 = 3.3;
18const POKEMON_BOOST_MULTIPLIER_5: f32 = 3.15;
19const POKEMON_BOOST_MULTIPLIER_4: f32 = 3.0;
20const POKEMON_BOOST_MULTIPLIER_3: f32 = 2.5;
21const POKEMON_BOOST_MULTIPLIER_2: f32 = 2.0;
22const POKEMON_BOOST_MULTIPLIER_1: f32 = 1.0;
23const POKEMON_BOOST_MULTIPLIER_0: f32 = 0.0;
24const POKEMON_BOOST_MULTIPLIER_NEG_1: f32 = -1.0;
25const POKEMON_BOOST_MULTIPLIER_NEG_2: f32 = -2.0;
26const POKEMON_BOOST_MULTIPLIER_NEG_3: f32 = -2.5;
27const POKEMON_BOOST_MULTIPLIER_NEG_4: f32 = -3.0;
28const POKEMON_BOOST_MULTIPLIER_NEG_5: f32 = -3.15;
29const POKEMON_BOOST_MULTIPLIER_NEG_6: f32 = -3.3;
30
31const POKEMON_FROZEN: f32 = -40.0;
32const POKEMON_ASLEEP: f32 = -25.0;
33const POKEMON_PARALYZED: f32 = -25.0;
34const POKEMON_TOXIC: f32 = -30.0;
35const POKEMON_POISONED: f32 = -10.0;
36const POKEMON_BURNED: f32 = -25.0;
37
38const LEECH_SEED: f32 = -30.0;
39const SUBSTITUTE: f32 = 40.0;
40const CONFUSION: f32 = -20.0;
41
42const REFLECT: f32 = 20.0;
43const LIGHT_SCREEN: f32 = 20.0;
44const AURORA_VEIL: f32 = 40.0;
45const SAFE_GUARD: f32 = 5.0;
46const TAILWIND: f32 = 7.0;
47const HEALING_WISH: f32 = 30.0;
48
49const STEALTH_ROCK: f32 = -10.0;
50const SPIKES: f32 = -7.0;
51const TOXIC_SPIKES: f32 = -7.0;
52const STICKY_WEB: f32 = -25.0;
53
54fn evaluate_poison(pokemon: &Pokemon, base_score: f32) -> f32 {
55    match pokemon.ability {
56        Abilities::POISONHEAL => 15.0,
57        Abilities::GUTS
58        | Abilities::MARVELSCALE
59        | Abilities::QUICKFEET
60        | Abilities::TOXICBOOST
61        | Abilities::MAGICGUARD => 10.0,
62        _ => base_score,
63    }
64}
65
66fn evaluate_burned(pokemon: &Pokemon) -> f32 {
67    // burn is not as punishing in certain situations
68
69    // guts, marvel scale, quick feet will result in a positive evaluation
70    match pokemon.ability {
71        Abilities::GUTS | Abilities::MARVELSCALE | Abilities::QUICKFEET => {
72            return -2.0 * POKEMON_BURNED
73        }
74        _ => {}
75    }
76
77    let mut multiplier = 0.0;
78    for mv in pokemon.moves.into_iter() {
79        if mv.choice.category == MoveCategory::Physical {
80            multiplier += 1.0;
81        }
82    }
83
84    // don't make burn as punishing for special attackers
85    if pokemon.special_attack > pokemon.attack {
86        multiplier /= 2.0;
87    }
88
89    multiplier * POKEMON_BURNED
90}
91
92fn get_boost_multiplier(boost: i8) -> f32 {
93    match boost {
94        6 => POKEMON_BOOST_MULTIPLIER_6,
95        5 => POKEMON_BOOST_MULTIPLIER_5,
96        4 => POKEMON_BOOST_MULTIPLIER_4,
97        3 => POKEMON_BOOST_MULTIPLIER_3,
98        2 => POKEMON_BOOST_MULTIPLIER_2,
99        1 => POKEMON_BOOST_MULTIPLIER_1,
100        0 => POKEMON_BOOST_MULTIPLIER_0,
101        -1 => POKEMON_BOOST_MULTIPLIER_NEG_1,
102        -2 => POKEMON_BOOST_MULTIPLIER_NEG_2,
103        -3 => POKEMON_BOOST_MULTIPLIER_NEG_3,
104        -4 => POKEMON_BOOST_MULTIPLIER_NEG_4,
105        -5 => POKEMON_BOOST_MULTIPLIER_NEG_5,
106        -6 => POKEMON_BOOST_MULTIPLIER_NEG_6,
107        _ => panic!("Invalid boost value: {}", boost),
108    }
109}
110
111fn evaluate_hazards(pokemon: &Pokemon, side: &Side) -> f32 {
112    let mut score = 0.0;
113    let pkmn_is_grounded = pokemon.is_grounded();
114    if pokemon.item != Items::HEAVYDUTYBOOTS {
115        if pokemon.ability != Abilities::MAGICGUARD {
116            score += side.side_conditions.stealth_rock as f32 * STEALTH_ROCK;
117            if pkmn_is_grounded {
118                score += side.side_conditions.spikes as f32 * SPIKES;
119                score += side.side_conditions.toxic_spikes as f32 * TOXIC_SPIKES;
120            }
121        }
122        if pkmn_is_grounded {
123            score += side.side_conditions.sticky_web as f32 * STICKY_WEB;
124        }
125    }
126
127    score
128}
129
130fn evaluate_pokemon(pokemon: &Pokemon) -> f32 {
131    let mut score = 0.0;
132    score += POKEMON_HP * pokemon.hp as f32 / pokemon.maxhp as f32;
133
134    match pokemon.status {
135        PokemonStatus::BURN => score += evaluate_burned(pokemon),
136        PokemonStatus::FREEZE => score += POKEMON_FROZEN,
137        PokemonStatus::SLEEP => score += POKEMON_ASLEEP,
138        PokemonStatus::PARALYZE => score += POKEMON_PARALYZED,
139        PokemonStatus::TOXIC => score += evaluate_poison(pokemon, POKEMON_TOXIC),
140        PokemonStatus::POISON => score += evaluate_poison(pokemon, POKEMON_POISONED),
141        PokemonStatus::NONE => {}
142    }
143
144    if pokemon.item != Items::NONE {
145        score += 10.0;
146    }
147
148    // without this a low hp pokemon could get a negative score and incentivize the other side
149    // to keep it alive
150    if score < 0.0 {
151        score = 0.0;
152    }
153
154    score += POKEMON_ALIVE;
155
156    score
157}
158
159pub fn evaluate(state: &State) -> f32 {
160    let mut score = 0.0;
161
162    let mut iter = state.side_one.pokemon.into_iter();
163    let mut s1_used_tera = false;
164    while let Some(pkmn) = iter.next() {
165        if pkmn.hp > 0 {
166            score += evaluate_pokemon(pkmn);
167            score += evaluate_hazards(pkmn, &state.side_one);
168            if iter.pokemon_index == state.side_one.active_index {
169                for vs in state.side_one.volatile_statuses.iter() {
170                    match vs {
171                        PokemonVolatileStatus::LEECHSEED => score += LEECH_SEED,
172                        PokemonVolatileStatus::SUBSTITUTE => score += SUBSTITUTE,
173                        PokemonVolatileStatus::CONFUSION => score += CONFUSION,
174                        _ => {}
175                    }
176                }
177
178                score += get_boost_multiplier(state.side_one.attack_boost) * POKEMON_ATTACK_BOOST;
179                score += get_boost_multiplier(state.side_one.defense_boost) * POKEMON_DEFENSE_BOOST;
180                score += get_boost_multiplier(state.side_one.special_attack_boost)
181                    * POKEMON_SPECIAL_ATTACK_BOOST;
182                score += get_boost_multiplier(state.side_one.special_defense_boost)
183                    * POKEMON_SPECIAL_DEFENSE_BOOST;
184                score += get_boost_multiplier(state.side_one.speed_boost) * POKEMON_SPEED_BOOST;
185            }
186        }
187        if pkmn.terastallized {
188            s1_used_tera = true;
189        }
190    }
191    if s1_used_tera {
192        score += USED_TERA;
193    }
194    let mut iter = state.side_two.pokemon.into_iter();
195    let mut s2_used_tera = false;
196    while let Some(pkmn) = iter.next() {
197        if pkmn.hp > 0 {
198            score -= evaluate_pokemon(pkmn);
199            score -= evaluate_hazards(pkmn, &state.side_two);
200
201            if iter.pokemon_index == state.side_two.active_index {
202                for vs in state.side_two.volatile_statuses.iter() {
203                    match vs {
204                        PokemonVolatileStatus::LEECHSEED => score -= LEECH_SEED,
205                        PokemonVolatileStatus::SUBSTITUTE => score -= SUBSTITUTE,
206                        PokemonVolatileStatus::CONFUSION => score -= CONFUSION,
207                        _ => {}
208                    }
209                }
210
211                score -= get_boost_multiplier(state.side_two.attack_boost) * POKEMON_ATTACK_BOOST;
212                score -= get_boost_multiplier(state.side_two.defense_boost) * POKEMON_DEFENSE_BOOST;
213                score -= get_boost_multiplier(state.side_two.special_attack_boost)
214                    * POKEMON_SPECIAL_ATTACK_BOOST;
215                score -= get_boost_multiplier(state.side_two.special_defense_boost)
216                    * POKEMON_SPECIAL_DEFENSE_BOOST;
217                score -= get_boost_multiplier(state.side_two.speed_boost) * POKEMON_SPEED_BOOST;
218            }
219        }
220        if pkmn.terastallized {
221            s2_used_tera = true;
222        }
223    }
224    if s2_used_tera {
225        score -= USED_TERA;
226    }
227
228    score += state.side_one.side_conditions.reflect as f32 * REFLECT;
229    score += state.side_one.side_conditions.light_screen as f32 * LIGHT_SCREEN;
230    score += state.side_one.side_conditions.aurora_veil as f32 * AURORA_VEIL;
231    score += state.side_one.side_conditions.safeguard as f32 * SAFE_GUARD;
232    score += state.side_one.side_conditions.tailwind as f32 * TAILWIND;
233    score += state.side_one.side_conditions.healing_wish as f32 * HEALING_WISH;
234
235    score -= state.side_two.side_conditions.reflect as f32 * REFLECT;
236    score -= state.side_two.side_conditions.light_screen as f32 * LIGHT_SCREEN;
237    score -= state.side_two.side_conditions.aurora_veil as f32 * AURORA_VEIL;
238    score -= state.side_two.side_conditions.safeguard as f32 * SAFE_GUARD;
239    score -= state.side_two.side_conditions.tailwind as f32 * TAILWIND;
240    score -= state.side_two.side_conditions.healing_wish as f32 * HEALING_WISH;
241
242    score
243}