PK!$pokemaster/__init__.py""" Public APIs. """ from .pokemon import Pokemon __author__ = 'Kip Yin' __version__ = '0.1.4' __all__ = ['__author__', '__version__'] PK!#@&*&*pokemaster/_database.py"""Query helpers. Make simple queries in a breeze anywhere, any time. Note that tables should not be passed as arguments! That'll defeat the purpose of this module. """ import warnings from typing import List, Optional, Tuple import pokedex import pokedex.db import pokedex.db.load import pokedex.defaults import sqlalchemy.exc import sqlalchemy.orm.session from pokemaster.prng import PRNG def get_session(database_uri: str = None) -> sqlalchemy.orm.session.Session: """Connect to a database with the given ``engine_uri``. :param database_uri: The uri of the database. The default uri set by :mod:`pokedex.defaults` will be used if not specified. :return: A ``sqlalchemy.orm.session.Session``. """ database_uri = database_uri or pokedex.defaults.get_default_db_uri() try: session = pokedex.db.connect(database_uri) except sqlalchemy.exc.OperationalError: warnings.warn(f'Wrong database uri: {database_uri}.') warnings.warn( 'Connecting to the default database: ' f'{pokedex.defaults.get_default_db_uri()}.' ) session = pokedex.db.connect(pokedex.defaults.get_default_db_uri()) if not pokedex.db.tables.Pokemon.__table__.exists(session.bind): # Empty database warnings.warn('Initializing database') pokedex.db.load.load(session, drop_tables=True, safe=False) session = pokedex.db.connect(database_uri) return session SESSION = get_session() def set_session(session): """Bind a session.""" global SESSION SESSION = session def _check_completeness( *args, msg='Must specify at least one value.' ) -> Optional[bool]: """Return True if at least one argument in args is not None.""" for arg in args: if arg is not None: return True else: raise ValueError(msg) def get_pokemon( national_id: int = None, species: str = None, form: str = None ) -> pokedex.db.tables.Pokemon: """Make a query from ``pokedex.db.tables.Pokemon``. :param national_id: The National Pokédex ID. :param species: Pokémon's species, the standard 386, 493, etc. :param form: The form identifier of a Pokémon, such as 'a' for 'unown', 'attack' for 'deoxys', etc. :return: a ``pokedex.db.tables.Pokemon`` row. """ _check_completeness(national_id, species) query: sqlalchemy.orm.query.Query = ( SESSION.query(pokedex.db.tables.Pokemon) .join(pokedex.db.tables.PokemonForm) .join(pokedex.db.tables.PokemonSpecies) ) if national_id is not None: query = query.filter(pokedex.db.tables.PokemonSpecies.id == national_id) if species is not None: query = query.filter( pokedex.db.tables.PokemonSpecies.identifier == species ) if form is not None: query = query.filter( pokedex.db.tables.PokemonForm.form_identifier == form ) else: query = query.filter(pokedex.db.tables.Pokemon.is_default == 1) return query.one() def _get_experience_table( national_id: int = None, species: str = None ) -> sqlalchemy.orm.query.Query: """Get an experience table specific to the species. :param national_id: The National Pokédex ID. :param species: The Pokémon's species. :return: sqlalchemy.orm.query.Query """ # _check_completeness([national_id, species], growth_rate_id) if species is not None or national_id is not None: pokemon = get_pokemon(species=species, national_id=national_id) conditions = {'growth_rate_id': pokemon.species.growth_rate_id} else: raise ValueError( 'Must specify either the species or the National Pokédex ID.' ) return SESSION.query(pokedex.db.tables.Experience).filter_by(**conditions) def get_experience( national_id: int = None, species: str = None, level: int = None, exp: int = None, ) -> pokedex.db.tables.Experience: """Look up a Pokémon's experience at various levels.""" if level is None and exp is None: raise ValueError('Gimme something to look up!') query = _get_experience_table(national_id=national_id, species=species) if level is not None: query = query.filter_by(level=level) if exp is not None: query = query.filter( pokedex.db.tables.Experience.experience <= exp ).order_by(pokedex.db.tables.Experience.experience.desc()) result = query.first() if result is None: raise ValueError(f'Inconsistent data.') else: return result def wild_pokemon_held_item( prng: PRNG, national_id: int, compound_eyes: bool ) -> Optional[pokedex.db.tables.Item]: """Determine the held item of a wild Pokémon.""" pokemon_ = get_pokemon(national_id=national_id) held_item_chance = prng() / 0xFFFF if compound_eyes: rare_item_chance = 0.2 common_item_chance = 0.6 else: rare_item_chance = 0.05 common_item_chance = 0.5 for item in reversed(pokemon_.items): if item.rarity == 5 and held_item_chance <= rare_item_chance: return item elif item.rarity == 50 and held_item_chance <= common_item_chance: return item elif item.rarity == 100: return item else: return None def get_pokemon_default_moves( level: int, national_id: int = None, species: str = None, form: str = None, version_group: str = 'emerald', ) -> Tuple[pokedex.db.tables.Move]: """Determine the moves of a wild Pokémon.""" pokemon = get_pokemon(national_id, species, form) pokemon_moves: List[pokedex.db.tables.PokemonMove] = reversed( SESSION.query(pokedex.db.tables.PokemonMove) .join(pokedex.db.tables.PokemonMoveMethod) .join(pokedex.db.tables.VersionGroup) .filter( pokedex.db.tables.PokemonMove.pokemon_id == pokemon.id, pokedex.db.tables.PokemonMoveMethod.identifier == 'level-up', pokedex.db.tables.PokemonMove.level <= level, pokedex.db.tables.VersionGroup.identifier == version_group, ) .order_by(pokedex.db.tables.PokemonMove.level.desc()) .order_by(pokedex.db.tables.PokemonMove.order) .limit(4) .all() ) # Just to get the signatures. def _get_move(x: pokedex.db.tables.PokemonMove) -> pokedex.db.tables.Move: return x.move return tuple(map(_get_move, pokemon_moves)) def get_nature( personality: int = None, identifier: str = None ) -> pokedex.db.tables.Nature: """Determine a Pokémon's nature from its personality value.""" conditions = {} if personality is None and identifier is None: raise ValueError('Gimme something to look up!') if personality is not None: conditions['game_index'] = personality % 25 if identifier is not None: conditions['identifier'] = identifier return SESSION.query(pokedex.db.tables.Nature).filter_by(**conditions).one() def get_ability( national_id: int = None, species: str = None, form: str = None, personality: int = None, ) -> pokedex.db.tables.Ability: """Not quite a query, but definitely hides the mess. If a Pokémon only have one ability, then that'll be its ability straight away; if it has more than one ability, then the last bit of `personality` comes to play. """ pokemon_ = get_pokemon(species=species, national_id=national_id, form=form) abilities = pokemon_.abilities return abilities[min(len(abilities) - 1, personality % 2)] # FIXME: change `gender_rate` to `species` (issue #6) def get_pokemon_gender( national_id: int = None, species: str = None, form: str = None, personality: int = None, ) -> pokedex.db.tables.Gender: """Determine a Pokémon's gender by its gender rate and personality.""" pokemon = get_pokemon(national_id=national_id, species=species, form=form) gender_rate = pokemon.species.gender_rate if gender_rate == -1: gender = 'genderless' elif gender_rate == 8: gender = 'female' elif gender_rate == 0: gender = 'male' else: # Gender is determined by the last byte of the PID. p_gender = personality % 0x100 gender_threshold = 0xFF * gender_rate // 8 if p_gender >= gender_threshold: gender = 'male' else: gender = 'female' return ( SESSION.query(pokedex.db.tables.Gender) .filter_by(identifier=gender) .one() ) def get_move(move: str = None, move_id: int = None) -> pokedex.db.tables.Move: """""" _check_completeness(move, move_id) conditions = {'generation': 3} if move is not None: conditions['identifier'] = move if move_id is not None: conditions['id'] = move_id return (SESSION.query(pokedex.db.tables.Move).filter_by(**conditions)).one() # TODO: get it via the machine no. or the move name def get_machine( machine_number: int = None, move_identifier: str = None, move_id: int = None ) -> Optional[pokedex.db.tables.Machine]: """ Get a TM or HM by the machine number,or the move's identifier, if it is a valid machine. """ _check_completeness(machine_number, move_identifier, move_id) query = ( SESSION.query(pokedex.db.tables.Machine) .join(pokedex.db.tables.VersionGroup) .join(pokedex.db.tables.Move) .filter(pokedex.db.tables.VersionGroup.identifier == 'emerald') ) if machine_number is not None: query = query.filter( pokedex.db.tables.Machine.machine_number == machine_number ) if move_identifier is not None: query = query.filter( pokedex.db.tables.Move.identifier == move_identifier ) if move_id is not None: query = query.filter(pokedex.db.tables.Move.id == move_id) return query.one_or_none() def get_move_pool( species: str, move_method: str = None ) -> List[pokedex.db.tables.PokemonMove]: """Get a pool of moves that a Pokémon can learn via a specific method.""" query = ( SESSION.query(pokedex.db.tables.PokemonMove) .join(pokedex.db.tables.PokemonMoveMethod) .join(pokedex.db.tables.Pokemon) .join(pokedex.db.tables.PokemonSpecies) .filter( pokedex.db.tables.PokemonSpecies.identifier == species, pokedex.db.tables.VersionGroup.identifier == 'emerald', ) ) if move_method is not None: query = query.filter( pokedex.db.tables.PokemonMoveMethod.identifier == move_method ) return query.all() if __name__ == '__main__': p = get_machine(108) print(p.move.identifier) PK!x::pokemaster/pokemon.py"""Basic Pokémon API""" from collections import deque from numbers import Real from typing import List, MutableMapping, NoReturn from pokedex.db import tables as tb from pokemaster import _database from pokemaster.prng import PRNG from pokemaster.stats import Conditions, Stats def _sign(x: int) -> int: """Get the sign of ``x``.""" return int(abs(x) / x) class Pokemon: """ A Real, Living™ Pokémon. A ``Pokemon`` instance has the exact same attributes and behaviors as the ones in game: a Pokémon knows up to four moves, holds some kind of item, have stats (hp, attack, speed, etc.), can level up and evolve to another Pokémon, can be in some status conditions, can be cured by using medicines, and much more. """ _prng = PRNG() def __init__( self, species: str = None, national_id: int = None, form: str = None, level: int = None, exp: int = None, gender: str = None, ability: str = None, nature: str = None, iv: Stats = None, ): """Instantiate a Pokémon. :param str species: The species of the Pokémon, in lowercase ASCII. :param int national_id: The Pokémon's index number in the National Pokédex. At least one of ``species`` and ``national_id`` must be specified to instantiate a Pokémon. If both are present, then they need to be consistent with each other. :param str form: The variant of the Pokémon, in lowercase ASCII. :param int level: The current level of the Pokémon. Must be an ``int`` between 1 and 100. Needs to be consistent with ``exp``, if specified. :param int exp: The current get_experience of the Pokémon. If the level is also specified, the level and the exp. points need to be consistent. At least one of ``level`` and ``exp`` must be specified to instantiate a Pokémon. Leaving this to None while having ``level`` set will automatically set the exp. points to the minimum amount required to be at the specified level. :param str gender: The Pokémon's gender. It needs to be consistent with the Pokémon's gender rate. If the gender is not set, a random gender will be assigned based on the Pokémon's gender rate and the personality ID. :param str ability: The Pokémon's ability. The ability needs to be consistent with the Pokémon's species. If this is not specified, a random ability will be determined from the personality ID. :param str nature: The Pokémon's nature. If this is not specified, a random nature will be determined from the personality ID. :param MutableMapping[str, int] iv: A dictionary of the Pokémon's individual values. The keys are the names of the statistics (hp, attack, defense, special-attack, special-defense, speed). Each individual value must not exceed 32. If it is not specified, a random set of IV's will be generated using the PRNG. """ _pokemon = _database.get_pokemon( national_id=national_id, species=species, form=form ) _growth = _database.get_experience( national_id=national_id, species=species, level=level, exp=exp ) _species = _pokemon.species self._national_id = _species.id self._species = _species.identifier self._form = form self._height = _pokemon.height / 10 # In meters self._weight = _pokemon.weight / 10 # In meters self._types = list(map(lambda x: x.identifier, _pokemon.types)) self._level = _growth.level self._exp = _growth.experience if exp is None else exp self._happiness = 0 if iv is None: _gene = self._prng.create_gene() self._iv = Stats.make_iv(_gene) elif isinstance(iv, Stats) and iv.validate_iv(): self._iv = iv else: raise TypeError(f"`iv` must be of type `pokemaster.Stats`.") self._personality = self._prng.create_personality() self._nature = ( nature or _database.get_nature(self._personality).identifier ) self._ability = ( ability or _database.get_ability( species=self._species, personality=self._personality ).identifier ) self._gender = gender or _database.get_pokemon_gender( species=self.species, personality=self._personality ) self._species_strengths = Stats.make_species_strengths(self._species) self._nature_modifiers = Stats.make_nature_modifiers(self._nature) self._ev = Stats() self._stats = self._calculate_stats() self._current_hp = self._stats.hp self._conditions = Conditions() _moves = _database.get_pokemon_default_moves( species=self._species, level=self._level ) self._moves = deque(map(lambda x: x.identifier, _moves), maxlen=4) self._pp = list(map(lambda x: x.pp, _moves)) self._held_item = None @property def ability(self) -> str: """The Pokémon's ability.""" return self._ability @property def current_hp(self) -> Real: """The amount of HP the Pokémon currently has.""" return self._current_hp @property def exp(self) -> int: """The current exp. points.""" return self._exp @property def exp_to_next_level(self) -> int: """The exp. points needed to get to the next level.""" if self._level < 100: return ( _database.get_experience( species=self._species, level=self._level + 1 ).experience - self._exp ) else: return 0 @property def form(self) -> str: """The Pokémon's form.""" return self._form @property def gender(self) -> str: """ The Pokémon's gender. Possible values are: 'male', 'female', and 'genderless'. """ return self._gender @property def held_item(self) -> str: """The item the Pokémon is currently holding.""" return self._held_item @property def level(self) -> int: """The Pokémon's level.""" return self._level @property def moves(self) -> List[str]: """The Pokémon's learned moves.""" return list(self._moves) @property def national_id(self) -> int: """The Pokémon's national ID.""" return self._national_id @property def nature(self) -> str: """The Pokémon's nature.""" return self._nature @property def species(self) -> str: """The Pokémon's species.""" return self._species @property def stats(self) -> Stats: """The statistics of the Pokémon.""" return self._stats @property def types(self) -> List[str]: """The Pokémon's types.""" return self._types def _calculate_stats(self) -> Stats: """Calculate the Pokémon's stats.""" residual_stats = Stats( hp=10 + self._level, attack=5, defense=5, special_attack=5, special_defense=5, speed=5, ) stats = ( (self._species_strengths * 2 + self._iv + self._ev // 4) * self._level // 100 + residual_stats ) * self._nature_modifiers if self._species_strengths.hp == 1: stats.hp = 1 return stats def _check_evolution_condition( self, trigger: str, evolution: tb.PokemonEvolution ) -> bool: """Check the evolution conditions.""" if trigger != evolution.trigger.identifier: return False evolve = False if evolution.minimum_level: # The minimum level for the Pokémon. evolve = True if self._level >= evolution.minimum_level else False if evolution.held_item: # the item the Pokémon must hold. if self.held_item and self.held_item == evolution.held_item: evolve = True if evolution.time_of_day: # The required time of day. enum: [day, night] ... if evolution.known_move: # the move the Pokémon must know. ... if evolution.minimum_happiness: # The minimum happiness value the Pokémon must have. evolve = ( True if self._happiness >= evolution.minimum_happiness else False ) if evolution.minimum_beauty: # The minimum Beauty value the Pokémon must have. evolve = ( True if self._conditions.beauty >= evolution.minimum_beauty else False ) if evolution.relative_physical_stats: # The required relation between the Pokémon’s Attack and Defense # stats, as sgn(atk-def). if evolution.relative_physical_stats == _sign( self.stats.attack - self.stats.defense ): evolve = True if evolution.party_species: # the species that must be present in the party. ... return evolve def _evolve(self, trigger: str) -> NoReturn: """Evolve the Pokémon via ``trigger``. :param trigger: the event that triggers the evolution. Valid triggers are: level-up, trade, use-item, and shed. :return: Nothing. """ pokemon = _database.get_pokemon(species=self._species) for child_species in pokemon.species.child_species: if self._check_evolution_condition( trigger=trigger, evolution=child_species.evolutions[0] ): evolved_species = child_species.identifier break else: return evolved_pokemon = _database.get_pokemon(species=evolved_species) self._ability = _database.get_ability( species=evolved_pokemon.species.identifier, personality=self._personality, ) self._form = evolved_pokemon.default_form # TODO: use the correct form self._height = evolved_pokemon.height self._national_id = evolved_pokemon.species.id self._species = evolved_pokemon.species.identifier self._species_strengths = Stats.make_species_strengths( species=evolved_pokemon.species.identifier ) self._stats = self._calculate_stats() self._weight = evolved_pokemon.weight def gain_exp(self, earned_exp: int) -> NoReturn: """Add ``earned_exp`` to the Pokémon's exp. points. :param earned_exp: The earned get_experience points upon defeating an opponent Pokémon. :return: NoReturn """ # earned_exp = new_exp - self._exp if earned_exp < 0: raise ValueError( f'The new exp. points, {self._exp + earned_exp}, ' f'needs to be no less ' f'than the current exp, {self._exp}.' ) while self._level < 100 and earned_exp >= self.exp_to_next_level: earned_exp -= self.exp_to_next_level self._level_up() # <- where evolution and other magic take place. # At this point, the incremental_exp is not enough to let the # Pokémon level up anymore. But we still need to check if it # overflows if self._level < 100: self._exp += earned_exp def _learn_move( self, learn: str, forget: str = None, move_method: str = None ) -> NoReturn: """Learn a new move. If ``move_to_forget`` is not given, then the last move in the move set will be forgotten. HM moves are skipped. :param str learn: The name of the move to learn. :param str forget: The name of the move to forget. :return: NoReturn. """ # If the move to forget is specified, then first check if it # is a valid move to forget or not. # If the move is not specified, then assume forgetting the # first move on the list. if forget is not None: if forget not in self._moves: raise ValueError( 'Cannot forget a move: ' f'{self.species} does not know move {forget}.' ) else: forget = self._moves[0] # Get the tables.Machine by the move identifier. # If `forget_machine` is not None (i.e. it is a machine) and # `forget_machine.is_hm` is True, then the Pokémon cannot forget # the move. # Otherwise, remove the move from `self._moves`. forget_machine = _database.get_machine(move_identifier=forget) if forget_machine is not None and forget_machine.is_hm: raise ValueError( f'{self._species} cannot forget {forget_machine.move.identifier}!' ) elif len(self._moves) == 4: self._moves.remove(forget) move_pool = _database.get_move_pool( species=self._species, move_method=move_method ) if learn in map(lambda x: x.move.identifier, move_pool): self._moves.append(learn) else: raise ValueError(f'{self._species} cannot learn move {learn}!') def use_machine(self, machine: int, forget: str = None) -> NoReturn: """Use a TM or HM to learn a new move. :param machine: The machine number. For TMs, it is the TM number as-is. For HMs, it is the HM number plus 100. For example, the machine number of TM50 is 50, and the machine number of HM08 is 108. :param forget: The move to forget. If this is ``None``, then the earliest learned move will be forgotten. :return: NoReturn. """ move_to_learn = _database.get_machine( machine_number=machine ).move.identifier self._learn_move(learn=move_to_learn, forget=forget) def _level_up(self): """ Increase Pokémon's level by one. Evolve the Pokémon as needed. """ if self._level >= 100: return self._level += 1 self._exp = _database.get_experience( species=self._species, level=self._level ).experience self._stats = self._calculate_stats() if self.held_item and self.held_item == 'everstone': return self._evolve('level-up') if __name__ == '__main__': p = Pokemon('eevee', level=5) PK!%# # pokemaster/prng.py""" Provides the pseudo-random number generator used in various places. """ from typing import List, Tuple class PRNG: """A linear congruential random number generator. Usage:: >>> prng = PRNG() >>> prng() 0 >>> prng() 59774 >>> prng.reset(seed=0x1A56B091) >>> prng() 475 >>> prng = PRNG(seed=0x1A56B091) >>> prng() 475 References: https://bulbapedia.bulbagarden.net/wiki/Pseudorandom_number_generation_in_Pokémon https://www.smogon.com/ingame/rng/pid_iv_creation#pokemon_random_number_generator """ def __init__(self, seed=0): # In Emerald, the initial seed is always 0. if not isinstance(seed, int): raise TypeError(f'The seed must be an integer.') self._seed = seed # XXX: __iter__? def _generator(self): while True: self._seed = (0x41C64E6D * self._seed + 0x6073) & 0xFFFFFFFF yield self._seed >> 16 def __call__(self) -> int: # FIXME: will eventually return StopIteration! try: return next(self._generator()) except StopIteration: self._seed = 0 return next(self._generator()) def reset(self, seed=None): """Reset the generator with a seed, if given.""" self._seed = seed or 0 def next(self, n=1) -> List[int]: """Generate the next n random numbers.""" return [self() for _ in range(n)] def create_genome(self, method=2) -> Tuple[int, int]: """ Generate the PID and IVs using the internal generator. Return a tuple of two integers, in the order of 'PID' and 'IVs'. The generated 32-bit IVs is different from how it is actually stored. Checkout [this link](https://www.smogon.com/ingame/rng/pid_iv_creation#rng_pokemon_generation) for more information on Method 1, 2, and 4. """ return self.create_personality(), self.create_gene(method) def create_personality(self) -> int: """Create the Personality ID (PID) for a Pokémon. :return: int """ pid_src_1, pid_src_2 = self.next(2) return pid_src_1 + (pid_src_2 << 16) def create_gene(self, method: int = 2) -> int: """Create the number used to generate a Pokémon's IVs. :param method: the Pokémon generation method. Valid values are 1, 2, and 4. :return: int """ if method not in (1, 2, 4): raise ValueError( 'Only methods 1, 2, 4 are supported. For more information on ' 'the meaning of the methods, see ' 'https://www.smogon.com/ingame/rng/pid_iv_creation#rng_pokemon_generation' ' for help.' ) elif method == 1: iv_src_1, iv_src_2 = self.next(2) elif method == 2: _, iv_src_1, iv_src_2 = self.next(3) else: # method == 4: iv_src_1, _, iv_src_2 = self.next(3) return iv_src_1 + (iv_src_2 << 16) PK!/Ypokemaster/stats.py"""Provides general ``Stats`` class for statistics-related functionality and ``Conditions`` class for contests. """ import operator from numbers import Real from typing import Callable, ClassVar, Tuple, Union import attr from attr.validators import instance_of from pokemaster import _database @attr.s(slots=True, auto_attribs=True) class Conditions: """Contest conditions""" coolness: int = 0 beauty: int = 0 cuteness: int = 0 smartness: int = 0 toughness: int = 0 @attr.s(auto_attribs=True, slots=True, frozen=True) class Stats: """A generic statistics representation. ``Stats`` instances are "immutable". For IV, species strengths (a.k.a. base stats), and EV yields, having ``Stats`` immutable makes perfect sense, since they are either bound to individual Pokémon or shared within a species. For EV and permanent stats, since they should get updated only from adding/subtracting/multiplying/dividing another ``Stats`` instance, being mutable does not do much good for ``Stats``. Usage:: >>> ev = Stats() >>> iv = Stats.make_iv(gene=0x00ff) >>> max_iv = Stats(32, 32, 32, 32, 32, 32) >>> species_strengths = Stats.make_species_strengths('eevee') """ _NAMES: ClassVar[Tuple[str, ...]] = ( 'hp', 'attack', 'defense', 'special_attack', 'special_defense', 'speed', ) hp: Real = attr.ib(validator=instance_of(Real), default=0) attack: Real = attr.ib(validator=instance_of(Real), default=0) defense: Real = attr.ib(validator=instance_of(Real), default=0) special_attack: Real = attr.ib(validator=instance_of(Real), default=0) special_defense: Real = attr.ib(validator=instance_of(Real), default=0) speed: Real = attr.ib(validator=instance_of(Real), default=0) def __add__(self, other): return self._make_operator(operator.add, other) def __sub__(self, other): return self._make_operator(operator.sub, other) def __mul__(self, other): return self._make_operator(operator.mul, other) def __floordiv__(self, other): return self._make_operator(operator.floordiv, other) __radd__ = __add__ __rmul__ = __mul__ @classmethod def make_nature_modifiers(cls, nature: str) -> 'Stats': """Create a NatureModifiers instance from the Nature table. :param nature: The identifier of a nature. :return: A ``Stats`` instance. """ nature_row = _database.get_nature(identifier=nature) modifiers = {} for stat in cls._NAMES: modifiers[stat] = 1 if nature_row.is_neutral: return cls(**modifiers) increased_stat = nature_row.increased_stat.identifier.replace('-', '_') decreased_stat = nature_row.decreased_stat.identifier.replace('-', '_') modifiers[increased_stat] = 1.1 modifiers[decreased_stat] = 0.9 return cls(**modifiers) @classmethod def make_species_strengths(cls, species: str) -> 'Stats': """Create a Pokémon's species strengths stats. :param species: The identifier of a Pokémon species. :return: A ``Stats`` instance. """ pokemon = _database.get_pokemon(species=species) strengths = {} for i, stat in enumerate(cls._NAMES): strengths[stat] = pokemon.stats[i].base_stat return cls(**strengths) @classmethod def make_iv(cls, gene: int) -> 'Stats': """Create IV stats from a Pokémon's gene. :param gene: An ``int`` generated by the PRNG. :return: A ``Stats`` instance. """ return cls( hp=gene % 32, attack=(gene >> 5) % 32, defense=(gene >> 10) % 32, speed=(gene >> 16) % 32, special_attack=(gene >> 21) % 32, special_defense=(gene >> 26) % 32, ) @classmethod def make_ev_yield(cls, species: str) -> 'Stats': """Create an EV instance from PokemonStats table. :param species: The identifier of a Pokémon species. :return: A ``Stats`` instance. """ pokemon = _database.get_pokemon(species=species) stats = pokemon.stats yields = {stat: stats[i].effort for i, stat in enumerate(cls._NAMES)} return cls(**yields) def validate_iv(self) -> bool: """Check if each IV is between 0 and 32.""" for stat in self._NAMES: if not (0 <= getattr(self, stat) <= 32): raise ValueError( f"The {stat} IV ({getattr(self, stat)}) must be a number " "between 0 and 32 inclusive." ) return True def _make_operator( self, operator: Callable[[Real, Real], Real], other: Union['Stats', Real], ) -> 'Stats': """Programmatically create point-wise operators. :param operator: A callable (Real, Real) -> Real. :param other: If ``other`` is a ``Stats`` instance, then the operator will be applied point-wisely. If ``other`` is a number, then a scalar operation will be applied. :return: A ``Stats`` instance. """ if not isinstance(other, type(self)) and not isinstance(other, Real): raise TypeError( f"unsupported operand type(s) for {operator}: " f"'{type(self)}' and '{type(other)}'" ) result_stats = {} for stat in self._NAMES: if isinstance(other, type(self)): result_stats[stat] = operator( getattr(self, stat), getattr(other, stat) ) elif isinstance(other, Real): result_stats[stat] = operator(getattr(self, stat), other) return self.__class__(**result_stats) PK!Hu)GTU pokemaster-0.1.4.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)$qzd&Y)r$UV&UrPK!H{#pokemaster-0.1.4.dist-info/METADATAVmr?I,U@P,cyC2EHʊk+!9ʬT$s&9I^@qLcQVUd:PCW,*?*Me;eid@otAcs&#xk&&U\z;|? @G*+qp{}0E\66Mo>y>t+`zs滅kt^[lb̽TLϕ.T٪ۋWoޛSTYz@wӡ4nfb'*¬ 2[UkkWFp:d Y'x K!imQۋޛӓ2J[T7kI1,]\Q|\9D&Z[ {rΎU:Lg22ۚmsT !+<ַJ"lt6/;vsk^LۋBfѺ|Jfu8'BntIQM+\k%Z' ?lD.TrQxRc"y҃Tm.#vF)NݟgYXiΰL=A^ZZBvxRDْPҪ}m2iF5PWhUUXO)bQф-]~MC͔/D=a'T#dJQTI f m>\GGֲ$I t!tͦU'3PRsKSbN<-IxnVfw"EAgdb*T#UX +$ɀARcEQ8֝]'> AkR@ ʆ2I$w6Z!nDu>:kYWΛ1:K]`ny>gL/ ;X~PS\!FGzPn-UyfݐjKYnKl!f x":-loҲ0 榰,Ή,d!K J]Ώ3W Yg؃x}>ԁKwr'uvYKM˓}ЉeL*LQ|) m^mcD d|?OΨ;8ϻK-x[-:xg*v+v*d#.b}'?uSv&mS &HJ>IXEn 9 ` ~e.q;f;^r!@?& JmIrvȹnl˅u qmGUިc;t9”gn؄' 戴+>?O}! |l1559QQŃz]M