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)