def _get_features(self, state: tuple[Pokemon, Pokemon], move: Move): # TODO: Optimize feature extraction pokemon_a, pokemon_b = state type_mod = Type.dmg_modifier(move.info.type, pokemon_b.species.types[0]) * \ (Type.dmg_modifier(move.info.type, pokemon_b.species.types[1]) if len(pokemon_b.species.types) > 1 else 1) type_mod /= 4 # Normalize the value between 0 and 1 dmg_class_mod_stat_atk, dmg_class_mod_stat_def = (pokemon_a.stats.attack, pokemon_b.stats.defense) \ if move.info.damage_class is DamageClass.PHYSICAL else (pokemon_a.stats.special, pokemon_b.stats.special) dmg_class_mod = (dmg_class_mod_stat_atk / (dmg_class_mod_stat_atk + dmg_class_mod_stat_def)) return [ pokemon_a.hp / pokemon_a.stats.total_hp, pokemon_b.hp / pokemon_b.stats.total_hp, dmg_class_mod, type_mod, (move.info.power / 250), (move.info.accuracy if move.info.accuracy is not None else 1), # move.info.hit_info.min_hits, # move.info.hit_info.max_hits, int(move.info.high_crit_ratio), # move.info.priority, move.info.drain, # move.info.healing, # int(move.pp == 0) # TODO: Add more features ]
def _calc_modifier(self, attacking_pokemon: PokemonSpecies, defending_pokemon: PokemonSpecies, move_used: MoveInfo) -> float: rand_modifier = random.uniform(0.85, 1.0) if move_used.name == ATTACK_SELF: return rand_modifier stab = 1.5 if move_used.type in attacking_pokemon.types else 1 type_modifier = Type.dmg_modifier(move_used.type, defending_pokemon.types[0]) if len(defending_pokemon.types) > 1: type_modifier *= Type.dmg_modifier(move_used.type, defending_pokemon.types[1]) return rand_modifier * stab * type_modifier
def _types_advantage(self, pokemon_a: PokemonSpecies, pokemon_b: PokemonSpecies) -> Matchup: weight = math.prod( Type.dmg_modifier(at, dt) for at, dt in itertools.product(pokemon_a.types, pokemon_b.types)) if weight > 1: return Matchup.ADVANTAGEOUS elif weight < 1: return Matchup.DISADVANTAGEOUS else: return Matchup.NEUTRAL