def get_move_combination_scores(mutator, depth=2, previous_moves=tuple(), previous_instructions=()): """ :param mutator: a StateMutator object representing the state of the battle :param depth: the remaining depth before the state is evaluated :param previous_moves: any previous moves that happened prior to this call :param previous_instructions: any previous instructions that were applied to the state before this call :return: a dictionary representing the potential move combinations and their associated scores """ depth -= 1 if battle_is_over(mutator.state): return { (constants.DO_NOTHING_MOVE, constants.DO_NOTHING_MOVE): evaluate(mutator.state) } 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() for i, user_move in enumerate(user_options): for j, opponent_move in enumerate(opponent_options): 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_move_combination_scores( mutator, depth, previous_moves=previous_moves + (user_move, opponent_move), previous_instructions=previous_instructions + tuple(instructions.instructions))) score += safest[1] * this_percentage mutator.reverse(instructions.instructions) state_scores[(user_move, opponent_move)] = score return state_scores
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
def test_returns_false_when_only_reserve_are_dead_for_opponent(self): for pkmn in self.state.opponent.reserve.values(): pkmn.hp = 0 self.assertFalse(battle_is_over(self.state))
def test_returns_false_when_only_active_is_dead_for_opponent(self): self.state.opponent.active.hp = 0 self.assertFalse(battle_is_over(self.state))
def test_returns_false_when_all_pokemon_are_alive_for_opponent(self): self.assertFalse(battle_is_over(self.state))
def test_returns_true_when_all_pokemon_for_opponent_are_dead(self): self.state.opponent.active.hp = 0 for pkmn in self.state.opponent.reserve.values(): pkmn.hp = 0 self.assertTrue(battle_is_over(self.state))