Adapting the max player to gen 8 OU and managing team preview
The corresponding complete source code can be found here.
Note
A similar example using gen 7 mechanics is available here.
This example adapts Creating a simple max damage player to the gen 8 overused format. In particular, it shows how to specify a team and manage team preview.
Team Preview management
We start with the MaxDamagePlayer
from Creating a simple max damage player, and add a team preview method.
class MaxDamagePlayer(Player):
# Same method as in previous examples
def choose_move(self, battle):
# If the player can attack, it will
if battle.available_moves:
# Finds the best move among available ones
best_move = max(battle.available_moves, key=lambda move: move.base_power)
return self.create_order(best_move)
# If no attack is available, a random switch will be made
else:
return self.choose_random_move(battle)
def teampreview(self, battle):
...
teampreview
takes a Battle
object as argument, and returns a team preview order. These orders are strings of the form /team abcdef
, where abcdef
is a sequence of integers from 1 to 6, which designates the pokemons in your team and determines the order in which they are ordered: in particular, the first integer defines your lead.
You can access your team with Battle.team
and your opponent’s team with Battle.opponent_team
.
The order of the keys in Battle.team
is the same as the order that showdown is considering: if you want to lead with the second pokemon in your team, returning /team 213456
would work.
Here, we are going to evaluate how good of a lead each pokemon we have is, and return the one we deem to be best. To do that, we are going to need an evaluation function.
We define it as follows: we evaluate the performance of a pokemon against another one as the difference between the effectiveness of the first pokemon and the second’s pokemon types. Here is an implementation:
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))
# 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))
# Our performance metric is the different between the two
return a_on_b - b_on_a
We can now use it in our teampreview
method:
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])
This method sends our pokemons ordered by their average estimated performance against the opponent team.
Specifying a team
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.
Here is an example team, both in showdown and packed formats:
Packed format
|Timid|,,4,252,,252|||||]Landorus-Therian||leftovers|intimidate|earthquake,uturn,stealthrock,hiddenpowerice|Impish|120,,252,,,136||,30,30,,,|||]Toxapex||shedshell|regenerator|scald,toxicspikes,recover,toxic|Bold|252,,60,,196,||,0,,,,|||]Serperior||leftovers|contrary|leafstorm,leechseed,substitute,hiddenpowerfire|Timid|,,4,252,,252||,0,,,,|||]Celesteela||leftovers|beastboost|heavyslam,protect,earthquake,leechseed|Sassy|252,,28,,228,|||||]Medicham-Mega||medichamite|purepower|fakeout,highjumpkick,zenheadbutt,icepunch|Adamant|,252,,,4,252|||||
Showdown format
Tapu Koko @ Electrium Z
Ability: Electric Surge
EVs: 4 Def / 252 SpA / 252 Spe
Timid Nature
- Thunderbolt
- U-turn
- Hidden Power [Ice]
- Taunt
Landorus-Therian @ Leftovers
Ability: Intimidate
EVs: 120 HP / 252 Def / 136 Spe
Impish Nature
- Earthquake
- U-turn
- Stealth Rock
- Hidden Power [Ice]
Toxapex @ Shed Shell
Ability: Regenerator
EVs: 252 HP / 60 Def / 196 SpD
Bold Nature
IVs: 0 Atk
- Scald
- Toxic Spikes
- Recover
- Toxic
Serperior @ Leftovers
Ability: Contrary
EVs: 4 Def / 252 SpA / 252 Spe
Timid Nature
IVs: 0 Atk
- Leaf Storm
- Leech Seed
- Substitute
- Hidden Power [Fire]
Celesteela @ Leftovers
Ability: Beast Boost
EVs: 252 HP / 28 Def / 228 SpD
Sassy Nature
- Heavy Slam
- Protect
- Earthquake
- Leech Seed
Medicham-Mega @ Medichamite
Ability: Pure Power
EVs: 252 Atk / 4 SpD / 252 Spe
Adamant Nature
- Fake Out
- High Jump Kick
- Zen Headbutt
- Ice Punch
Attributing a team to an agent
To attribute a team to an agent, you need to pass a team
argument to the agent’s constructor. This argument can either be a Teambuilder
object, or the string describing your team. Here is an example:
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
Cinderace (M) @ Life Orb
Ability: Blaze
EVs: 252 Atk / 4 SpD / 252 Spe
Jolly Nature
- Pyro Ball
- Sucker Punch
- U-turn
- High Jump Kick
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
"""
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 @ King's Rock
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 @ King's Rock
Ability: Skill Link
EVs: 252 Atk / 4 Def / 252 Spe
Jolly Nature
- Bullet Seed
- Knock Off
- Rock Blast
- Tail Slap
"""
# We create two players.
random_player = RandomPlayer(
battle_format="gen8ou",
team=team_1,
max_concurrent_battles=10,
)
max_damage_player = MaxDamagePlayer(
battle_format="gen8ou",
team=team_2,
max_concurrent_battles=10,
)
Warning
Parsing team can be sensitive to case or spaces. If you encounter errors, make sure that the string your are passing does not contain any unexpected characters.
Warning
Team parsing is a recent feature, and may contain unexpected bugs. If you encounter one, please do not hesitate to open an issue.
Running and testing our agent
We can now test our agent. To do so, we can use the cross_evaluate
function from poke_env.player
or the battle_against
method from Player
.
import asyncio
import numpy as np
from poke_env.player import Player, RandomPlayer
class MaxDamagePlayer(Player):
def choose_move(self, battle):
# If the player can attack, it will
if battle.available_moves:
# Finds the best move among available ones
best_move = max(battle.available_moves, key=lambda move: move.base_power)
return self.create_order(best_move)
# If no attack is available, a random switch will be made
else:
return self.choose_random_move(battle)
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])
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))
# 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))
# Our performance metric is the different between the two
return a_on_b - b_on_a
async def main():
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
Cinderace (M) @ Life Orb
Ability: Blaze
EVs: 252 Atk / 4 SpD / 252 Spe
Jolly Nature
- Pyro Ball
- Sucker Punch
- U-turn
- High Jump Kick
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
"""
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 @ King's Rock
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 @ King's Rock
Ability: Skill Link
EVs: 252 Atk / 4 Def / 252 Spe
Jolly Nature
- Bullet Seed
- Knock Off
- Rock Blast
- Tail Slap
"""
# We create two players.
random_player = RandomPlayer(
battle_format="gen8ou",
team=team_1,
max_concurrent_battles=10,
)
max_damage_player = MaxDamagePlayer(
battle_format="gen8ou",
team=team_2,
max_concurrent_battles=10,
)
# Now, let's evaluate our player
await max_damage_player.battle_against(random_player, n_battles = 100)
print(
"Max damage player won %d / 100 battles"
% max_damage_player.n_won_battles
)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Running it should take a couple of seconds and print something similar to this:
Max damage player won 99 / 100 battles
If you want to use Reinforcement Learning, take a look at Reinforcement learning with the OpenAI Gym wrapper example.
If you want to create a custom teambuilder, take a look at Creating a custom teambuilder.