poke_engine/
evaluate.rs

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