def test_returns_only_options_from_one_item_dictionary(self): score_lookup = {("a", "b"): 100} safest = pick_safest(score_lookup) expected_result = (("a", "b"), 100) self.assertEqual(expected_result, safest)
def test_returns_better_option_for_two_different_moves(self): score_lookup = {("a", "b"): 100, ("c", "b"): 200} safest = pick_safest(score_lookup) expected_result = (("c", "b"), 200) self.assertEqual(expected_result, safest)
def test_returns_option_with_the_lowest_minimum_in_2_by_2(self): score_lookup = { ("a", "x"): 100, ("a", "y"): -100, ("c", "x"): 200, ("c", "y"): -200, } safest = pick_safest(score_lookup) expected_result = (("a", "y"), -100) self.assertEqual(expected_result, safest)
def find_winner(mutator, p1, p2): mutator = deepcopy(mutator) mutator.state.self.reserve = dict() mutator.state.self.active = p1 mutator.state.opponent.reserve = dict() mutator.state.opponent.active = p2 evaluation = evaluate(mutator.state) scores = get_payoff_matrix(mutator, depth=2) safest = pick_safest(scores) return safest[1] > evaluation
def find_best_move_safest(battles): all_scores = dict() for i, b in enumerate(battles): state = b.to_object() mutator = StateMutator(state) logger.debug("Attempting to find best move from: {}".format(mutator.state)) scores = get_payoff_matrix(mutator, depth=config.search_depth, prune=True) prefixed_scores = prefix_opponent_move(scores, str(i)) all_scores = {**all_scores, **prefixed_scores} decision, payoff = pick_safest(all_scores) bot_choice = decision[0] logger.debug("Safest: {}, {}".format(bot_choice, payoff)) return bot_choice
def get_payoff_matrix(mutator, depth=2, forced_options=None, prune=None): """ :param mutator: a StateMutator object representing the state of the battle :param depth: the remaining depth before the state is evaluated :param forced_options: options that can be forced instead of using `get_all_options` :param prune: specify whether or not to prune the tree :return: a dictionary representing the potential move combinations and their associated scores """ if prune is None: prune = True winner = battle_is_over(mutator.state) if winner: return { (constants.DO_NOTHING_MOVE, constants.DO_NOTHING_MOVE): evaluate(mutator.state) + WON_BATTLE * depth * winner } depth -= 1 if forced_options: user_options, opponent_options = forced_options else: user_options, opponent_options = get_all_options(mutator) # if the battle is not over, but the opponent has no moves - we want to return the user options as moves # this is a special case in a random battle where the opponent's pokemon has fainted, but the opponent still # has reserves left that are unseen if opponent_options == [constants.DO_NOTHING_MOVE ] and mutator.state.opponent.active.hp == 0: return {(user_option, constants.DO_NOTHING_MOVE): evaluate(mutator.state) for user_option in user_options} state_scores = dict() best_score = float('-inf') for i, user_move in enumerate(user_options): worst_score_for_this_row = float('inf') skip = False # opponent_options can change during the loop # using opponent_options[:] makes a copy when iterating to ensure no funny-business for j, opponent_move in enumerate(opponent_options[:]): if skip: state_scores[(user_move, opponent_move)] = float('nan') continue score = 0 state_instructions = get_all_state_instructions( mutator, user_move, opponent_move) if depth == 0: for instructions in state_instructions: mutator.apply(instructions.instructions) t_score = evaluate(mutator.state) score += (t_score * instructions.percentage) mutator.reverse(instructions.instructions) else: for instructions in state_instructions: this_percentage = instructions.percentage mutator.apply(instructions.instructions) safest = pick_safest( get_payoff_matrix(mutator, depth, prune=prune)) score += safest[1] * this_percentage mutator.reverse(instructions.instructions) state_scores[(user_move, opponent_move)] = score if score < worst_score_for_this_row: worst_score_for_this_row = score if prune and score < best_score: skip = True # MOST of the time in pokemon, an opponent's move that causes a prune will cause a prune elsewhere # move this item to the front of the list to prune faster opponent_options = move_item_to_front_of_list( opponent_options, opponent_move) if worst_score_for_this_row > best_score: best_score = worst_score_for_this_row return state_scores
'canMegaEvo': False } }, 'side_conditions': { 'toxic_count': 0 }, 'trapped': False }, 'weather': None, 'field': None, 'trickroom': False, 'forceSwitch': False, 'wait': False } # second = {'self': {'active': {'id': 'audino', 'level': 81, 'hp': 299, 'maxhp': 299, 'ability': 'regenerator', 'baseStats': {'hp': 103, 'attack': 60, 'defense': 86, 'special-attack': 60, 'special-defense': 86, 'speed': 50}, 'attack': 144, 'defense': 186, 'special-attack': 144, 'special-defense': 186, 'speed': 128, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'healbell', 'disabled': False, 'current_pp': 8}, {'id': 'protect', 'disabled': False, 'current_pp': 16}, {'id': 'wish', 'disabled': False, 'current_pp': 16}, {'id': 'hypervoice', 'disabled': False, 'current_pp': 16}], 'types': ['normal']}, 'reserve': {'heatran': {'id': 'heatran', 'level': 75, 'hp': 0, 'maxhp': 260, 'ability': 'flashfire', 'baseStats': {'hp': 91, 'attack': 90, 'defense': 106, 'special-attack': 130, 'special-defense': 106, 'speed': 77}, 'attack': 179, 'defense': 203, 'special-attack': 239, 'special-defense': 203, 'speed': 159, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'roar', 'disabled': False, 'current_pp': 32}, {'id': 'stealthrock', 'disabled': False, 'current_pp': 32}, {'id': 'earthpower', 'disabled': False, 'current_pp': 16}, {'id': 'lavaplume', 'disabled': False, 'current_pp': 24}], 'types': ['fire', 'steel']}, 'omastar': {'id': 'omastar', 'level': 81, 'hp': 246, 'maxhp': 246, 'ability': 'swiftswim', 'baseStats': {'hp': 70, 'attack': 60, 'defense': 125, 'special-attack': 115, 'special-defense': 70, 'speed': 55}, 'attack': 144, 'defense': 249, 'special-attack': 233, 'special-defense': 160, 'speed': 136, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'scald', 'disabled': False, 'current_pp': 24}, {'id': 'shellsmash', 'disabled': False, 'current_pp': 24}, {'id': 'earthpower', 'disabled': False, 'current_pp': 16}, {'id': 'icebeam', 'disabled': False, 'current_pp': 16}], 'types': ['rock', 'water']}, 'alomomola': {'id': 'alomomola', 'level': 77, 'hp': 381, 'maxhp': 381, 'ability': 'regenerator', 'baseStats': {'hp': 165, 'attack': 75, 'defense': 80, 'special-attack': 40, 'special-defense': 45, 'speed': 65}, 'attack': 160, 'defense': 168, 'special-attack': 106, 'special-defense': 114, 'speed': 145, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'knockoff', 'disabled': False, 'current_pp': 32}, {'id': 'wish', 'disabled': False, 'current_pp': 16}, {'id': 'toxic', 'disabled': False, 'current_pp': 16}, {'id': 'scald', 'disabled': False, 'current_pp': 24}], 'types': ['water']}, 'hawlucha': {'id': 'hawlucha', 'level': 75, 'hp': 105, 'maxhp': 241, 'ability': 'unburden', 'baseStats': {'hp': 78, 'attack': 92, 'defense': 75, 'special-attack': 74, 'special-defense': 63, 'speed': 118}, 'attack': 182, 'defense': 156, 'special-attack': 155, 'special-defense': 138, 'speed': 221, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'swordsdance', 'disabled': False, 'current_pp': 32}, {'id': 'stoneedge', 'disabled': False, 'current_pp': 8}, {'id': 'roost', 'disabled': False, 'current_pp': 16}, {'id': 'highjumpkick', 'disabled': False, 'current_pp': 16}], 'types': ['fighting', 'flying']}, 'swalot': {'id': 'swalot', 'level': 83, 'hp': 129, 'maxhp': 302, 'ability': 'stickyhold', 'baseStats': {'hp': 100, 'attack': 73, 'defense': 83, 'special-attack': 73, 'special-defense': 83, 'speed': 55}, 'attack': 169, 'defense': 185, 'special-attack': 169, 'special-defense': 185, 'speed': 139, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'icebeam', 'disabled': False, 'current_pp': 16}, {'id': 'encore', 'disabled': False, 'current_pp': 8}, {'id': 'yawn', 'disabled': False, 'current_pp': 16}, {'id': 'sludgebomb', 'disabled': False, 'current_pp': 16}], 'types': ['poison']}}, 'side_conditions': {'stealthrock': 0, 'spikes': 0}, 'trapped': False}, 'opponent': {'active': {'id': 'zekrom', 'level': 73, 'hp': 202.16, 'maxhp': 266, 'ability': 'teravolt', 'baseStats': {'hp': 100, 'attack': 150, 'defense': 120, 'special-attack': 120, 'special-defense': 100, 'speed': 90}, 'attack': 261, 'defense': 218, 'special-attack': 218, 'special-defense': 188, 'speed': 174, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'boltstrike', 'disabled': False, 'current_pp': 8}, {'id': 'substitute', 'disabled': False, 'current_pp': 16}], 'types': ['dragon', 'electric']}, 'reserve': {'rotomfrost': {'id': 'rotomfrost', 'level': 83, 'hp': 199.29000000000002, 'maxhp': 219, 'ability': 'levitate', 'baseStats': {'hp': 50, 'attack': 65, 'defense': 107, 'special-attack': 105, 'special-defense': 107, 'speed': 86}, 'attack': 156, 'defense': 225, 'special-attack': 222, 'special-defense': 225, 'speed': 190, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'voltswitch', 'disabled': False, 'current_pp': 32}], 'types': ['electric', 'ice']}, 'bibarel': {'id': 'bibarel', 'level': 83, 'hp': 0, 'maxhp': 267, 'ability': None, 'baseStats': {'hp': 79, 'attack': 85, 'defense': 60, 'special-attack': 55, 'special-defense': 60, 'speed': 71}, 'attack': 189, 'defense': 147, 'special-attack': 139, 'special-defense': 147, 'speed': 166, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'return', 'disabled': False, 'current_pp': 32}], 'types': ['normal', 'water']}, 'passimian': {'id': 'passimian', 'level': 83, 'hp': 193.28, 'maxhp': 302, 'ability': None, 'baseStats': {'hp': 100, 'attack': 120, 'defense': 90, 'special-attack': 40, 'special-defense': 60, 'speed': 80}, 'attack': 247, 'defense': 197, 'special-attack': 114, 'special-defense': 147, 'speed': 180, 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'status': None, 'volatileStatus': [], 'moves': [{'id': 'uturn', 'disabled': False, 'current_pp': 32}], 'types': ['fighting']}}, 'side_conditions': {'stealthrock': 0, 'spikes': 0}, 'trapped': False}, 'weather': None, 'field': None, 'forceSwitch': False, 'wait': False} state = State.from_dict(first) mutator = StateMutator(state) instruction = get_all_state_instructions(mutator, 'flareblitz', 'nastyplot') scores = get_payoff_matrix(mutator, depth=2) df = pd.Series(scores).unstack() averages = df.mean(axis=1) safest = pick_safest(scores) pass