def compute_scores(attacker): """Computes the scores of a single rental Pokemon against all other rental Pokemon and all boss Pokemon. Called by the multiprocessing pool. Parameters: attacker (Pokemon): the rental Pokemon to be scored. """ logger = logging.getLogger(LOG_NAME) attacker_id = attacker.name_id # Load a separate dictionary for each process because elements get popped # and re-added during the matchup scoring process. with open( base_dir + '/data/rental_pokemon.json', 'r', encoding='utf8' ) as file: rental_pokemon = jsonpickle.decode(file.read()) with open( base_dir + '/data/boss_pokemon.json', 'r', encoding='utf8' ) as file: boss_pokemon = jsonpickle.decode(file.read()) # First iterate through all boss Pokemon and score the interactions. boss_matchups = {} for defender_id in tuple(boss_pokemon): defender = boss_pokemon[defender_id] score = matchup_scoring.evaluate_matchup( attacker, defender, rental_pokemon.values() ) boss_matchups[defender_id] = score logger.debug( f'Matchup between {attacker_id} and {defender_id}: {score:.2f}' ) # Then, iterate through all rental Pokemon and score the interactions. rental_matchups = {} rental_score = 0 for defender_id in tuple(rental_pokemon): defender = rental_pokemon[defender_id] score = matchup_scoring.evaluate_matchup( attacker, defender, rental_pokemon.values() ) rental_matchups[defender_id] = score # We sum the attacker's score which will later be normalized. rental_score += score logger.debug( f'Matchup between {attacker_id} and {defender_id}: {score:.2f}' ) rental_score /= len(rental_pokemon) logger.debug(f'Score for {attacker_id}: {rental_score:.3f}') # Return the results as a tuple which will be unpacked and repacked in # dicts later, with the first element (the Pokemon's name identifier) as # the key and the other elements as values in their respective dicts. logger.info('Finished computing matchups for %s', attacker) return (attacker_id, boss_matchups, rental_matchups, rental_score)
def scientist(ctrlr) -> str: """Take (or not) a Pokemon from the scientist.""" run = ctrlr.current_run ctrlr.log('Scientist encountered.', 'DEBUG') # Consider the amount of remaining minibosses when scoring each rental # Pokemon, at the start of the run, there are 3 - num_caught minibosses # and 1 final boss. We weigh the boss more heavily because it is more # difficult than the other bosses. rental_weight = 3 - run.num_caught boss_weight = 2 # Calculate scores for an average and existing Pokemon. pokemon_scores = [] for name_id in run.rental_pokemon: score = matchup_scoring.get_weighted_score( run.rental_scores[name_id], rental_weight, run.boss_matchups[name_id][ctrlr.boss], boss_weight ) pokemon_scores.append(score) average_score = sum(pokemon_scores) / len(pokemon_scores) # TODO: actually read the current Pokemon's health so the bot can # decide to switch if it's low. existing_score = matchup_scoring.get_weighted_score( run.rental_scores[run.pokemon.name_id], rental_weight, matchup_scoring.evaluate_matchup( run.pokemon, run.boss_pokemon[ctrlr.boss], run.team_pokemon, run.lives ), boss_weight ) ctrlr.log(f'Score for average pokemon: {average_score:.2f}', 'DEBUG') ctrlr.log( f'Score for {run.pokemon.name_id}: {existing_score:.2f}', 'DEBUG') if average_score > existing_score: # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_buttons((None, 3), (b'a', 2 + VIDEO_EXTRA_DELAY)) run.pokemon = None ctrlr.log('Took a Pokemon from the scientist.') else: # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_buttons((None, 3), (b'b', 2 + VIDEO_EXTRA_DELAY)) ctrlr.log(f'Decided to keep going with {run.pokemon.name_id}') # Read teammates. ctrlr.identify_team_pokemon() # If we took a Pokemon from the scientist, try to identify it. # Note: as of Python 3.6, dicts remember insertion order so using an # OrderedDict is unnecessary. if average_score > existing_score: ctrlr.log(f'Identified {run.pokemon.name_id} as our new Pokemon.') ctrlr.push_button(None, 5) return 'detect'
def calculate_score(self, possible_pokemon, legendary): # just pass things through some kind of algorithm that determines an average score # *just* based on type the_score = 0.0 for pokemon in possible_pokemon: the_score += matchup_scoring.evaluate_matchup(pokemon, legendary) the_score /= len(possible_pokemon) return the_score
def scientist(ctrlr) -> str: """Take (or not) a Pokemon from the scientist.""" run = ctrlr.current_run ctrlr.log('Scientist encountered.', 'DEBUG') if run.pokemon is not None: # Consider the amount of remaining minibosses when scoring each rental # Pokemon, at the start of the run, there are 3 - num_caught minibosses # and 1 final boss. We weigh the boss more heavily because it is more # difficult than the other bosses. rental_weight = 3 - run.num_caught boss_weight = 2 # Calculate scores for an average and existing Pokemon. pokemon_scores = [] for name_id in run.rental_pokemon: score = matchup_scoring.get_weighted_score( run.rental_scores[name_id], rental_weight, run.boss_matchups[name_id][ctrlr.boss], boss_weight) pokemon_scores.append(score) average_score = sum(pokemon_scores) / len(pokemon_scores) # TODO: actually read the current Pokemon's health so the bot can # decide to switch if it's low. existing_score = matchup_scoring.get_weighted_score( run.rental_scores[run.pokemon.name_id], rental_weight, matchup_scoring.evaluate_matchup( run.pokemon, run.boss_pokemon[ctrlr.boss], run.rental_pokemon), boss_weight) * run.HP ctrlr.log(f'Score for average pokemon: {average_score:.2f}', 'DEBUG') ctrlr.log(f'Score for {run.pokemon.name_id}: {existing_score:.2f}', 'DEBUG') # If current pokemon is None, it means we just already talked to scientist # Also it means we took the pokemon from scientist. # So let's try to pick it up again if run.pokemon is None or average_score > existing_score: # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_buttons((None, 3), (b'a', 7 + VIDEO_EXTRA_DELAY)) run.pokemon = None ctrlr.log('Took a Pokemon from the scientist.') else: # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_buttons((None, 3), (b'b', 7 + VIDEO_EXTRA_DELAY)) ctrlr.log(f'Decided to keep going with {run.pokemon.name_id}') return 'detect'
def catch(ctrlr) -> str: """Catch each boss after defeating it.""" run = ctrlr.current_run # Check if we need to skip catching a the final boss. # This scenario is used by Ball Saver mode when it can't afford to reset # the game. if ( run.num_caught == 3 and ctrlr.mode == 'ball saver' and not ctrlr.check_sufficient_ore(1) ): ctrlr.log('Finishing the run without wasting a ball on the boss.') ctrlr.push_buttons((b'v', 2), (b'a', 10)) ctrlr.log('Congratulations!') return 'select_pokemon' # Catch the boss in almost all cases. ctrlr.log(f'Catching boss #{run.num_caught + 1}.') # Start by navigating to the ball selection screen ctrlr.push_button(b'a', 2) # then navigate to the ball specified in the config file while (ctrlr.get_target_ball() not in ctrlr.check_ball()): ctrlr.push_button(b'<', 2 + VIDEO_EXTRA_DELAY) ctrlr.push_button(b'a', 30) ctrlr.record_ball_use() # If the caught Pokemon was not the final boss, check out the Pokemon and # decide whether to keep it. if run.num_caught < 4: # Note that read_selectable_pokemon returns a list of preconfigured # Pokemon objects with types, abilities, stats, moves, et cetera. # # In this stage the list contains only 1 item. pokemon = ctrlr.read_selectable_pokemon('catch')[0] run.caught_pokemon.append(pokemon.name_id) # Update the list of potential minibosses that we might encounter later # in the den. run.prune_potential_minibosses() ctrlr.log( 'The following Pokemon have been encountered and will not appear ' f'again: {run.all_encountered_pokemon}', 'DEBUG' ) ctrlr.log( 'The following Pokemon may still appear along the target path: ' f'{[x for x in run.potential_boss_pokemon]}', 'DEBUG' ) # Consider the amount of remaining minibosses when scoring each rental # Pokemon, at the start of the run, there are 3 - num_caught minibosses # and 1 final boss. We weigh the boss more heavily because it is more # difficult than the other bosses. rental_weight = 3 - run.num_caught boss_weight = 2 # Calculate scores for every potential team resulting from the decision # to take or leave the new Pokemon. team = run.team_pokemon # Re-measure team HP. for i, HP in enumerate(ctrlr.measure_team_HP()): if i == 0: run.pokemon.HP = HP else: team[i - 1].HP = HP team_scores = [] potential_teams = ( (pokemon, team[0], team[1], team[2]), (run.pokemon, team[0], team[1], team[2]), (run.pokemon, pokemon, team[1], team[2]), (run.pokemon, team[0], pokemon, team[2]), (run.pokemon, team[0], team[1], pokemon) ) ctrlr.log(f'HP of current team: {[x.HP for x in team]}.', 'DEBUG') for potential_team in potential_teams: score = matchup_scoring.get_weighted_score( matchup_scoring.evaluate_average_matchup( potential_team[0], run.potential_boss_pokemon.values(), potential_team[1:], run.lives ), rental_weight, matchup_scoring.evaluate_matchup( potential_team[0], run.boss_pokemon[run.boss], potential_team[1:], run.lives ), boss_weight ) ctrlr.log( 'Score for potential team: ' f'{[x.name_id for x in potential_team]}: {score:.2f}', 'DEBUG') team_scores.append(score) # Choosing not to take the Pokemon may result in a teammate choosing # it, or none may choose it. The mechanics of your teammates choosing # is currently not known. Qualitatively, it seems like they choose yes # or no randomly with about 50% chance. Using this assumption, the # chance that none of them take the Pokemon is (1/2)^3 or 12.5%. choose_score = team_scores[0] leave_score = 0.125 * team_scores[1] + 0.875 * sum(team_scores[2:]) / 3 ctrlr.log( f'Score for taking {pokemon.name_id}: {choose_score:.2f}', 'DEBUG') ctrlr.log( f'Score for declining {pokemon.name_id}: {leave_score:.2f}', 'DEBUG') # Compare the scores for the two options and choose the best one. if choose_score > leave_score: # Choose to swap your existing Pokemon for the new Pokemon. run.pokemon = pokemon ctrlr.log(f'Decided to swap for {run.pokemon.name_id}.') # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_button(b'a', 6) else: ctrlr.log(f'Decided to keep going with {run.pokemon.name_id}.') # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_button(b'b', 6) # Re-read teammates in case something changed. ctrlr.identify_team_pokemon() run.prune_potential_minibosses() # Move on to the detect stage. return 'detect' # If the final boss was the caught Pokemon, wrap up the run and check the # Pokemon caught along the way. else: run.caught_pokemon.append(ctrlr.boss) ctrlr.push_button(None, 10) ctrlr.log('Congratulations!') return 'select_pokemon'
def catch(ctrlr) -> str: """Catch each boss after defeating it.""" run = ctrlr.current_run # Check if we need to skip catching a the final boss. # This scenario is used by Ball Saver mode when it can't afford to reset # the game. if (run.num_caught == 3 and ctrlr.mode == 'ball saver' and not ctrlr.check_sufficient_ore(1)): ctrlr.log('Finishing the run without wasting a ball on the boss.') ctrlr.push_buttons((b'v', 2), (b'a', 30)) ctrlr.log('Congratulations!') return 'select_pokemon' # Catch the boss in almost all cases. ctrlr.log(f'Catching boss #{run.num_caught + 1}.') # Start by navigating to the ball selection screen ctrlr.push_button(b'a', 2) # then navigate to the ball specified in the config file while (ctrlr.get_target_ball().lower() != 'default' and ctrlr.get_target_ball() not in ctrlr.check_ball()): ctrlr.push_button(b'<', 2 + VIDEO_EXTRA_DELAY) ctrlr.push_button(b'a', 30) ctrlr.record_ball_use() # If the caught Pokemon was not the final boss, check out the Pokemon and # decide whether to keep it. if run.num_caught < 4: # Note that read_selectable_pokemon returns a list of preconfigured # Pokemon objects with types, abilities, stats, moves, et cetera. # # In this stage the list contains only 1 item. pokemon = ctrlr.read_selectable_pokemon('catch')[0] # Consider the amount of remaining minibosses when scoring each rental # Pokemon, at the start of the run, there are 3 - num_caught minibosses # and 1 final boss. We weigh the boss more heavily because it is more # difficult than the other bosses. rental_weight = 3 - run.num_caught boss_weight = 2 # Calculate scores for the new and existing Pokemon. # TODO: actually read the current Pokemon's health so the bot can # decide to switch if it's low. score = matchup_scoring.get_weighted_score( run.rental_scores[pokemon.name_id], rental_weight, run.boss_matchups[pokemon.name_id][ctrlr.boss], boss_weight) existing_score = matchup_scoring.get_weighted_score( run.rental_scores[run.pokemon.name_id], rental_weight, matchup_scoring.evaluate_matchup( run.pokemon, run.boss_pokemon[ctrlr.boss], run.rental_pokemon), boss_weight) * run.HP ctrlr.log(f'Score for {pokemon.name_id}: {score:.2f}', 'DEBUG') ctrlr.log(f'Score for {run.pokemon.name_id}: {existing_score:.2f}', 'DEBUG') run.caught_pokemon.append(pokemon.name_id) # Compare the scores for the two options and choose the best one. if score > existing_score: # Choose to swap your existing Pokemon for the new Pokemon. run.pokemon = pokemon # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_button(b'a', 7) ctrlr.log(f'Decided to swap for {run.pokemon.name_id}.') else: # Note: a long delay is required here so the bot doesn't think a # battle started. ctrlr.push_button(b'b', 7) ctrlr.log(f'Decided to keep going with {run.pokemon.name_id}.') # Move on to the detect stage. return 'detect' # If the final boss was the caught Pokemon, wrap up the run and check the # Pokemon caught along the way. else: run.caught_pokemon.append(ctrlr.boss) ctrlr.push_button(None, 10) ctrlr.log('Congratulations!') return 'select_pokemon'