poke-env Quickstart: Practical Examples and Snippets

Complete source code for this example is available here.

Note: this notebooks requires a locally running Pokémon Showdown server. Please see the getting started section for help on how to set one up.

Creating Agents and Making Them Battle

Creating Built-in Agents

poke-env comes with a few built-in agents. These agents are meant to be used as a baseline for your own agents.

The simplest agent is the RandomPlayer agent. This agent will select a random valid move at each turn. Let’s create one:

[1]:
import sys

sys.path.append("../src")
[2]:
from poke_env import RandomPlayer
from poke_env.data import GenData

# The RandomPlayer is a basic agent that makes decisions randomly,
# serving as a starting point for more complex agent development.
random_player = RandomPlayer()

Creating a Battle

To create a battle, let’s create a second agent and use the battle_against method. It’s an asynchronous method, so we need to await it.

[3]:
second_player = RandomPlayer()

# The battle_against method initiates a battle between two players.
# Here we are using asynchronous programming (await) to start the battle.
await random_player.battle_against(second_player, n_battles=1)

If you want to look at this battle, you can open a browser at http://localhost:8000 - you should see the battle in the lobby.

Inspecting the Result

Here are a couple of ways to inspect the result of this battle.

[4]:
# n_won_battles and n_finished_battles

print(
    f"Player {random_player.username} won {random_player.n_won_battles} out of {random_player.n_finished_battles} played"
)
print(
    f"Player {second_player.username} won {second_player.n_won_battles} out of {second_player.n_finished_battles} played"
)

# Looping over battles

for battle_tag, battle in random_player.battles.items():
    print(battle_tag, battle.won)
Player RandomPlayer 1 won 1 out of 1 played
Player RandomPlayer 2 won 0 out of 1 played
battle-gen9randombattle-170198 True

You can look at more properties of the Player and Battle classes in the documentation.

Running a Cross-Evaluation

poke-env provides a cross_evaluate function, that allows you to run a cross evaluation between multiple agents. It will run a number of battles between the two agents, and return the results of the evaluation in a structured way.

[5]:
from poke_env import cross_evaluate

third_player = RandomPlayer()

players = [random_player, second_player, third_player]

cross_evaluation = await cross_evaluate(players, n_challenges=5)
cross_evaluation
[5]:
{'RandomPlayer 1': {'RandomPlayer 1': None,
  'RandomPlayer 2': 0.8333333333333334,
  'RandomPlayer 3': 0.8},
 'RandomPlayer 2': {'RandomPlayer 1': 0.16666666666666666,
  'RandomPlayer 2': None,
  'RandomPlayer 3': 0.6},
 'RandomPlayer 3': {'RandomPlayer 1': 0.2,
  'RandomPlayer 2': 0.4,
  'RandomPlayer 3': None}}

Here’s one way to pretty print the results of the cross evaluation using tabulate:

[6]:
from tabulate import tabulate

table = [["-"] + [p.username for p in players]]
for p_1, results in cross_evaluation.items():
    table.append([p_1] + [cross_evaluation[p_1][p_2] for p_2 in results])

print(tabulate(table))
--------------  -------------------  ------------------  --------------
-               RandomPlayer 1       RandomPlayer 2      RandomPlayer 3
RandomPlayer 1                       0.8333333333333334  0.8
RandomPlayer 2  0.16666666666666666                      0.6
RandomPlayer 3  0.2                  0.4
--------------  -------------------  ------------------  --------------

Building a Max Damage Player

In this section, we introduce the MaxDamagePlayer, a custom agent designed to choose moves that maximize damage output.

Implementing the MaxDamagePlayer Class

The primary task is to override the choose_move method. This method, defined as choose_move(self, battle: Battle) -> str, requires a Battle object as input, representing the current game state, and outputs a move order as a string. This move order must adhere to the showdown protocol format. The poke-env library provides the create_order method to assist in formatting move orders directly from Pokemon and Move objects.

The battle parameter, a Battle object, encapsulates the agent’s current knowledge of the game state. It provides various properties for easy access to game details, such as active_pokemon, available_moves, available_switches, opponent_active_pokemon, opponent_team, and team.

For this example, we’ll utilize available_moves, which gives us a list of Move objects available in the current turn.

Our focus in implementing MaxDamagePlayer involves two key steps: interpreting the game state information from the battle object and then generating and returning a correctly formatted move order.

[7]:
from poke_env.player import Player


class MaxDamagePlayer(Player):
    def choose_move(self, battle):
        # Chooses a move with the highest base power when possible
        if battle.available_moves:
            # Iterating over available moves to find the one with the highest base power
            best_move = max(battle.available_moves, key=lambda move: move.base_power)
            # Creating an order for the selected move
            return self.create_order(best_move)
        else:
            # If no attacking move is available, perform a random switch
            # This involves choosing a random move, which could be a switch or another available action
            return self.choose_random_move(battle)

In the choose_move method, our first step is to determine if there are any available moves for the current turn, as indicated by battle.available_moves. When a move is available, we select the one with the highest base_power. Formatting our choice is achieved by the create_order.

However, there are scenarios where no moves are available. In such cases, we use choose_random_move(battle). This method randomly selects either a move or a switch, and guarantees that we will return a valid order.

The Player.create_order function is a crucial part of this process. It’s a wrapper method that generates valid battle messages. It can take either a Move or a Pokemon object as its input. When passing a Move object, additional parameters such as mega, z_move, dynamax, or terastallize can be specified to indicate special battle actions.

We will adjust our strategy to include terastallize at the earliest opportunity, enhancing the effectiveness of our player in battle scenarios.

[8]:
class MaxDamagePlayer(Player):
    def choose_move(self, battle):
        if battle.available_moves:
            best_move = max(battle.available_moves, key=lambda move: move.base_power)

            if battle.can_tera:
                return self.create_order(best_move, terastallize=True)

            return self.create_order(best_move)
        else:
            return self.choose_random_move(battle)

Testing the MaxDamagePlayer

Next, we’ll test our MaxDamagePlayer against a RandomPlayer in a series of battles:

[9]:
# Creating players
random_player = RandomPlayer()
max_damage_player = MaxDamagePlayer()

# Running battles
await max_damage_player.battle_against(random_player, n_battles=100)

# Displaying results
print(f"Max damage player won {max_damage_player.n_won_battles} / 100 battles")
Max damage player won 92 / 100 battles

Unsurprisingly, the MaxDamagePlayer wins most of the battles.

Setting teams

Most formats do not provide a team automatically.

To specify a team, you have two main options: you can either provide a str describing your team, or a Teambuilder object. This example will focus on the first option; if you want to learn more about using teambuilders, please refer to Creating a custom teambuilder and Teambuilder: Parse, manage and generate showdown teams.

The easiest way to specify a team in poke-env is to copy-paste a showdown team. You can use showdown’s teambuilder and export it directly.

Alternatively, you can use showdown’s packed formats, which correspond to the actual string sent by the showdown client to the server.

Using a str

Here’s an example:

[10]:
team_1 = """
Goodra (M) @ Assault Vest
Ability: Sap Sipper
EVs: 248 HP / 252 SpA / 8 Spe
Modest Nature
IVs: 0 Atk
- Dragon Pulse
- Flamethrower
- Sludge Wave
- Thunderbolt

Sylveon (M) @ Leftovers
Ability: Pixilate
EVs: 248 HP / 244 Def / 16 SpD
Calm Nature
IVs: 0 Atk
- Hyper Voice
- Mystical Fire
- Protect
- Wish

Toxtricity (M) @ Throat Spray
Ability: Punk Rock
EVs: 4 Atk / 252 SpA / 252 Spe
Rash Nature
- Overdrive
- Boomburst
- Shift Gear
- Fire Punch

Seismitoad (M) @ Leftovers
Ability: Water Absorb
EVs: 252 HP / 252 Def / 4 SpD
Relaxed Nature
- Stealth Rock
- Scald
- Earthquake
- Toxic

Corviknight (M) @ Leftovers
Ability: Pressure
EVs: 248 HP / 80 SpD / 180 Spe
Impish Nature
- Defog
- Brave Bird
- Roost
- U-turn

Galvantula @ Focus Sash
Ability: Compound Eyes
EVs: 252 SpA / 4 SpD / 252 Spe
Timid Nature
IVs: 0 Atk
- Sticky Web
- Thunder Wave
- Thunder
- Energy Ball
"""
team_2 = """
Togekiss @ Leftovers
Ability: Serene Grace
EVs: 248 HP / 8 SpA / 252 Spe
Timid Nature
IVs: 0 Atk
- Air Slash
- Nasty Plot
- Substitute
- Thunder Wave

Galvantula @ Focus Sash
Ability: Compound Eyes
EVs: 252 SpA / 4 SpD / 252 Spe
Timid Nature
IVs: 0 Atk
- Sticky Web
- Thunder Wave
- Thunder
- Energy Ball

Cloyster @ Leftovers
Ability: Skill Link
EVs: 252 Atk / 4 SpD / 252 Spe
Adamant Nature
- Icicle Spear
- Rock Blast
- Ice Shard
- Shell Smash

Sandaconda @ Focus Sash
Ability: Sand Spit
EVs: 252 Atk / 4 SpD / 252 Spe
Jolly Nature
- Stealth Rock
- Glare
- Earthquake
- Rock Tomb

Excadrill @ Focus Sash
Ability: Sand Rush
EVs: 252 Atk / 4 SpD / 252 Spe
Adamant Nature
- Iron Head
- Rock Slide
- Earthquake
- Rapid Spin

Cinccino @ Leftovers
Ability: Skill Link
EVs: 252 Atk / 4 Def / 252 Spe
Jolly Nature
- Bullet Seed
- Knock Off
- Rock Blast
- Tail Slap
"""

p1 = MaxDamagePlayer(battle_format="gen8ou", team=team_1)
p2 = MaxDamagePlayer(battle_format="gen8ou", team=team_2)

await p1.battle_against(p2, n_battles=1)

Dealing with team preview

By default, teampreview will be handled by randomly selecting the order of your pokemons. You can change this behaviour by overriding the teampreview method of the Player class. Here is an example using type-based heuristics:

[11]:
import numpy as np


def teampreview_performance(mon_a, mon_b):
    # We evaluate the performance on mon_a against mon_b as its type advantage
    a_on_b = b_on_a = -np.inf
    for type_ in mon_a.types:
        if type_:
            a_on_b = max(
                a_on_b,
                type_.damage_multiplier(
                    *mon_b.types, type_chart=GenData.from_gen(8).type_chart
                ),
            )
    # We do the same for mon_b over mon_a
    for type_ in mon_b.types:
        if type_:
            b_on_a = max(
                b_on_a,
                type_.damage_multiplier(
                    *mon_a.types, type_chart=GenData.from_gen(8).type_chart
                ),
            )
    # Our performance metric is the different between the two
    return a_on_b - b_on_a


class MaxDamagePlayerWithTeampreview(MaxDamagePlayer):
    def teampreview(self, battle):
        mon_performance = {}

        # For each of our pokemons
        for i, mon in enumerate(battle.team.values()):
            # We store their average performance against the opponent team
            mon_performance[i] = np.mean(
                [
                    teampreview_performance(mon, opp)
                    for opp in battle.opponent_team.values()
                ]
            )

        # We sort our mons by performance
        ordered_mons = sorted(mon_performance, key=lambda k: -mon_performance[k])

        # We start with the one we consider best overall
        # We use i + 1 as python indexes start from 0
        #  but showdown's indexes start from 1
        return "/team " + "".join([str(i + 1) for i in ordered_mons])


p3 = MaxDamagePlayerWithTeampreview(battle_format="gen8ou", team=team_1)
p4 = MaxDamagePlayerWithTeampreview(battle_format="gen8ou", team=team_2)

await p3.battle_against(p4, n_battles=1)

Other Initialization Options for Player Objects

Specifying an Avatar

You can specify an avatar argument when initializing a Player object. This argument is a string, corresponding to the avatar’s name.

You can find a list of avatar names here. If the avatar you are looking for is not in this list, you can inspect the message the client is sending to the server by opening your browser’s development console and selecting the avatar manually.

[12]:
player_with_avatar = RandomPlayer(avatar="boarder")

Saving Battle Replays

You can save battle replays by specifying a save_replay value when initializing a Player object. This argument can either be a boolean (if True, the replays will be saved in the replays) or a string - in which case the replays will be saved in the specified directory.

[13]:
player_with_replays = RandomPlayer(save_replays="my_folder")

Logging

Every Player instance has a custom logger. By default, it will only surface warnings and errors. You can change the logging level by specifying a log_level argument when initializing a Player object.

The two most relevant values are logging.INFO or 20, which will surface every message sent or received by the client (which is very useful when debugging) and 25, which is a custom level used by poke-env to surface only the most relevant events.

You can also use logging.DEBUG or 10, but the difference with logging.INFO should only be relevant for poke-env internals.

[14]:
verbose_player = RandomPlayer(log_level=20)

from asyncio import sleep

await sleep(1)
2023-12-17 02:48:16,370 - RandomPlayer 7 - INFO - Starting listening to showdown websocket
2023-12-17 02:48:16,374 - RandomPlayer 7 - INFO - <<< |updateuser| Guest 12|0|170|{"blockChallenges":false,"blockPMs":false,"ignoreTickets":false,"hideBattlesFromTrainerCard":false,"blockInvites":false,"doNotDisturb":false,"blockFriendRequests":false,"allowFriendNotifications":false,"displayBattlesToFriends":false,"hideLogins":false,"hiddenNextBattle":false,"inviteOnlyNextBattle":false,"language":null}
|customgroups|[{"symbol":"&","name":"Administrator","type":"leadership"},{"symbol":"#","name":"Room Owner","type":"leadership"},{"symbol":"★","name":"Host","type":"leadership"},{"symbol":"@","name":"Moderator","type":"staff"},{"symbol":"%","name":"Driver","type":"staff"},{"symbol":"§","name":"Section Leader","type":"staff"},{"symbol":"*","name":"Bot","type":"normal"},{"symbol":"☆","name":"Player","type":"normal"},{"symbol":"+","name":"Voice","type":"normal"},{"symbol":"^","name":"Prize Winner","type":"normal"},{"symbol":"whitelist","name":"Whitelist","type":"normal"},{"symbol":" ","name":null,"type":"normal"},{"symbol":"‽","name":"Locked","type":"punishment"},{"symbol":"!","name":"Muted","type":"punishment"}]
|formats|,LL|,1|S/V Singles|[Gen 9] Random Battle,f|[Gen 9] Unrated Random Battle,b|[Gen 9] Free-For-All Random Battle,7|[Gen 9] Random Battle (Blitz),f|[Gen 9] Multi Random Battle,5|[Gen 9] OU,e|[Gen 9] Ubers,e|[Gen 9] UU,e|[Gen 9] RU,e|[Gen 9] NU,e|[Gen 9] PU,e|[Gen 9] LC,e|[Gen 9] Monotype,e|[Gen 9] CAP,e|[Gen 9] Battle Stadium Singles Regulation D,5c|[Gen 9] Battle Stadium Singles Regulation E,5e|[Gen 9] Dragon King Cup,1e|[Gen 9] Custom Game,c|,1|S/V Doubles|[Gen 9] Random Doubles Battle,f|[Gen 9] Doubles OU,e|[Gen 9] Doubles Ubers,e|[Gen 9] Doubles UU,e|[Gen 9] Doubles LC,c|[Gen 9] VGC 2023 Regulation D,5c|[Gen 9] VGC 2023 Regulation E,5e|[Gen 9] VGC 2023 Regulation E (Bo3),1a|[Gen 9] Doubles Custom Game,c|,1|Unofficial Metagames|[Gen 9] 1v1,e|[Gen 9] 2v2 Doubles,e|[Gen 9] Anything Goes,e|[Gen 9] Free-For-All,6|[Gen 9] LC UU,c|[Gen 9] Monothreat Poison,c|[Gen 9] Monotype CAP,c|[Gen 9] Monotype LC,c|[Gen 9] NFE,c|[Gen 9] ZU,e|,1|Pet Mods|[Gen 3] Hoenn Gaiden,e|[Gen 8] JolteMons Random Battle,f|[Gen 6] NEXT OU,8|,1|Draft|[Gen 9] Paldea Dex Draft,c|[Gen 9] Tera Preview Paldea Dex Draft,c|[Gen 9] 6v6 Doubles Draft,c|[Gen 9] 4v4 Doubles Draft,1c|[Gen 9] NatDex Draft,c|[Gen 9] Tera Preview NatDex Draft,c|[Gen 9] NatDex 6v6 Doubles Draft,c|[Gen 9] NatDex LC Draft,c|[Gen 8] Galar Dex Draft,c|[Gen 8] NatDex Draft,c|[Gen 8] NatDex 4v4 Doubles Draft,1c|[Gen 7] Draft,c|[Gen 6] Draft,c|,2|OM of the Month|[Gen 9] Shared Power,e|[Gen 9] Protean Palace,e|[Gen 9] Inheritance,e|,2|Other Metagames|[Gen 9] Almost Any Ability,e|[Gen 9] Balanced Hackmons,e|[Gen 9] Godly Gift,e|[Gen 9] Mix and Mega,e|[Gen 9] STABmons,e|[Gen 9] Partners in Crime,e|[Gen 6] Pure Hackmons,e|,2|Challengeable OMs|[Gen 9] Camomons,c|[Gen 9] Convergence,c|[Gen 9] Cross Evolution,c|[Gen 9] Fortemons,c|[Gen 9] Frantic Fusions,c|[Gen 9] Full Potential,c|[Gen 9] Pokebilities,c|[Gen 9] Pure Hackmons,c|[Gen 9] Revelationmons,c|[Gen 9] Sharing is Caring,c|[Gen 9] Tera Donation,c|[Gen 9] The Card Game,c|[Gen 9] The Loser's Game,c|[Gen 9] Trademarked,c|,2|National Dex|[Gen 9] National Dex,e|[Gen 9] National Dex Ubers,e|[Gen 9] National Dex UU,e|[Gen 9] National Dex RU,c|[Gen 9] National Dex Monotype,e|[Gen 9] National Dex Doubles,e|[Gen 9] National Dex AG,c|[Gen 9] National Dex BH,c|[Gen 8] National Dex,e|[Gen 8] National Dex UU,c|[Gen 8] National Dex Monotype,c|,3|Randomized Format Spotlight|[Gen 9] Partners in Crime Random Battle,f|[Gen 9] Random Roulette,d|,3|Randomized Metas|[Gen 9] Monotype Random Battle,f|[Gen 9] Random Battle Mayhem,f|[Gen 9] Computer-Generated Teams,f|[Gen 9] Hackmons Cup,f|[Gen 9] Doubles Hackmons Cup,d|[Gen 9] Broken Cup,d|[Gen 9] Challenge Cup 1v1,f|[Gen 9] Challenge Cup 2v2,f|[Gen 9] Challenge Cup 6v6,d|[Gen 9] Metronome Battle,e|[Gen 8] Random Battle,f|[Gen 8] Random Doubles Battle,f|[Gen 8] Free-For-All Random Battle,7|[Gen 8] Multi Random Battle,5|[Gen 8] Battle Factory,f|[Gen 8] BSS Factory,1d|[Gen 8] Super Staff Bros 4,f|[Gen 8] Hackmons Cup,f|[Gen 8] Metronome Battle,c|[Gen 8] CAP 1v1,d|[Gen 8 BDSP] Random Battle,d|[Gen 7] Random Battle,f|[Gen 7] Random Doubles Battle,9|[Gen 7] Battle Factory,f|[Gen 7] BSS Factory,1d|[Gen 7] Hackmons Cup,d|[Gen 7 Let's Go] Random Battle,d|[Gen 6] Random Battle,f|[Gen 6] Battle Factory,9|[Gen 5] Random Battle,f|[Gen 4] Random Battle,f|[Gen 3] Random Battle,f|[Gen 2] Random Battle,f|[Gen 1] Random Battle,f|[Gen 1] Challenge Cup,9|[Gen 1] Hackmons Cup,9|,4|RoA Spotlight|[Gen 6] Ubers,e|[Gen 3] ZU,e|[Gen 3] LC,e|,4|Past Gens OU|[Gen 8] OU,e|[Gen 7] OU,e|[Gen 6] OU,e|[Gen 5] OU,e|[Gen 4] OU,e|[Gen 3] OU,e|[Gen 2] OU,e|[Gen 1] OU,e|,4|Past Gens Doubles OU|[Gen 8] Doubles OU,e|[Gen 7] Doubles OU,e|[Gen 6] Doubles OU,e|[Gen 5] Doubles OU,c|[Gen 4] Doubles OU,c|[Gen 3] Doubles OU,c|,4|Sw/Sh Singles|[Gen 8] Ubers,c|[Gen 8] UU,c|[Gen 8] RU,c|[Gen 8] NU,c|[Gen 8] PU,c|[Gen 8] LC,c|[Gen 8] Monotype,c|[Gen 8] 1v1,c|[Gen 8] Anything Goes,c|[Gen 8] ZU,c|[Gen 8] CAP,c|[Gen 8] Battle Stadium Singles,5c|[Gen 8 BDSP] OU,c|[Gen 8] Custom Game,c|,4|Sw/Sh Doubles|[Gen 8] Doubles Ubers,c|[Gen 8] Doubles UU,c|[Gen 8] VGC 2022,5c|[Gen 8] VGC 2021,5c|[Gen 8] VGC 2020,5c|[Gen 8 BDSP] Doubles OU,c|[Gen 8 BDSP] Battle Festival Doubles,1c|[Gen 8] Doubles Custom Game,c|,4|US/UM Singles|[Gen 7] Ubers,c|[Gen 7] UU,c|[Gen 7] RU,c|[Gen 7] NU,c|[Gen 7] PU,c|[Gen 7] LC,c|[Gen 7] Monotype,c|[Gen 7] 1v1,c|[Gen 7] Anything Goes,c|[Gen 7] ZU,c|[Gen 7] CAP,c|[Gen 7] Battle Spot Singles,5c|[Gen 7 Let's Go] OU,1c|[Gen 7] Custom Game,c|,4|US/UM Doubles|[Gen 7] Doubles UU,c|[Gen 7] VGC 2019,5c|[Gen 7] VGC 2018,5c|[Gen 7] VGC 2017,5c|[Gen 7] Battle Spot Doubles,5c|[Gen 7 Let's Go] Doubles OU,c|[Gen 7] Doubles Custom Game,c|,4|OR/AS Singles|[Gen 6] UU,c|[Gen 6] RU,c|[Gen 6] NU,c|[Gen 6] PU,c|[Gen 6] LC,c|[Gen 6] Monotype,c|[Gen 6] 1v1,c|[Gen 6] Anything Goes,c|[Gen 6] ZU,c|[Gen 6] CAP,c|[Gen 6] Battle Spot Singles,5c|[Gen 6] Custom Game,c|,4|OR/AS Doubles/Triples|[Gen 6] VGC 2016,5c|[Gen 6] VGC 2015,5c|[Gen 6] VGC 2014,5c|[Gen 6] Battle Spot Doubles,5c|[Gen 6] Doubles Custom Game,c|[Gen 6] Battle Spot Triples,1c|[Gen 6] Triples Custom Game,c|,4|B2/W2 Singles|[Gen 5] Ubers,c|[Gen 5] UU,c|[Gen 5] RU,c|[Gen 5] NU,c|[Gen 5] PU,c|[Gen 5] LC,c|[Gen 5] Monotype,c|[Gen 5] 1v1,c|[Gen 5] ZU,c|[Gen 5] CAP,c|[Gen 5] GBU Singles,5c|[Gen 5] Custom Game,c|,4|B2/W2 Doubles|[Gen 5] VGC 2013,5c|[Gen 5] VGC 2012,5c|[Gen 5] VGC 2011,5c|[Gen 5] Doubles Custom Game,c|[Gen 5] Triples Custom Game,c|,4|DPP Singles|[Gen 4] Ubers,c|[Gen 4] UU,c|[Gen 4] NU,c|[Gen 4] PU,c|[Gen 4] LC,c|[Gen 4] Anything Goes,c|[Gen 4] 1v1,c|[Gen 4] ZU,c|[Gen 4] CAP,c|[Gen 4] Custom Game,c|,4|DPP Doubles|[Gen 4] VGC 2010,1c|[Gen 4] VGC 2009,1c|[Gen 4] Doubles Custom Game,c|,4|Past Generations|[Gen 3] Ubers,c|[Gen 3] UU,c|[Gen 3] NU,c|[Gen 3] PU,c|[Gen 3] 1v1,c|[Gen 3] Custom Game,c|[Gen 3] Doubles Custom Game,c|[Gen 2] Ubers,c|[Gen 2] UU,c|[Gen 2] NU,c|[Gen 2] 1v1,c|[Gen 2] Nintendo Cup 2000,c|[Gen 2] Stadium OU,c|[Gen 2] Custom Game,c|[Gen 1] Ubers,c|[Gen 1] UU,c|[Gen 1] NU,c|[Gen 1] PU,c|[Gen 1] 1v1,c|[Gen 1] Japanese OU,c|[Gen 1] Stadium OU,c|[Gen 1] Tradebacks OU,c|[Gen 1] Nintendo Cup 1997,c|[Gen 1] Custom Game,c
2023-12-17 02:48:16,375 - RandomPlayer 7 - INFO - <<< |challstr|4|8f1978d24e0553f9e3bac3088000980eda899fc83326ab257e3dd8f19fefe68c243499dfa7cbcf6d13a8dd836e3670818324ddd0d456c466470005ff0cc1705d973b6feeac31dbe6c9cc5d6ad57b66b08ee3ca8722f03a85777f5547a56e1967118efe04b3c2fd96dc3c51ea71f1f3853e6dd98a3cf343f0a7a085eaf65080f2
2023-12-17 02:48:16,375 - RandomPlayer 7 - INFO - Bypassing authentication request
2023-12-17 02:48:16,375 - RandomPlayer 7 - INFO - >>> |/trn RandomPlayer 7,0,
2023-12-17 02:48:16,376 - RandomPlayer 7 - INFO - <<< |updatesearch|{"searching":[],"games":null}
2023-12-17 02:48:16,377 - RandomPlayer 7 - INFO - <<< |updateuser| RandomPlayer 7|1|170|{"blockChallenges":false,"blockPMs":false,"ignoreTickets":false,"hideBattlesFromTrainerCard":false,"blockInvites":false,"doNotDisturb":false,"blockFriendRequests":false,"allowFriendNotifications":false,"displayBattlesToFriends":false,"hideLogins":false,"hiddenNextBattle":false,"inviteOnlyNextBattle":false,"language":null}

Concurrency

By default, a poke-env Player will only run a single battle at a time. You can change this behavior by specifying a max_concurrent_battles argument when initializing a Player object.

This argument is an integer, and represents the maximum number of battles a Player can run at the same time. If 0, no limit will be enforced.

This can provide a significant speedup when your process is not CPU bound.

[15]:
import time

# Time to run 50 battles, one at a time
start = time.time()
await random_player.battle_against(second_player, n_battles=50)
end = time.time()
print(f"Time to run 50 battles, one at a time: {end - start:.2f} seconds")
Time to run 50 battles, one at a time: 14.34 seconds
[16]:
unrestricted_random_player = RandomPlayer(max_concurrent_battles=0)
unrestricted_second_player = RandomPlayer(max_concurrent_battles=0)

# Time to run 50 battles, in parallel
start = time.time()
await unrestricted_random_player.battle_against(
    unrestricted_second_player, n_battles=50
)
end = time.time()
print(f"Time to run 50 battles, in parallel: {end - start:.2f} seconds")
Time to run 50 battles, in parallel: 3.75 seconds

Other options can also be used on the server side to make battles run faster.

Pokemon Showdown Timer

You can turn on the Pokemon Showdown timer by setting start_timer_on_battle_start to True when initializing a Player object.

This is mostly relevant when pitting your argents against humans.

[17]:
impatient_player = RandomPlayer(start_timer_on_battle_start=True)