def pick_safest_move_using_dynamic_search_depth(battles): """ Dynamically decides how far to look into the game. This requires a strong computer to be able to search 3/4 turns ahead. Using a pypy interpreter will also result in better performance. """ all_scores = dict() num_battles = len(battles) if num_battles > 1: search_depth = 2 for i, b in enumerate(battles): state = b.create_state() mutator = StateMutator(state) user_options, opponent_options = b.get_all_options() logger.debug("Searching through the state: {}".format(mutator.state)) scores = get_payoff_matrix(mutator, user_options, opponent_options, depth=search_depth, prune=True) prefixed_scores = prefix_opponent_move(scores, str(i)) all_scores = {**all_scores, **prefixed_scores} elif num_battles == 1: search_depth = 3 b = battles[0] state = b.create_state() mutator = StateMutator(state) user_options, opponent_options = b.get_all_options() num_user_options = len(user_options) num_opponent_options = len(opponent_options) options_product = num_user_options * num_opponent_options if options_product < 20 and num_user_options > 1 and num_opponent_options > 1: logger.debug("Low options product, looking an additional depth") search_depth += 1 logger.debug("Searching through the state: {}".format(mutator.state)) logger.debug("Options Product: {}".format(options_product)) logger.debug("My Options: {}".format(user_options)) logger.debug("Opponent Options: {}".format(opponent_options)) logger.debug("Search depth: {}".format(search_depth)) all_scores = get_payoff_matrix(mutator, user_options, opponent_options, depth=search_depth, prune=True) else: raise ValueError("less than 1 battle?: {}".format(battles)) decision, payoff = pick_safest(all_scores, remove_guaranteed=True) bot_choice = decision[0] logger.debug("Safest: {}, {}".format(bot_choice, payoff)) logger.debug("Depth: {}".format(search_depth)) return bot_choice
def find_best_move(self): battles = self.prepare_battles() if len(battles) > 7: logger.debug( "Not enough is known about the opponent's active pokemon - falling back to safest decision making" ) battles = self.prepare_battles(join_moves_together=True) decision = pick_safest_move_from_battles(battles) else: list_of_payoffs = list() for b in battles: state = b.create_state() mutator = StateMutator(state) logger.debug("Attempting to find best move from: {}".format( mutator.state)) user_options, opponent_options = b.get_all_options() scores = get_payoff_matrix(mutator, user_options, opponent_options, prune=False) list_of_payoffs.append(scores) decision = pick_move_in_equilibrium_from_multiple_score_lookups( list_of_payoffs) return format_decision(self, decision)
def setUp(self): self.state = State( Side( Pokemon.from_state_pokemon_dict(StatePokemon("pikachu", 100).to_dict()), { "rattata": Pokemon.from_state_pokemon_dict(StatePokemon("rattata", 100).to_dict()), "charmander": Pokemon.from_state_pokemon_dict(StatePokemon("charmander", 100).to_dict()), "squirtle": Pokemon.from_state_pokemon_dict(StatePokemon("squirtle", 100).to_dict()), "bulbasaur": Pokemon.from_state_pokemon_dict(StatePokemon("bulbasaur", 100).to_dict()), "pidgey": Pokemon.from_state_pokemon_dict(StatePokemon("pidgey", 100).to_dict()) }, (0, 0), defaultdict(lambda: 0) ), Side( Pokemon.from_state_pokemon_dict(StatePokemon("pikachu", 100).to_dict()), { "rattata": Pokemon.from_state_pokemon_dict(StatePokemon("rattata", 100).to_dict()), "charmander": Pokemon.from_state_pokemon_dict(StatePokemon("charmander", 100).to_dict()), "squirtle": Pokemon.from_state_pokemon_dict(StatePokemon("squirtle", 100).to_dict()), "bulbasaur": Pokemon.from_state_pokemon_dict(StatePokemon("bulbasaur", 100).to_dict()), "pidgey": Pokemon.from_state_pokemon_dict(StatePokemon("pidgey", 100).to_dict()) }, (0, 0), defaultdict(lambda: 0) ), None, None, False ) self.mutator = StateMutator(self.state)
def pick_bfs_move(self, battle): state = battle.create_state() mutator = StateMutator(state) user_options, opponent_options = battle.get_all_options() logger.debug("Attempting to find best move from: {}".format(mutator.state)) is_winning = self.is_winning(state) # Builds a tree to search for opponent's moves, assume opponent picks safest scores = get_payoff_matrix(mutator, user_options, opponent_options, is_winning, depth=3, prune=True) logger.debug(f"\nScores: {scores}") move_string = "Aggresive" if is_winning: decision, payoff = self.pick_safest(scores) move_string = "Safest" else: decision, payoff = self.pick_aggresive(scores) bot_choice = decision[0] logger.debug(f"{move_string}: {bot_choice}, {payoff}") return bot_choice
def calculate_value(state, transition, depth): """ Takes in the current state, a specific transition (pair of our move and opponent move), and estimates the value associated with applying this transition at current search depth, taking into account the probability of this transition occuring """ state_instructions = get_all_state_instructions(StateMutator(state), transition[0], transition[1]) total_value = 0 for instruction in state_instructions: mutator = StateMutator(copy.deepcopy(state)) mutator.apply(instruction.instructions) value = expectiminimax(mutator.state, depth) total_value += value * instruction.percentage return total_value
def generate_next_child(self, chosen_transition): """ Generates a child node by choosing the most likely mutation instructions (instructions are potential results of a transition) of the given transition. Params: - chosen_transition: the pair of (our move : opponent move) to apply """ mutator = StateMutator(copy.deepcopy(self.state)) state_instructions = get_all_state_instructions( mutator, chosen_transition[0], chosen_transition[1]) choice = max(state_instructions, key=lambda i: i.percentage).instructions mutator.apply(choice) return MonteCarloTree(mutator.state)
def pick_safest_move_from_battles(battles): all_scores = dict() for i, b in enumerate(battles): state = b.create_state() mutator = StateMutator(state) user_options, opponent_options = b.get_all_options() logger.debug("Searching through the state: {}".format(mutator.state)) scores = get_payoff_matrix(mutator, user_options, opponent_options, 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, remove_guaranteed=True) bot_choice = decision[0] logger.debug("Safest: {}, {}".format(bot_choice, payoff)) return bot_choice
def pick_safest_move_from_battles(battles): all_scores = dict() for i, b in enumerate(battles): state = b.create_state() mutator = StateMutator(state) user_options, opponent_options = b.get_all_options() logger.debug("Attempting to find best move from: {}".format( mutator.state)) scores = get_payoff_matrix(mutator, user_options, opponent_options, 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 make_a_choice(battles): # Battles is a list of possible states the opponent's team might be in # The list is based on guesses done by the framework, one battle per guess # There's always at least one battle # The probability of each battle/state can be assumed to be equal # Below a battle is transformed into a StateMutator # We can apply choices to the StateMutator in order to create new states a_battle = battles[0] a_state = a_battle.to_object() a_mutator = StateMutator(a_state) # Fetch possible options for you and your opponent by calling get_all_options on a StateMutator # user_options includes using one of your four moves or switching to a remaining Pokémon # opponent_options is based on assumptions about what the opponent might be able to do user_options, opponent_options = get_all_options(a_mutator) # We can evaluate a state using the pre-defined evaluate method # The method is already fine-tuned in order to save us some time # But you can play around with it by checking out evaluate.py a_score = evaluate(a_mutator.state) # By picking an option and assuming the opponent's option, we can generate a list of low-level state instructions # Because certain moves can lead to several different states (because of randomness), the list of possible instructions might be long # Applying one of the possible instructions to the StateMutator mutates the state # After evaluating the score of the new state, we might want to reverse the changes and try something else all_possible_instructions = get_all_state_instructions( a_mutator, user_options[0], opponent_options[0]) a_mutator.apply(all_possible_instructions[0].instructions) a_mutator.reverse(all_possible_instructions[0].instructions) # Logging data might be handy logger.debug("User's options: {}".format(user_options)) logger.debug("Opponent's options: {}".format(opponent_options)) logger.debug("Current state's score: {}".format(a_score)) # For now, let's just return a random choice a_random_choice = random.choice(user_options) logger.debug("Move chosen: {}".format(a_random_choice)) return a_random_choice
def random_playout(self, initial_position, depth): """ Random playout of from this node. If max depth is reached then the evaluation function of the state is compared against initial_position. If the evaluation of the state is better than the initial position, it is counted as win, since the bot position was improved. """ self.total += 1 mutator = StateMutator(copy.deepcopy(self.state)) while True: if depth == MAX_DEPTH: if evaluate(mutator.state) >= initial_position: self.wins += 1 return True else: return False winner = mutator.state.battle_is_finished() if winner: if winner == 1: self.wins += 1 return True else: return False transition = random.choice(get_transitions(mutator.state)) state_instructions = get_all_state_instructions( mutator, transition[0], transition[1]) possible_instrucitons = [ i.instructions for i in state_instructions ] weights = [i.percentage for i in state_instructions] choice = random.choices(possible_instrucitons, weights=weights)[0] mutator.apply(choice) depth += 1
'id': 'dragondance', 'disabled': False, 'current_pp': 32 }, { 'id': 'earthquake', 'disabled': False, 'current_pp': 16 }, { 'id': 'bounce', 'disabled': False, 'current_pp': 8 }], 'types': ['water', 'flying'], 'canMegaEvo': False } }, 'side_conditions': { 'toxic_count': 0 }, 'trapped': False }, 'weather': None, 'field': None, 'trickroom': False, 'forceSwitch': False, 'wait': False }) mutator = StateMutator(state) state_instructions = get_payoff_matrix(mutator, depth=3)
def setUp(self): self.state = State( Side( Pokemon.from_state_pokemon_dict( StatePokemon("raichu", 73).to_dict()), { "xatu": Pokemon.from_state_pokemon_dict( StatePokemon("xatu", 81).to_dict()), "starmie": Pokemon.from_state_pokemon_dict( StatePokemon("starmie", 81).to_dict()), "gyarados": Pokemon.from_state_pokemon_dict( StatePokemon("gyarados", 81).to_dict()), "dragonite": Pokemon.from_state_pokemon_dict( StatePokemon("dragonite", 81).to_dict()), "hitmonlee": Pokemon.from_state_pokemon_dict( StatePokemon("hitmonlee", 81).to_dict()), }, defaultdict(lambda: 0), False), Side( Pokemon.from_state_pokemon_dict( StatePokemon("aromatisse", 81).to_dict()), { "yveltal": Pokemon.from_state_pokemon_dict( StatePokemon("yveltal", 73).to_dict()), "slurpuff": Pokemon.from_state_pokemon_dict( StatePokemon("slurpuff", 73).to_dict()), "victini": Pokemon.from_state_pokemon_dict( StatePokemon("victini", 73).to_dict()), "toxapex": Pokemon.from_state_pokemon_dict( StatePokemon("toxapex", 73).to_dict()), "bronzong": Pokemon.from_state_pokemon_dict( StatePokemon("bronzong", 73).to_dict()), }, defaultdict(lambda: 0), False), None, None, False, False, False) self.mutator = StateMutator(self.state) self.state.self.active.moves = [ { constants.ID: 'tackle', constants.DISABLED: False }, { constants.ID: 'charm', constants.DISABLED: False }, { constants.ID: 'growl', constants.DISABLED: False }, { constants.ID: 'stringshot', constants.DISABLED: False }, ] self.state.opponent.active.moves = [ { constants.ID: 'tackle', constants.DISABLED: False }, { constants.ID: 'charm', constants.DISABLED: False }, { constants.ID: 'growl', constants.DISABLED: False }, { constants.ID: 'stringshot', constants.DISABLED: False }, ]
def pick_move_from_battles(self, battles): # Only work on current battle # In practice the bot can play several at once, for now we simplified to one battle. battle = battles[0] root = Node("Root") state = battle.create_state() mutator = StateMutator(state) user_options, opponent_options = battle.get_all_options() logger.debug("Attempting to find best move from: {}".format(mutator.state)) #get the scores from the "safest" algorithm provided by the starter code scores = get_payoff_matrix(mutator, user_options, opponent_options, depth=2, prune=False) # Create tree using payoff matrix from "Safest" algorithm checked_moves = {} for (myMove, opponentMove), score in scores.items(): #checked moves keeps track of which nodes have been added to the tree, so we dont duplicate. if myMove not in checked_moves: child = Node(myMove, root) checked_moves[myMove] = child grandchild = Node(opponentMove, checked_moves[myMove]) Node(score, grandchild) # a library function that prints our tree for readability. for pre, _, node in RenderTree(root): print("%s%s" % (pre, node.name)) myTotalHP = 0.0 # max of 600, 100 points for full hp oppTotalHP = 0.0 # calculate my total hp my_pokes = state.self # get active pokemon hp if it isn't dead if my_pokes.active.maxhp != 0: myTotalHP += my_pokes.active.hp / my_pokes.active.maxhp # get reserve pokmeons hps for p in my_pokes.reserve.values(): if p.maxhp !=0: myTotalHP += p.hp / p.maxhp myTotalHP *= 100 # calculate opp total hp opp_pokes = state.opponent # get active pokemon hp if opp_pokes.active.maxhp != 0: oppTotalHP += opp_pokes.active.hp / opp_pokes.active.maxhp # get reserve pokmeons hps for p in opp_pokes.reserve.values(): if p.maxhp !=0: oppTotalHP += p.hp / p.maxhp #accounts for the pokemon of opponent that have not been revealed unseenPoke = 5-len(opp_pokes.reserve) oppTotalHP += unseenPoke oppTotalHP *=100 possibleStatuses = { "psn" : .06, "frz" : .25, "tox" : .19, "par" : .16, "slp" : .16, "brn" : .20, None: 0, } statBuffs = { 6 : .25, 5 : .235, 4 : .22, 3 : .19, 2 : .15, 1 : .08, 0 : 0, -1 : -.08, -2 : -.15, -3 : -.19, -4 : -.22, -5 : -.235, -6 : -.25, } # check how many status conditions we have myStatuses = 0 # active pokemon myStatuses += possibleStatuses[my_pokes.active.status] # reserve pokemon for p in my_pokes.reserve.values(): myStatuses += possibleStatuses[p.status] # check how many status conditions opponent has opponentStatuses = 0 # active pokemon opponentStatuses += possibleStatuses[opp_pokes.active.status] # reserve pokemon for p in opp_pokes.reserve.values(): opponentStatuses += possibleStatuses[p.status] status_aggression_multiplier = 1 status_aggression_modifier = (opponentStatuses - myStatuses) * status_aggression_multiplier print(f"Status modifier:{status_aggression_modifier}") # Stat Buffs # check how many stat boosts/nerfs we have myBuffs = 0 # active pokemon myBuffs += statBuffs[my_pokes.active.accuracy_boost] myBuffs += statBuffs[my_pokes.active.attack_boost] myBuffs += statBuffs[my_pokes.active.defense_boost] myBuffs += statBuffs[my_pokes.active.evasion_boost] myBuffs += statBuffs[my_pokes.active.special_attack_boost] myBuffs += statBuffs[my_pokes.active.special_defense_boost] myBuffs += statBuffs[my_pokes.active.speed_boost] # check how many stat boosts/nerfs opponent has oppBuffs = 0 # active pokemon oppBuffs += statBuffs[opp_pokes.active.accuracy_boost] oppBuffs += statBuffs[opp_pokes.active.attack_boost] oppBuffs += statBuffs[opp_pokes.active.defense_boost] oppBuffs += statBuffs[opp_pokes.active.evasion_boost] oppBuffs += statBuffs[opp_pokes.active.special_attack_boost] oppBuffs += statBuffs[opp_pokes.active.special_defense_boost] oppBuffs += statBuffs[opp_pokes.active.speed_boost] buff_aggression_multiplier = 1 buff_aggression_modifier = (myBuffs - oppBuffs) * buff_aggression_multiplier print(f"Buff modifier:{buff_aggression_modifier}") safety = (3 + status_aggression_modifier + buff_aggression_modifier) * myTotalHP/oppTotalHP if safety < 0: print("WARNING: safety constant is less than 0, changing to 0.1") safety = 0.1 print(f"bot will play with safety constant of {safety}") bot_choice = self.aggressive_pick(root,safety) print(f"choice: {bot_choice}") print(f"the safest pick was {self.safest_pick(root)}") print("Choice: {}".format(bot_choice)) return bot_choice