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