def evaluate_matchup(attacker: Pokemon,
                     boss: Pokemon,
                     teammates: Iterable[Pokemon] = [],
                     num_lives: int = 1) -> float:
    """Return a matchup score between an attacker and defender, with the
    attacker using optimal moves and the defender using average moves.
    """

    # Transform Ditto (Dynamax Adventure Ditto has Imposter).
    if attacker.name_id == 'ditto':
        attacker = transform_ditto(attacker, boss)
    elif boss.name_id == 'ditto':
        boss = transform_ditto(boss, attacker)

    # Calculate scores for base and Dynamaxed versions of the attacker.
    base_version = copy.copy(attacker)
    base_version.dynamax = False
    dmax_version = copy.copy(attacker)
    dmax_version.dynamax = True
    base_version_score = select_best_move(base_version, boss, Field(),
                                          teammates)[2]
    dmax_version_score = select_best_move(dmax_version, boss, Field(),
                                          teammates)[2]
    score = max(base_version_score,
                (base_version_score + dmax_version_score) / 2)

    # Adjust the score depending on the Pokemon's HP. If there are multiple
    # lives left, fainting is less important.
    assert 1 <= num_lives <= 4, 'num_lives should be between 1 and 4.'
    HP_correction = ((5 - num_lives) * attacker.HP + num_lives - 1) / 4

    return score * HP_correction
def weather_damage_multiplier(attacker: Pokemon, move: Move, defender: Pokemon,
                              field: Field) -> float:
    modifier = 1
    if field.is_weather_sunlight():
        if move.name_id == 'solar-beam':
            modifier *= 2
        elif move.name_id == 'thunder' or move.name_id == 'hurricane':
            modifier *= (0.5 / 0.7)
        elif move.name_id == 'weather-ball':
            move.type_id = 'fire'
            modifier *= 2

        if move.type_id == 'fire':
            modifier *= 1.5
        elif move.type_id == 'water':
            modifier *= 0.5
    elif field.is_weather_rain():
        if move.name_id == 'solar-beam':
            modifier *= 0.5
        elif move.name_id == 'thunder' or move.name_id == 'hurricane':
            modifier *= (1.0 / 0.7)
        elif move.name_id == 'weather-ball':
            move.type_id = 'water'
            modifier *= 2

        if move.type_id == 'water':
            modifier *= 1.5
        elif move.type_id == 'fire':
            modifier *= 0.5

    elif field.is_weather_hail():
        if move.name_id == 'solar-beam':
            modifier *= 0.5
        elif move.name_id == 'blizzard':
            modifier *= (1.0 / 0.7)
        elif move.name_id == 'weather-ball':
            move.type_id = 'ice'
            modifier *= 2
    elif field.is_weather_sandstorm():
        if move.name_id == 'solar-beam':
            modifier *= 0.5
        elif move.name_id == 'weather-ball':
            move.type_id = 'rock'
            modifier *= 2
    else:
        if move.name_id == 'weather-ball':
            move.type_id = 'normal'

    return modifier
def terrain_damage_multiplier(attacker: Pokemon, move: Move, defender: Pokemon,
                              field: Field) -> float:
    modifier = 1.0

    if 'flying' not in attacker.type_ids or ('levitate' !=
                                             attacker.ability_name_id):
        if field.is_terrain_electric():
            if move.name_id == 'terrain-pulse':
                move.type_id = 'electric'
                modifier *= 1.5

            if move.type_id == 'electric':
                modifier *= 1.3
        elif field.is_terrain_grassy():
            if move.name_id == 'terrain-pulse':
                move.type_id = 'grass'
                modifier *= 1.5

            if move.type_id == 'grass':
                modifier *= 1.3
        elif field.is_terrain_psychic():
            if move.name_id == 'terrain-pulse':
                move.type_id = 'psychic'
                modifier *= 1.5
            elif move.name_id == 'expanding-force':
                modifier *= 1.5

            if move.type_id == 'psychic':
                modifier *= 1.3

        elif field.is_terrain_misty():
            if move.name_id == 'terrain-pulse':
                move.type_id = 'fairy'
                modifier *= 1.5

            if move.type_id == 'dragon':
                modifier *= 0.5

    if 'flying' not in defender.type_ids or ('levitate' !=
                                             defender.ability_name_id):
        if field.is_terrain_electric():
            if move.name_id == 'rising-voltage':
                modifier *= 2

    return modifier
示例#4
0
 def reset_stage(self) -> None:
     """Reset after a battle."""
     self.move_index = 0
     self.dmax_timer = -1
     self.opponent = None
     self.dynamax_available = False
     self.field = Field()
     if self.pokemon is not None:
         if self.pokemon.name_id == 'ditto':
             self.pokemon = self.rental_pokemon['ditto']
         self.pokemon.dynamax = False
示例#5
0
def test_weather(rental_pokemon, boss_pokemon):
    print('Weather tests')
    field_clear = Field()
    field_clear.set_weather_clear()

    field_rain = Field()
    field_rain.set_weather_rain()

    field_sandstorm = Field()
    field_sandstorm.set_weather_sandstorm()

    field_sunlingt = Field()
    field_sunlingt.set_weather_sunlight()

    field_hail = Field()
    field_hail.set_weather_hail()
    print('Solar beam (clear / rain / sandstorm / sun / hail)')
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['exeggutor'], 1,
                                               boss_pokemon['guzzlord'], field)
        print(f'Damage is {dmg}')

    print('Water attack (clear / rain / sandstorm / sun / hail)')
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['vaporeon'], 0,
                                               boss_pokemon['guzzlord'], field)
        print(f'Damage is {dmg}')

    print('Fire attack (clear / rain / sandstorm / sun / hail)')
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['flareon'], 0,
                                               boss_pokemon['guzzlord'], field)
        print(f'Damage is {dmg}')

    print(
        'Weather ball attack vs ground type (clear / rain / sandstorm / sun / hail)'
    )
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2,
                                               rental_pokemon['marowak'],
                                               field)
        print(f'Damage is {dmg}')

    print(
        'Weather ball attack vs flying type (clear / rain / sandstorm / sun / hail)'
    )
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2,
                                               rental_pokemon['unfezant'],
                                               field)
        print(f'Damage is {dmg}')

    print(
        'Weather ball attack vs grass type (clear / rain / sandstorm / sun / hail)'
    )
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 2,
                                               rental_pokemon['ivysaur'],
                                               field)
        print(f'Damage is {dmg}')

    print(
        'Special attack vs rock type (clear / rain / sandstorm / sun / hail)')
    for field in [
            field_clear, field_rain, field_sandstorm, field_sunlingt,
            field_hail
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['roselia'], 0,
                                               rental_pokemon['sudowoodo'],
                                               field)
        print(f'Damage is {dmg}')
示例#6
0
def main():
    with open(os.path.join(base_dir, 'data', 'boss_pokemon.json'),
              'r',
              encoding='utf8') as file:
        boss_pokemon = jsonpickle.decode(file.read())
    with open(os.path.join(base_dir, 'data', 'rental_pokemon.json'),
              'r',
              encoding='utf8') as file:
        rental_pokemon = jsonpickle.decode(file.read())
    with open(os.path.join(base_dir, 'data', 'boss_matchup_LUT.json'),
              'r',
              encoding='utf8') as file:
        boss_matchups = jsonpickle.decode(file.read())
    with open(os.path.join(base_dir, 'data', 'rental_matchup_LUT.json'),
              'r',
              encoding='utf8') as file:
        rental_matchups = jsonpickle.decode(file.read())
    with open(os.path.join(base_dir, 'data', 'rental_pokemon_scores.json'),
              'r',
              encoding='utf8') as file:
        rental_scores = jsonpickle.decode(file.read())

    # Test retrieval of a rental Pokemon
    rental_pokemon['stunfisk-galar'].print_verbose()
    print('________________________________________')

    # Test retrieval of a boss Pokemon
    boss_pokemon['mewtwo'].print_verbose()
    print('________________________________________')

    # Test retrieval of rental Pokemon matchups
    print('Matchup for Chansey against Golurk (poor): '
          f'{rental_matchups["chansey"]["golurk"]}')
    print('Matchup for Carkol against Butterfree (good): '
          f'{rental_matchups["carkol"]["butterfree"]}')
    print('________________________________________')

    # Test retrieval of boss Pokemon matchups
    print('Matchup for Jynx against Heatran (poor): '
          f'{boss_matchups["jynx"]["heatran"]}')
    print('Matchup for Golurk against Raikou (good): '
          f'{boss_matchups["golurk"]["raikou"]}')
    print('________________________________________')

    # Test retrieval of rental Pokemon scores
    print(f'Score for Jigglypuff (poor): {rental_scores["jigglypuff"]}')
    print(f'Score for Doublade (good): {rental_scores["doublade"]}')
    print('________________________________________')

    # Test move selection
    print('Wide Guard utility:')
    matchup_scoring.print_matchup_summary(rental_pokemon['pelipper'],
                                          boss_pokemon['groudon'], Field(),
                                          rental_pokemon.values())
    salazzle = rental_pokemon['salazzle']
    print('Regular matchup:')
    matchup_scoring.print_matchup_summary(salazzle, boss_pokemon['kartana'],
                                          Field(), rental_pokemon.values())
    print('Max move scores:')
    salazzle.dynamax = True
    matchup_scoring.print_matchup_summary(salazzle, boss_pokemon['kartana'],
                                          Field(), rental_pokemon.values())
    print('Sap Sipper:')
    matchup_scoring.print_matchup_summary(rental_pokemon['tsareena'],
                                          rental_pokemon['azumarill'], Field(),
                                          rental_pokemon.values())
    print('________________________________________')

    # Ensure all rental Pokemon have sprites
    with open(os.path.join(base_dir, 'data', 'pokemon_sprites.pickle'),
              'rb') as file:
        pokemon_sprites = pickle.load(file)
    pokemon_sprites_dict = dict(pokemon_sprites)
    for name_id in rental_pokemon:
        if pokemon_sprites_dict.get(name_id) is None:
            raise KeyError(f'ERROR: no image found for: {name_id}')
    print('Successfully tested Pokemon sprite importing without errors.')

    print('________________________________________')
    test_field(rental_pokemon, boss_pokemon)
示例#7
0
def test_terrain(rental_pokemon, boss_pokemon):
    print('Terrain tests')
    field_clear = Field()
    field_clear.set_terrain_clear()

    field_electric = Field()
    field_electric.set_terrain_electric()

    field_psychic = Field()
    field_psychic.set_terrain_psychic()

    field_grassy = Field()
    field_grassy.set_terrain_grassy()

    field_misty = Field()
    field_misty.set_terrain_misty()
    print('electric move (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['electabuzz'], 1,
                                               boss_pokemon['guzzlord'], field)
        print(f'Damage is {dmg}')

    print('rising voltage (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['electabuzz'], 0,
                                               boss_pokemon['guzzlord'], field)
        print(f'Damage is {dmg}')

    print('psychic move (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['slowbro'], 3,
                                               rental_pokemon['electabuzz'],
                                               field)
        print(f'Damage is {dmg}')

    print('expanding force (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['slowbro'], 0,
                                               rental_pokemon['electabuzz'],
                                               field)
        print(f'Damage is {dmg}')

    print('dragon move (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['charmeleon'], 1,
                                               rental_pokemon['electabuzz'],
                                               field)
        print(f'Damage is {dmg}')

    print('grass move (clear / electric / psychic / grassy / misty)')
    for field in [
            field_clear, field_electric, field_psychic, field_grassy,
            field_misty
    ]:
        dmg = matchup_scoring.calculate_damage(rental_pokemon['tangela'], 2,
                                               rental_pokemon['electabuzz'],
                                               field)
        print(f'Damage is {dmg}')
def calculate_damage(attacker: Pokemon,
                     move_index: int,
                     defender: Pokemon,
                     field: Field,
                     multiple_targets: bool = False) -> float:
    """Return the damage (default %) of a move used by the attacker against the
    defender.
    """

    move = (attacker.max_moves[move_index]
            if attacker.dynamax else attacker.moves[move_index])

    modifier = 0.925  # Random between 0.85 and 1
    modifier *= move.accuracy
    if multiple_targets and move.is_spread:
        modifier *= 0.75
    # Ignore crits

    # It needs to be before STAB
    modifier *= weather_damage_multiplier(attacker, move, defender, field)

    modifier *= terrain_damage_multiplier(attacker, move, defender, field)

    # It needs to be before STAB
    if move.type_id == 'normal':
        if attacker.ability_name_id == 'refrigerate':
            move.type_id = 'ice'
            modifier *= 1.2
        elif attacker.ability_name_id == 'aerilate':
            move.type_id = 'flying'
            modifier *= 1.2
        elif attacker.ability_name_id == 'galvanize':
            move.type_id = 'electric'
            modifier *= 1.2
        elif attacker.ability_name_id == 'pixilate':
            move.type_id = 'fairy'
            modifier *= 1.2
    else:
        if attacker.ability_name_id == 'normalize':
            move.type_id = 'normal'
            modifier *= 1.2

    if move.type_id in attacker.type_ids:  # Apply STAB
        # Note that Adaptability is handled elsewhere.
        modifier *= 1.5
    # Apply type effectiveness
    if move.name_id != 'thousand-arrows' or 'flying' not in defender.type_ids:
        modifier *= type_damage_multiplier(move.type_id, defender.type_ids)
    # Apply status effects
    if move.category == 'physical' and attacker.status == 'burn':
        modifier *= 0.5
    # Apply modifiers from abilities
    modifier *= ability_damage_multiplier(attacker, move_index, defender)
    # Apply boosts from auras
    if move.type_id == 'fairy' and (attacker.ability_name_id == 'fairy-aura' or
                                    defender.ability_name_id == 'fairy-aura'):
        modifier *= 1.33
    if move.type_id == 'dark' and (attacker.ability_name_id == 'dark-aura'
                                   or defender.ability_name_id == 'dark-aura'):
        modifier *= 1.33

    # Apply attacker and defender stats
    if move.category == 'physical':
        if move.name_id == 'body-press':
            numerator = attacker.stats[2]
        elif move.name_id == 'foul-play':
            numerator = defender.stats[1]
        else:
            numerator = attacker.stats[1]
        denominator = defender.stats[2]
    else:
        numerator = attacker.stats[3]
        if move.name_id not in ('psystrike', 'psyshock'):
            denominator = defender.stats[4]
            if field.is_weather_sandstorm() and 'rock' in defender.type_ids:
                denominator *= 1.5
        else:
            denominator = defender.stats[2]

    return (((2 / 5 * attacker.level + 2) * move.power * numerator /
             denominator / 50 + 2) * modifier / defender.stats[0])