Esempio n. 1
0
 def _turn_start(self, player: Player):
     """
     Called when the provided player's turn starts.
     :param player: The player whose turn it is.
     """
     if not player.is_ai():
         turn_complete = False
         while not turn_complete:
             pokemon = player.get_party().get_starting()
             move = prompt_multi(
                 'What will ' + player.get_name() + '\'s ' +
                 pokemon.get_name() + ' do?', "Attack", "Bag",
                 "Switch Pokemon", "Check Opponent's Pokemon", "Forfeit")[0]
             if move == 0:
                 turn_complete = self._turn_attack(player)
             elif move == 1:
                 turn_complete = self._turn_bag(player)
             elif move == 2:
                 turn_complete = self._turn_switch_pokemon(player)
             elif move == 3:
                 turn_complete = False
                 self._turn_check_pokemon(player)
             else:
                 clear_battle_screen()
                 self._alert(player.get_name() + ' forfeits...', player)
                 exit(0)
     else:
         # AI player should make move decision based on provided objects.
         self._turn_ai(player)
Esempio n. 2
0
    def take_turn(self, player: Player, other_player: Player,
                  attack: Callable[[Move], None], use_item: Callable[[Item],
                                                                     None],
                  switch_pokemon_at_idx: Callable[[int], None]) -> None:
        pokemon = player.get_party().get_starting()

        num_available_moves = sum([
            int(move.is_available())
            for move in pokemon.get_move_bank().get_as_list()
        ])
        num_available_pokemon = sum([
            int(not pokemon.is_fainted())
            for pokemon in player.get_party().get_as_list()
        ]) - 1

        if num_available_moves + num_available_pokemon > 0 and randint(
                1, num_available_moves +
                num_available_pokemon) <= num_available_moves:
            # Perform a move
            move_list = deepcopy(pokemon.get_move_bank().get_as_list())
            shuffle(move_list)
            for move in move_list:
                if move.is_available():
                    attack(move)
                    break
        else:
            # Perform a switch
            idx = self.force_switch_pokemon(player.get_party())
            switch_pokemon_at_idx(idx)
Esempio n. 3
0
def outcome_func_v1(player: Player, opponent: Player) -> float:
    """
    Calculates the outcome value on [0, 1] for use in Monte Carlo Tree Search.
    :param player: The current player.
    :param opponent: The opposing player.
    :return: An outcome value between 0 and 1. 1 denotes a strong win, 0 denotes a bad loss.
    """
    # Calculate HP differences and fainted pokemon
    player_total_hp, opp_total_hp = 0, 0
    hp_taken, hp_dealt = 0, 0
    player_fainted_count, opp_fainted_count = 0, 0
    for pokemon in player.get_party().get_as_list():
        player_total_hp += pokemon.get_base_hp()
        hp_taken += pokemon.get_base_hp() - pokemon.get_hp()
        player_fainted_count += int(pokemon.get_hp() == 0)
    for pokemon in opponent.get_party().get_as_list():
        opp_total_hp += pokemon.get_base_hp()
        hp_dealt += pokemon.get_base_hp() - pokemon.get_hp()
        opp_fainted_count += int(pokemon.get_hp() == 0)

    outcome = 0.2 if player_fainted_count == len(
        player.get_party().get_as_list()) else 0.8

    # Outcome = %hp_dealt - %hp_taken + %pokemon_killed - (%pokemon_fainted)^2
    hp_perc_diff = hp_dealt / opp_total_hp - hp_taken / player_total_hp
    pokemon_fainted_perc_diff = opp_fainted_count / len(
        opponent.get_party().get_as_list()) - (
            player_fainted_count / len(player.get_party().get_as_list()))**2
    scalar = (hp_perc_diff + pokemon_fainted_perc_diff) / 10

    return outcome + scalar
Esempio n. 4
0
        def create_node(node_player: Player, node_other_player: Player, action_type: MonteCarloActionType, index: int) -> MonteCarloNode:
            """
            Creates an attack or switch node.
            :param node_player: The current player object for the node.
            :param action_type: Either MonteCarloActionType.ATTACK or MonteCarloActionType.SWITCH.
            :param index: The index of the attack or Pokemon to switch to.
            """
            # The currently battling pokemon
            pokemon = node_player.get_party().get_starting()

            # Create action descriptor
            if action_type == MonteCarloActionType.ATTACK:
                attack = pokemon.get_move_bank().get_move(index)
                action_descriptor = index
                description = "%s used %s." % (pokemon.get_name(), attack.get_name())
            else:
                switch_pokemon = node_player.get_party().get_at_index(index)
                action_descriptor = switch_pokemon.get_id()
                description = "%s switched out with %s." % (pokemon.get_name(), switch_pokemon.get_name())

            # Create the "turn" to be taken when this node is visited
            if action_type == MonteCarloActionType.ATTACK:
                def take_turn(_: Player, __: Player, do_move: Callable[[Move], None], ___: Callable[[Item], None],
                              ____: Callable[[int], None]):
                    do_move(attack)
            else:
                def take_turn(_: Player, __: Player, ___: Callable[[Move], None], ____: Callable[[Item], None],
                              switch_pokemon_at_idx: Callable[[int], None]):
                    switch_pokemon_at_idx(index)

            model = RandomModel()
            model.take_turn = take_turn

            # Return the move node
            return MonteCarloNode(node_player.copy(), node_other_player.copy(), node_player.get_id(), action_type, action_descriptor, model, 0, description)
Esempio n. 5
0
 def _turn_perform_attacks(self, player_a: Player, player_b: Player):
     """
     Dequeues and performs the attacks in the attack queue.
     :param player_a: The first player to perform attacks for.
     :param player_b: The second player to perform attacks for.
     """
     for player, pokemon, move in self.attack_queue:
         if player.get_id() is player_a.get_id():
             if self._perform_attack(move, player_a, player_b, pokemon):
                 break
         elif player.get_id() is player_b.get_id():
             if self._perform_attack(move, player_b, player_a, pokemon):
                 break
     self.attack_queue = []
Esempio n. 6
0
    def _make_input_vector(player: Player, other_player: Player) -> np.ndarray:
        """
        Creates an input vector for the dense net.
        :param player: The focused player.
        :param other_player: The other player.
        :return: A 60-len numpy array with [HP ratio, Move 1 PP ratio, ... Move 4 PP ratio] at each row for max 12 Pokemon,
        flattened.
        """
        # Store each player's Pokemon lists
        player_pokemon = player.get_party().get_sorted_list()
        other_player_pokemon = other_player.get_party().get_sorted_list()

        mat = []

        def fill_rows(pokemon_list: List[Pokemon]):
            """
            Fills a list of rows with Pokemon stats.
            :param pokemon_list: A list of Pokemon.
            """
            for pkmn in pokemon_list:
                row = []

                # Add HP component
                hp_comp = pkmn.get_hp() / pkmn.get_base_hp()
                row.append(hp_comp)

                # Add move components
                move_list = pkmn.get_move_bank().get_as_list()
                for move in move_list:
                    move_comp = move.get_pp() / move.get_base_pp()
                    row.append(move_comp)
                # Fill remaining moves with 0
                for _ in range(POKEMON_MOVE_LIMIT - len(move_list)):
                    row.append(EPSILON)

                # Add row to matrix
                mat.append(row)

        def fill_empty(num: int):
            for _ in range(num):
                mat.append([EPSILON, EPSILON, EPSILON, EPSILON, EPSILON])

        # Fill rows
        fill_rows(player_pokemon)
        fill_empty(POKEMON_PARTY_LIMIT - len(player_pokemon))
        fill_rows(other_player_pokemon)
        fill_empty(POKEMON_PARTY_LIMIT - len(other_player_pokemon))

        return np.array(mat).flatten()
Esempio n. 7
0
    def _turn_bag(self, player: Player, ai_item: Item = None):
        """
        Called when the player chooses to use a bag item.
        :param player: The player who is opening the bag.
        :param ai_item: The item the AI is using.
        :return: True if the player uses a bag item, False if the player chooses to go back.
        """
        pokemon = player.get_party().get_starting()
        if not player.is_ai():
            item = prompt_multi(
                'Use which item?', 'None (Go back)', *[
                    i.get_name() + " (" + i.get_description() + ")"
                    for i in player.get_bag().get_as_list()
                ])[0]
        elif ai_item is not None:
            item = ai_item
        else:
            item = 0

        if item == 0:
            return False

        item_idx = item - 1
        item = player.get_bag().get_as_list()[item_idx]

        # Use the item and remove it from the player's bag
        self._alert("%s used a %s." % (player.get_name(), item.get_name()),
                    self.player1, self.player2)
        item.use(player, pokemon)
        player.get_bag().get_as_list().remove(item_idx)

        return True
Esempio n. 8
0
def print_battle_screen(player: Player,
                        other_player: Player,
                        screen_width=None):
    """
    Prints a battle screen.
    :param player: The current player who the battle screen is focused on.
    :param other_player: The other player.
    :param screen_width: The width of the terminal.
    """
    screen_width = screen_width if screen_width is not None else get_terminal_dimensions(
    )[0]

    def get_statuses(pkmn: Pokemon):
        statuses = []
        if pkmn.get_status() is not None:
            statuses.append(pkmn.get_status().name)
        if pkmn.get_other_status() is not None:
            statuses.append(pkmn.get_other_status().name)
        return ', '.join(statuses)

    txt_player_name = lambda plyr, aln: align(plyr.get_name().upper(), aln,
                                              screen_width)
    txt_pokemon_top = lambda pkmn: split_align(
        "%s (%s)" % (pkmn.get_name(), pkmn.get_type().name), 'Lv' + str(
            pkmn.get_level()), screen_width)
    txt_pokemon_bottom = lambda pkmn: split_align(
        str(pkmn.get_hp()) + '/' + str(pkmn.get_base_hp()) + ' HP',
        get_statuses(pkmn), screen_width)

    pokemon = player.get_party().get_starting()
    other_pokemon = other_player.get_party().get_starting()

    print("\n", end="\r", flush=True)
    print(repeat('=', screen_width))
    print(txt_player_name(other_player, Align.RIGHT))
    print(txt_pokemon_top(other_pokemon))
    print(txt_pokemon_bottom(other_pokemon))

    print(repeat('-', screen_width))

    print(txt_player_name(player, Align.LEFT))
    print(txt_pokemon_top(pokemon))
    print(txt_pokemon_bottom(pokemon))

    print(repeat('=', screen_width))
Esempio n. 9
0
    def take_turn(self, player: Player, other_player: Player,
                  attack: Callable[[Move], None], use_item: Callable[[Item],
                                                                     None],
                  switch_pokemon_at_idx: Callable[[int], None]) -> None:
        pokemon = player.get_party().get_starting()
        enemy = other_player.get_party().get_starting()

        damage_list = []

        for move in pokemon.get_move_bank().get_as_list():
            if move.is_available():
                damage = calculate_damage_deterministic(move, pokemon,
                                                        enemy)[0]
                damage_list.append((move, damage))

        damage_list.sort(reverse=True, key=lambda x: x[1])

        attack(damage_list[0][0])
Esempio n. 10
0
    def _turn_attack(self, player: Player, ai_move: Move = None):
        """
        Called when the player chooses to attack.
        :param player: The player who is attacking.
        :param ai_move: The move the AI is playing.
        :return: True if the player selected an attack, False if the player chooses to go back.
        """
        pokemon = player.get_party().get_starting()
        other_player = self.player2 if player.get_id(
        ) == self._PLAYER_1_ID else self.player1
        on_pokemon = other_player.get_party().get_starting()
        move = None
        if not player.is_ai():
            while move is None or not move.is_available():
                move_idx = prompt_multi(
                    'Seslect a move.', 'None (Go back)', *[
                        m.get_name() + ': ' +
                        PokemonType(m.get_type()).name.lower().capitalize() +
                        (" (" + is_effective(
                            m.get_type(), on_pokemon.get_type()).name.lower() +
                         " damage) " if self.use_hints else "") + ', ' +
                        str(m.get_pp()) + '/' + str(m.get_base_pp()) + ' PP'
                        for m in pokemon.get_move_bank().get_as_list()
                    ])[0]
                if move_idx == 0:
                    return False
                move = pokemon.get_move_bank().get_move(move_idx - 1)
                if not move.is_available():
                    self._alert("There's no PP left for this move!", player)
        elif ai_move is not None:
            # ai_move contains the same move on another copy of the player's Pokemon, so we have to retrieve the
            # same move in the current in-game Pokemon
            ai_move_name = ai_move.get_name()
            for temp_move in player.get_party().get_starting().get_move_bank(
            ).get_as_list():
                if temp_move.get_name() == ai_move_name:
                    move = temp_move
                    break
        else:
            return False

        self._enqueue_attack(player, move)
        return True
Esempio n. 11
0
 def take_turn(self, player: Player, other_player: Player, attack: Callable[[Move], None], use_item: Callable[[Item], None], switch_pokemon_at_idx: Callable[[int], None]) -> None:
     with ThreadPoolExecutor() as executor:
         self._predictor.predict_move(player, other_player)
         make_tree_thread = executor.submit(make_tree, player, other_player, NUM_SIMULATIONS, predictor=self._predictor, use_damage_model=self._use_damage_model, verbose=False)
         if self._verbose:
             print("%s is formulating a move..." % player.get_name())
         tree = make_tree_thread.result()
         model = tree.get_next_action()
         if self._verbose:
             print("Done")
         model.take_turn(player, other_player, attack, use_item, switch_pokemon_at_idx)
Esempio n. 12
0
    def _turn_end(self, player: Player):
        """
        Called when a turn is about to end to inflict damage from statuses and other things.
        :param player: The player to perform the calculations on.
        :return: True if the player wins, False otherwise.
        """
        # Get the Pokemon that's currently out
        pokemon = player.get_party().get_starting()

        def self_inflict(base_damage: int):
            # Performs damage calculation for self-inflicted attacks
            damage = int(pokemon.get_stats().get_attack() / base_damage)
            defense = int(pokemon.get_stats().get_defense() / base_damage * 1 /
                          10)
            total_damage = min(max(1, (damage - defense)), pokemon.get_hp())
            pokemon.take_damage(total_damage)
            if pokemon.is_fainted():
                self._alert(pokemon.get_name() + ' fainted!', player)
                return not self._turn_switch_pokemon(player, False)
            return False

        if pokemon.get_other_status() in [
                Status.POISON, Status.BAD_POISON, Status.BURN
        ]:
            self._alert(
                pokemon.get_name() + ' is ' +
                status_names[pokemon.get_other_status()] + '.', player)

            # Increment the number of turns with the other status
            pokemon.inc_other_status_turn()

            if pokemon.get_other_status() is Status.POISON:
                damage = int(pokemon.get_base_hp() / 16)
                self._alert(
                    pokemon.get_name() + ' took ' + str(damage) +
                    ' damage from poison.', player)
                return self_inflict(damage)
            if pokemon.get_other_status() is Status.BAD_POISON:
                damage = int(pokemon.get_base_hp() *
                             pokemon.get_other_status_turns() / 16)
                self._alert(
                    pokemon.get_name() + ' took ' + str(damage) +
                    ' damage from poison.', player)
                return self_inflict(damage)
            elif pokemon.get_other_status() is Status.BURN:
                damage = int(pokemon.get_base_hp() / 8)
                self._alert(
                    pokemon.get_name() + ' took ' + str(damage) +
                    ' damage from its burn.', player)
                return self_inflict(damage)

        return False
Esempio n. 13
0
    def _turn_ai(self, player: Player):
        """
        Let's the player's AI model perform the turn.
        :param player: The player the AI plays for.
        :return: True, always, as if the model knows what it's doing.
        """
        assert player.get_model() is not None

        # Get the other player
        other_player = self.player2 if player.get_id(
        ) == self._PLAYER_1_ID else self.player1

        # Create lambda functions to be used by the model
        attack_func = lambda move: self._turn_attack(player, move)
        use_item_func = lambda item: self._turn_bag(player, item)
        switch_pokemon_at_idx_func = lambda idx: self._turn_switch_pokemon(
            player, False, idx)

        # Take the turn using the model
        player.get_model().take_turn(player, other_player, attack_func,
                                     use_item_func, switch_pokemon_at_idx_func)

        return True
Esempio n. 14
0
 def _enqueue_attack(self, player: Player, move: Move):
     """
     Enqueues an attack to be done by the player, depending on the player's Pokemon's speed.
     :param player: The player whose starter Pokemon will be attacking.
     :param move: The move to attack with.
     """
     pokemon = player.get_party().get_starting()
     attack_triple = (player, pokemon, move)
     if len(self.attack_queue) == 0:
         self.attack_queue.append(attack_triple)
     else:
         op_speed = self.attack_queue[0][1].get_stats().get_speed()
         self_speed = pokemon.get_stats().get_speed()
         if op_speed > self_speed:
             self.attack_queue.append(attack_triple)
         elif op_speed < self_speed:
             self.attack_queue.insert(0, attack_triple)
         else:
             chance(0.5, lambda: self.attack_queue.append(attack_triple),
                    lambda: self.attack_queue.insert(0, attack_triple))
Esempio n. 15
0
    def _battle_start(self, player1: Player, player2: Player):
        """
        Called when the battle is about to begin.
        :param player1: The first player.
        :param player2: The second player.
        """
        self.started = True

        # Assign faux IDs for keeping track
        player1._id = self._PLAYER_1_ID
        player2._id = self._PLAYER_2_ID

        # Reveal starting Pokemon
        player1.get_party().get_starting().reveal()
        player2.get_party().get_starting().reveal()

        if self.verbose >= 1:
            # Clear the terminal window
            #clear(get_terminal_dimensions()[1])
            print("")
Esempio n. 16
0
    def _perform_attack(self, move: Move, player: Player, on_player: Player,
                        pokemon: Pokemon):
        """
        Performs a move by the starting Pokemon of player against the starting pokemon of on_player.
        :param move: The move player's Pokemon is performing.
        :param player: The player whose Pokemon is attacking.
        :param on_player: The player whose Pokemon is defending.
        :param pokemon: The attacking Pokemon.
        :return: Returns True if player wins, False otherwise.
        """

        if pokemon.is_fainted():
            return False

        on_pokemon = on_player.get_party().get_starting()

        def confusion():
            base_damage = 40
            damage = int(pokemon.get_stats().get_attack() / base_damage)
            defense = int(pokemon.get_stats().get_defense() / base_damage * 1 /
                          10)
            total_damage = max(1, damage - defense)
            pokemon.take_damage(total_damage)
            self._alert(pokemon.get_name() + ' hurt itself in its confusion.',
                        player, on_player)
            if pokemon.is_fainted():
                self._alert(pokemon.get_name() + ' fainted!', player)
                return not self._turn_switch_pokemon(player, False)
            return False

        def paralysis():
            self._alert(pokemon.get_name() + ' is unable to move.', player,
                        on_player)

        def infatuation():
            self._alert(
                pokemon.get_name() + ' is infatuated and is unable to move.',
                player, on_player)

        def freeze():
            self._alert(pokemon.get_name() + ' is frozen solid.', player,
                        on_player)

        def sleep():
            self._alert(pokemon.get_name() + ' is fast asleep.', player,
                        on_player)

        def try_attack():
            # Decrease the PP on the move
            move.dec_pp()
            move.reveal()

            # Checks to see if the attack hit
            change_of_hit = pokemon.get_stats().get_accuracy(
            ) / 100 * on_pokemon.get_stats().get_evasiveness() / 100
            did_hit = chance(change_of_hit, True, False)

            if not did_hit:
                self._alert(pokemon.get_name() + '\'s attack missed.', player,
                            on_player)
                return False

            self._alert(
                player.get_name() + "'s " + pokemon.get_name() + ' used ' +
                move.get_name() + '!', player, on_player)

            if move.is_damaging():
                # Calculate damage
                damage, effectiveness, critical = calculate_damage(
                    move, pokemon, on_pokemon)

                # Describe the effectiveness
                if critical == Criticality.CRITICAL and effectiveness != Effectiveness.NO_EFFECT:
                    self._alert('A critical hit!', player, on_player)
                if effectiveness == Effectiveness.NO_EFFECT:
                    self._alert(
                        'It has no effect on ' + on_pokemon.get_name() + '.',
                        player, on_player)
                if effectiveness == Effectiveness.SUPER_EFFECTIVE:
                    self._alert('It\'s super effective!', player, on_player)
                if effectiveness == Effectiveness.NOT_EFFECTIVE:
                    self._alert('It\'s not very effective...', player,
                                on_player)

                self._alert(
                    on_pokemon.get_name() + ' took ' + str(damage) +
                    ' damage.', player, on_player)

                # Lower the opposing Pokemon's HP
                on_pokemon._hp = max(0, on_pokemon.get_hp() - damage)

            # If the move inflicts a status, perform the status effect
            if move.get_status_inflict():
                if move.get_status_inflict() in [
                        Status.POISON, Status.BAD_POISON, Status.BURN
                ]:
                    # Unlike status turns, other status turns increase in length because they don't end
                    on_pokemon.set_other_status(move.get_status_inflict())
                else:
                    on_pokemon.set_status(move.get_status_inflict())
                self._alert(
                    on_pokemon.get_name() + ' was ' +
                    status_names[move.get_status_inflict()], player, on_player)

            # Heal the pokemon
            if move.get_base_heal() > 0:
                on_pokemon.heal(move.get_base_heal())
                self._alert(
                    pokemon.get_name() + ' gained ' +
                    str(move.get_base_heal()) + ' HP.', player)

            # Check if the Pokemon fainted
            if on_pokemon.is_fainted():
                self._alert(on_pokemon.get_name() + ' fainted!', player,
                            on_player)
                return not self._turn_switch_pokemon(on_player, False)

            return False

        if pokemon.get_status() not in [
                None, Status.POISON, Status.BAD_POISON, Status.BURN
        ]:
            # If the Pokemon is inflicted by a status effect that impacts the chance of landing the attack,
            # perform the calculations.
            status = pokemon.get_status()
            pokemon._status_turns = max(0, pokemon.get_status_turns() - 1)
            if pokemon.get_status_turns() == 0:
                pokemon._status = None
            self._alert(
                pokemon.get_name() + ' is ' + status_names[status] + '.',
                player, on_player)
            if status is Status.CONFUSION:
                chance(1 / 3, lambda: confusion(), try_attack)
            elif status is Status.PARALYSIS:
                chance(0.25, lambda: paralysis(), try_attack)
            elif status is Status.INFATUATION:
                chance(0.5, lambda: infatuation(), try_attack)
            elif status is Status.FREEZE:
                freeze()
            elif status is Status.SLEEP:
                sleep()
        elif pokemon.get_status() is None:
            # No status effect, attempt to attack
            return try_attack()

        return False
Esempio n. 17
0
    def predict_move(
        self, player: Player, other_player: Player
    ) -> Tuple[RandomModel, MonteCarloActionType, int, List[float],
               List[float]]:
        """
        Predict the move the player should make.
        :param player: The player.
        :param other_player: The opposing player.
        :return: A tuple containing the <move model, move type, move index, move probabilities, switch-out probabilities>.
        """
        if not self._is_trained:
            return RandomModel(
            ), MonteCarloActionType.ATTACK, 0, POKEMON_MOVE_LIMIT * [
                round(1 / POKEMON_MOVE_LIMIT)
            ], POKEMON_PARTY_LIMIT * [round(1 / POKEMON_PARTY_LIMIT)]

        input_matrix = self._make_input_vector(player, other_player)
        output = self._model.predict([input_matrix])[0]

        # Get the index of the 4 current Pokemon moves from the output
        current_pokemon_id = player.get_party().get_starting().get_id()
        current_pokemon_idx = -1
        for idx, pokemon in enumerate(player.get_party().get_sorted_list()):
            if pokemon.get_id() == current_pokemon_id:
                current_pokemon_idx = idx
                break

        # Create probabilities for picking moves and switches
        move_probs = []
        if current_pokemon_idx >= 0:
            start_idx = POKEMON_PARTY_LIMIT + current_pokemon_idx * POKEMON_MOVE_LIMIT
            move_probs = output[start_idx:start_idx + POKEMON_MOVE_LIMIT]
        switch_probs = output[:POKEMON_PARTY_LIMIT]

        # Create the model
        model = RandomModel()

        # Get probability of attacking and switching
        all_moves = list(np.concatenate((move_probs, switch_probs), axis=0))
        all_moves_probs = to_probs(all_moves)
        prob_attack = sum(all_moves_probs[:len(move_probs)])
        move_type = chance(prob_attack, MonteCarloActionType.ATTACK,
                           MonteCarloActionType.SWITCH)
        move_idx = 0

        # Randomly select a move given the move weights
        if move_type == MonteCarloActionType.ATTACK:
            # Get a random move
            move_idx = chances(
                move_probs,
                list(
                    range(
                        len(player.get_party().get_starting().get_move_bank().
                            get_as_list()))))
            attack = player.get_party().get_starting().get_move_bank(
            ).get_move(move_idx)

            # Create a turn function
            def take_turn(_: Player, __: Player, do_move: Callable[[Move],
                                                                   None],
                          ___: Callable[[Item], None], ____: Callable[[int],
                                                                      None]):
                do_move(attack)

            # Create the model
            model.take_turn = take_turn
        else:
            # Get a random switch index
            move_idx = chances(switch_probs,
                               [i for i, _ in enumerate(switch_probs)])

            # Create a turn function
            def take_turn(_: Player, __: Player, ___: Callable[[Move], None],
                          ____: Callable[[Item], None],
                          switch_pokemon: Callable[[int], None]):
                switch_pokemon(move_idx)

            # Create the model
            model.take_turn = take_turn

        return model, move_type, move_idx, move_probs, switch_probs
Esempio n. 18
0
import sys
from os.path import join, dirname

sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.battle import Battle
from pokemon_ai.classes import Bag, Player
from pokemon_ai.data import get_party
from pokemon_ai.ai.models import DamageModel

party1 = get_party("charizard")
party2 = get_party("bulbasaur")

player1 = Player("Player 1", party1, Bag())
player2 = Player("Player 2", party2, Bag(), DamageModel())

battle = Battle(player1, player2)
battle.play()
Esempio n. 19
0
from pokemon_ai.battle import Battle
from pokemon_ai.classes import Bag, Party, Player, Move, MoveBank, Pokemon, Stats, PokemonType
from pokemon_ai.ai.models import SampleModel

party1 = Party([
    Pokemon(PokemonType.FIRE, "Charizard", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Flamethrower", 100, 0, PokemonType.FIRE, True)]),
            300),
    Pokemon(PokemonType.WATER, "Piplup", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Water Squirt", 100, 3, PokemonType.WATER, True)]),
            300)
])
party2 = Party([
    Pokemon(PokemonType.FIRE, "Blaziken", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Flamethrower", 100, 3, PokemonType.FIRE, True)]),
            300),
    Pokemon(PokemonType.WATER, "Squirtle", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Water Squirt", 100, 3, PokemonType.WATER, True)]),
            300)
])

player1 = Player("Player 1", party1, Bag())
player2 = Player("Player 2", party2, Bag(), SampleModel())

battle = Battle(player1, player2)
battle.play()
Esempio n. 20
0
    def _make_actual_output_list(player: Player, node: Any) -> np.ndarray:
        """
        Creates a list of actual output values from a player object and a single node.
        :param player: A Player that owns the action in the node.
        :param node: A MonteCarloNode.
        :return: A list of output values of length OUTPUT_SIZE (31).
        """

        # Get list of Pokemon
        player_pokemon = player.get_party().get_as_list()

        # Calculate switch probabilities
        switch_probs = [EPSILON] * len(player_pokemon)
        for child in node.children:
            if child.action_type == 0:  # SWITCH
                pkmn_id = child.action_descriptor
                switch_idx = 0
                for i, pkmn in enumerate(player_pokemon):
                    if pkmn.get_id() == pkmn_id:
                        switch_idx = i
                switch_probs[switch_idx] = child.outcome / node.outcome
        # Create a tuple of switch probability / Pokemon values
        pokemon_switch_tuple: List[Tuple[float, Pokemon]] = []
        for i, switch_prob in enumerate(switch_probs):
            pokemon_switch_tuple.append((switch_prob, player_pokemon[i]))
        # Sort the tuple by Pokemon ID to retain order
        pokemon_switch_tuple.sort(key=lambda v: v[1].get_id())
        # Remake switch_probs in order
        switch_probs = [tup[0] for tup in pokemon_switch_tuple]
        # Add extra probs in case of short party
        for _ in range(POKEMON_PARTY_LIMIT - len(player_pokemon)):
            switch_probs.append(EPSILON)

        # Add attack moves of every Pokemon
        pkmn_id_to_move_prob_map = {}
        for child in node.children:
            if child.action_type == 0:  # ATTACK
                pkmn_id = child.detokenize_child()
                move_idx = child.action_descriptor
                if pkmn_id not in pkmn_id_to_move_prob_map:
                    pkmn_id_to_move_prob_map[pkmn_id] = [EPSILON
                                                         ] * POKEMON_MOVE_LIMIT
                pkmn_id_to_move_prob_map[pkmn_id][
                    move_idx] = child.outcome / node.outcome
        # Create a list of moves for all Pokemon and then traverse the sorted Pokemon list, adding the prob of each move
        # one by one.
        move_probs = []
        for pkmn in player.get_party().get_sorted_list():
            pkmn_id = pkmn.get_id()
            move_probs_for_pkmn = []
            if pkmn_id in pkmn_id_to_move_prob_map:
                for move_prob in pkmn_id_to_move_prob_map[pkmn_id]:
                    move_probs_for_pkmn.append(move_prob)
            while len(move_probs_for_pkmn) < 4:
                move_probs_for_pkmn.append(EPSILON)
            move_probs += move_probs_for_pkmn
        while len(move_probs) < POKEMON_PARTY_LIMIT * POKEMON_MOVE_LIMIT:
            move_probs += [EPSILON] * POKEMON_MOVE_LIMIT

        outcome_list = [node.outcome]

        mat = switch_probs + move_probs + outcome_list
        return np.array(mat)
Esempio n. 21
0
def make_tree(player_real: Player, other_player_real: Player, num_plays=1, predictor: Predictor = None, learning_turns: int = 10, use_damage_model=False, verbose=False):
    """
    Creates a MonteCarloTree of actions for the given battle.
    :param player_real: The player to find actions for.
    :param other_player_real: The opposing player.
    :param num_plays: The number of Monte Carlo simulations to perform.
    :param predictor: An optional neural network to weigh he training.
    :param learning_turns: Number of turns the model will learn before making decisions.
    :param use_damage_model: Use the DamageModel?
    :param verbose: Should the algorithm announce its current actions?
    :return: A MonteCarloTree.
    """

    # Create tree
    tree = MonteCarloTree(player_real.copy(), other_player_real.copy())
    root = tree.root
    root.player.set_model(RandomModel())
    root.other_player.set_model(RandomModel() if not use_damage_model else DamageModel())
    root.depth = 1
    root.description = 'Battle Start'

    # Use workaround to pass this to children
    current_learning_turn = [0]

    # Play num_plays amount of times
    for current_num_plays in range(num_plays):
        def backprop(node: MonteCarloNode, outcome: float) -> None:
            """
            Backpropogates and updates all nodes from the top node using the sums of the leaf nodes.
            Included logic to add wins for respective player
            :param node: The leaf node to start backpropgating from
            :param outcome: The calculated outcome
            """
            if node.depth % 2 == 0 or node.depth == 1:
                node.outcome += outcome
            else:
                node.outcome += (1 - outcome)
            node.visit()
            if node.parent is not None:
                backprop(node.parent, outcome)

        def create_node(node_player: Player, node_other_player: Player, action_type: MonteCarloActionType, index: int) -> MonteCarloNode:
            """
            Creates an attack or switch node.
            :param node_player: The current player object for the node.
            :param action_type: Either MonteCarloActionType.ATTACK or MonteCarloActionType.SWITCH.
            :param index: The index of the attack or Pokemon to switch to.
            """
            # The currently battling pokemon
            pokemon = node_player.get_party().get_starting()

            # Create action descriptor
            if action_type == MonteCarloActionType.ATTACK:
                attack = pokemon.get_move_bank().get_move(index)
                action_descriptor = index
                description = "%s used %s." % (pokemon.get_name(), attack.get_name())
            else:
                switch_pokemon = node_player.get_party().get_at_index(index)
                action_descriptor = switch_pokemon.get_id()
                description = "%s switched out with %s." % (pokemon.get_name(), switch_pokemon.get_name())

            # Create the "turn" to be taken when this node is visited
            if action_type == MonteCarloActionType.ATTACK:
                def take_turn(_: Player, __: Player, do_move: Callable[[Move], None], ___: Callable[[Item], None],
                              ____: Callable[[int], None]):
                    do_move(attack)
            else:
                def take_turn(_: Player, __: Player, ___: Callable[[Move], None], ____: Callable[[Item], None],
                              switch_pokemon_at_idx: Callable[[int], None]):
                    switch_pokemon_at_idx(index)

            model = RandomModel()
            model.take_turn = take_turn

            # Return the move node
            return MonteCarloNode(node_player.copy(), node_other_player.copy(), node_player.get_id(), action_type, action_descriptor, model, 0, description)

        def insert_node(node: MonteCarloNode, parent: MonteCarloNode) -> MonteCarloNode:
            """
            Adds an attack or switch move node to a tree.
            :param node: The current node.
            :param parent: The parent node..
            """
            pokemon = node.player.get_party().get_starting()
            child_exists = parent.has_child(pokemon, node.action_type, node.action_descriptor)

            if not child_exists:
                child = node
                parent.add_child(child, pokemon)
            else:
                child = parent.get_child(pokemon, node.action_type, node.action_descriptor)

            if node.depth % 2 == 1:
                # If on an odd depth, simulate a turn and update the node's state (players).
                node.player.set_model(node.model)
                node.other_player.set_model(parent.model)

                battle = Battle(node.player, node.other_player, 1 if verbose else 0)
                winner = battle.play_turn()

                # Get turn outcome
                if winner is not None and predictor is not None:
                    # Train the predictor
                    predictor.train_model(root, player, other_player)
                    current_learning_turn[0] += 1

            return child

        def traverse(node: MonteCarloNode) -> MonteCarloNode:
            """
            If the node is not fully expanded, pick one of the unvisited children.
            Else, pick the child node with greatest UCT value. If this child node is also
            fully expanded, repeat process.
            """

            def fully_expanded(node: MonteCarloNode):
                if node.depth == 1:
                    c_player = node.player.copy()
                    c_other_player = node.other_player.copy()
                else:
                    c_player = node.other_player.copy()
                    c_other_player = node.player.copy()

                pokemon = c_player.get_party().get_starting()

                # Creates all children for node if they do not already exist, and checks visit (0 is unvisited)
                attacks = list(filter(lambda m: m[0].is_available(),
                                      [(move, i) for i, move in enumerate(pokemon.get_move_bank().get_as_list())]))
                for _, attack_idx in attacks:
                    child = create_node(c_player, c_other_player, MonteCarloActionType.ATTACK, attack_idx)
                    insert_node(child, node)

                switches = list(filter(lambda p: not p[0].is_fainted(),
                                       [(pkmn, i) for i, pkmn in enumerate(node.player.get_party().get_as_list())]))[1:]
                for _, switch_idx in switches:
                    child = create_node(c_player, c_other_player, MonteCarloActionType.SWITCH, switch_idx)
                    insert_node(child, node)

                return all([child.visits > 0 for child in node.children])

            def best_uct_node(node: MonteCarloNode) -> MonteCarloNode:
                uct_values = []
                for child in node.children:
                    uct_values.append(calculations.upper_confidence_bounds(child.outcome, child.visits, node.visits))
                index_of_best_move = uct_values.index(max(uct_values))
                return node.children[index_of_best_move]

            def pick_unvisited(node: MonteCarloNode) -> MonteCarloNode:
                for child in node.children:
                    if child.visits == 0:
                        return child
                return None

            # Adds the opponents moves as child nodes to player's moves (MCT will calculate best move for both sides)
            while fully_expanded(node):
                node = best_uct_node(node)

            return pick_unvisited(node) or node

        # -------------------------
        # START OF ACTUAL ALGORITHM
        # -------------------------
        # Traverse and find the leaf to recur from
        leaf = traverse(root)

        # If the leaf has an even depth, the opponent has yet to chose a move. Thus, set the opp's model to random and
        # take a turn. Then continue to randomly simulate the rest of the battle.
        if leaf.depth % 2 == 0:
            # Even depth also indicates the player is player 1.
            player = leaf.player.copy()
            other_player = leaf.other_player.copy()

            # Adding a move for opponent and taking a turn.
            player.set_model(leaf.model)
            other_player.set_model(RandomModel() if not use_damage_model else DamageModel())

            battle = Battle(player, other_player, 1 if verbose else 0)
            winner = battle.play_turn()

            if winner is None:
                if predictor is not None and current_learning_turn[0] < learning_turns:
                    player.set_model(RandomModel())
                else:
                    model, _, _, _, _ = predictor.predict_move(player, other_player)
                    player.set_model(model)
                battle.play()
        else:
            # Odd depth indicates player is player 2.
            player = leaf.other_player.copy()
            other_player = leaf.player.copy()

            if predictor is not None and current_learning_turn[0] < learning_turns:
                player.set_model(RandomModel())
            else:
                model, _, _, _, _ = predictor.predict_move(player, other_player)
                player.set_model(model)

            other_player.set_model(RandomModel() if not use_damage_model else DamageModel())

            battle = Battle(player, other_player, 1 if verbose else 0)
            battle.play()

        outcome = calculations.outcome_func_v1(player, other_player)

        # On each run, calculate the outcomes via backpropagation
        backprop(leaf, outcome)

    return tree
Esempio n. 22
0
import sys
from os.path import join, dirname
sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.battle import Battle

from pokemon_ai.data import get_random_party
from pokemon_ai.classes import Bag, Player
from pokemon_ai.ai.models import RandomModel


party1 = get_random_party()
party2 = get_random_party()

player1 = Player("Player 1", party1, Bag())
player2 = Player("Player 2", party2, Bag(), RandomModel())

battle = Battle(player1, player2)
battle.play()
Esempio n. 23
0
import sys
from os.path import join, dirname

sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.battle import Battle
from pokemon_ai.classes import Bag, Party, Player, Move, MoveBank, Pokemon, Stats, PokemonType

party1 = Party([
    Pokemon(PokemonType.FIRE, "Charizard", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Flamethrower", 100, 0, PokemonType.FIRE, True)]),
            300),
    Pokemon(PokemonType.WATER, "Piplup", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Water Squirt", 100, 3, PokemonType.WATER, True)]),
            300)
])
party2 = Party([
    Pokemon(PokemonType.FIRE, "Blaziken", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Flamethrower", 100, 3, PokemonType.FIRE, True)]),
            300),
    Pokemon(PokemonType.WATER, "Squirtle", 100, Stats(300, 300, 300, 300, 300),
            MoveBank([Move("Water Squirt", 100, 3, PokemonType.WATER, True)]),
            300)
])

player1 = Player("Player 1", party1, Bag())
player2 = Player("Player 2", party2, Bag())

battle = Battle(player1, player2, 2)
battle.play()
Esempio n. 24
0
import sys
from os.path import join, dirname
sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.battle import Battle
from pokemon_ai.classes import Bag, Player
from pokemon_ai.data import get_party
from pokemon_ai.ai.models import PorygonModel, RandomModel

win_count = {}

for i in range(3):
    party1 = get_party("charizard", "venusaur")
    party2 = get_party("blastoise", "tentacruel")

    player1 = Player("MCTS", party1, Bag(), PorygonModel(), player_id=1)
    player2 = Player("GET HIT WIT ALLA DAT",
                     party2,
                     Bag(),
                     RandomModel(),
                     player_id=2)

    battle = Battle(player1, player2, 1)
    winner = battle.play()

    win_count[winner.get_name()] = int(0 if win_count.get(winner.get_name(
    )) is None else win_count.get(winner.get_name())) + 1

# for i in range(5):
#     party3 = get_party("bulbasaur", "venusaur", "ivysaur")
#     party4 = get_party("squirtle", "charizard", "wartortle")
Esempio n. 25
0
 def _turn_check_pokemon(self, player: Player):
     other_player = self.player2 if player.get_id(
     ) == self._PLAYER_1_ID else self.player1
     clear_battle_screen()
     print(str(other_player.get_party()))
Esempio n. 26
0
import sys
from os.path import join, dirname
sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.data import get_party
from pokemon_ai.classes import Player
from pokemon_ai.ai.models import RandomModel
from pokemon_ai.ai.models.porygon_model.mcts import make_tree

party1 = get_party("charizard", "venusaur")
party2 = get_party("blastoise", "caterpie")
print("%s vs. %s" % (', '.join([pkmn.get_name() for pkmn in party1.get_as_list()]), ', '.join([pkmn.get_name() for pkmn in party2.get_as_list()])))

player1 = Player("Player 1", party1, None, RandomModel(), player_id=1)
player2 = Player("Player 2", party2, None, RandomModel(), player_id=2)

tree = make_tree(player1, player2, 1000)
tree.print()

outcome_probs = tree.get_action_probabilities()
for prob in outcome_probs:
    print("Outcome: %1.4f, Prob: %1.4f, Visits: %d, Move: %s" % prob)
Esempio n. 27
0
    def _turn_switch_pokemon(self,
                             player: Player,
                             none_option=True,
                             ai_pokemon_idx: int = None):
        """
        Called when the player must switch Pokemon.
        :param player: The player who is switching pokemon.
        :param none_option: Is the player allowed to go back? Aka, is there an option to choose "None"?
        :param ai_pokemon_idx: The index of the Pokemon the AI is switching out.
        :return: True if the player switches Pokemon, false otherwise.
        """
        # Get the other player
        other_player = self.player2 if player.get_id(
        ) == self._PLAYER_1_ID else self.player1

        current_pokemon = player.get_party().get_starting()
        can_switch_pokemon = not all([
            pokemon.is_fainted()
            for pokemon in player.get_party().get_as_list()
        ])
        if not can_switch_pokemon:
            return False
        if not player.is_ai():
            while True:
                # Write the options out
                options = [
                    p.get_name() + " (" + str(p.get_hp()) + "/" +
                    str(p.get_base_hp()) + " HP)"
                    for p in player.get_party().get_as_list()
                ]
                if none_option:
                    options.insert(0, 'None (Go back)')
                item = prompt_multi(
                    'Which Pokémon would you like to switch in?', *options)[0]

                # The player chooses "None"
                if none_option and item == 0:
                    return False

                # Get the Pokemon to switch in
                idx = item - bool(none_option)
                switched_pokemon = player.get_party().get_at_index(idx)

                if switched_pokemon.is_fainted():
                    self._alert(switched_pokemon.get_name() + ' has fainted.',
                                player)
                elif idx == 0:
                    self._alert(
                        switched_pokemon.get_name() +
                        ' is currently in battle.', player)
                else:
                    if current_pokemon.get_status() == Status.CONFUSION:
                        current_pokemon.set_status(None)
                    switched_pokemon.reveal()
                    self._alert(
                        'Switched ' + current_pokemon.get_name() + ' with ' +
                        switched_pokemon.get_name() + '.', player)
                    self._alert(
                        player.get_name() + ' switched ' +
                        current_pokemon.get_name() + ' with ' +
                        switched_pokemon.get_name() + '.', other_player)
                    player.get_party().make_starting(idx)
                    return True
        elif player.is_ai():
            while ai_pokemon_idx is None or ai_pokemon_idx == 0 or ai_pokemon_idx >= len(
                    player.get_party().get_as_list()):
                ai_pokemon_idx = player.get_model().force_switch_pokemon(
                    player.get_party())
            switched_pokemon = player.get_party().get_at_index(ai_pokemon_idx)
            player.get_party().make_starting(ai_pokemon_idx)
            if current_pokemon.get_status() == Status.CONFUSION:
                current_pokemon.set_status(None)
            switched_pokemon.reveal()
            self._alert(
                'Switched ' + current_pokemon.get_name() + ' with ' +
                switched_pokemon.get_name() + '.', player)
            self._alert(
                player.get_name() + ' switched ' + current_pokemon.get_name() +
                ' with ' + switched_pokemon.get_name() + '.', other_player)
            return True
        else:
            return False
Esempio n. 28
0
 def take_turn(self, player: Player, other_player: Player, attack: Callable[[Move], None],
               use_item: Callable[[Item], None], switch_pokemon_at_idx: Callable[[int], None]) -> None:
     # I don't know what to do yet, so I'll just attack with my pokemon's first move.
     my_pokemon = player.get_party().get_starting()
     attack_move = my_pokemon.get_move_bank().get_move(0)
     attack(attack_move)
Esempio n. 29
0
import sys
from os.path import join, dirname
sys.path.append(join(dirname(__file__), '../..'))

from pokemon_ai.battle import Battle

from pokemon_ai.data import get_party
from pokemon_ai.classes import Bag, Player
from pokemon_ai.ai.models import PorygonModel

party1 = get_party("zapdos", "sandslash", "starmie", "charizard", "tauros",
                   "chansey")  # get_random_party()
party2 = get_party("caterpie", "diglett", "rattata", "poliwag", "meowth",
                   "vulpix")  # get_random_party()

player1 = Player("Player 1", party1, Bag())
player2 = Player("Player 2", party2, Bag(), PorygonModel())

battle = Battle(player1, player2, 2, use_hints=True)
battle.play()