poke-env

repository·master·Indexed 18 days ago

https://github.com/hsahovic/poke-env

A Python interface for training Reinforcement Learning bots to battle on Pokemon Showdown. It supports building scripted agents, self-play experiments, and RL workflows, providing tools for action mapping, custom bot development via the Player class, and integration with both local and official Showdown servers.

Tokens
22.7K
Snippets
75
Records
99
Agent score
66%

What's inside poke-env

  1. Overview of Poke-env

    master

    Poke-env is a Python library designed for building scripted agents, self-play experiments, and reinforcement learning workflows on Pokémon Showdown.

    It provides a battle-centric API centered around four core abstractions:

    • Players: Represent the entities participating in battles.
    • Battles: Represent the active combat state and interactions.
    • Pokémon: Represent the individual combatants.
    • Moves: Represent the actions available to Pokémon.

    Additionally, it provides a Farama Gymnasium interface specifically for reinforcement learning workflows.

  2. Compute raw Pokémon stats with the stats module

    master

    The poke_env.stats module provides utilities to calculate a Pokémon's raw stats. It accounts for the following factors:

    • Base Stats: The fundamental stats of the species.
    • IVs/DVs: Individual Values or Determinate Values.
    • EVs: Effort Values.
    • Nature: The stat modifiers applied by a Pokémon's nature.
    • Level: The current level of the Pokémon.

    You can use this module to derive the actual stat numbers used during a battle from these various components.

  3. Choose a poke-env workflow example

    master

    The poke-env documentation provides several end-to-end examples tailored to different development goals. Depending on your objective, you should follow one of these specific guides:

    • First local battle and custom agent: Follow the quickstart guide. This requires a local Pokemon Showdown server.
    • Custom team selection and generation: Follow the using_a_custom_teambuilder guide. This requires a local Showdown server.
    • Connecting to official or custom servers to challenge humans: Follow the connecting_to_showdown_and_challenging_humans guide. Note that an account is required for the official public server.
    • Reinforcement Learning (RL) training: Follow the reinforcement_learning guide. This requires a local server and RL-specific dependencies (like Stable-Baselines3).
    • Self-play training: Follow the self_play guide. This uses SuperSuit and Stable-Baselines3 and requires a local server.
    • Custom RL pipelines (Action mapping and strict modes): Follow the action_mapping_and_strict_modes guide to understand environment actions, legality checks, and masks.
    • Debugging battle state issues: Follow the strict_battle_tracking guide if you are experiencing request parsing or battle-state desynchronization issues.
    • Replay persistence: Follow the saving_replays guide to learn about automatic replay persistence and explicit HTML replay export.
  4. Identify battle targets using the Target class

    master
    When issuing moves or abilities in a battle, you may need to specify a Target. The Target class provides the necessary abstractions to represent which Pokemon or entity a move is directed towards, which is particularly important in DoubleBattle formats where multiple targets may be available.
  5. Manage the Player connection lifecycle

    master

    By default, a Player starts a background Showdown listener (start_listening=True), which is recommended for live battles.

    To prevent hanging scripts or to perform explicit teardown in tests/notebooks, call await player.ps_client.stop_listening().

    For offline tests or object construction where no connection is desired, set start_listening=False.

    import asyncio
    
    from poke_env.player import RandomPlayer
    
    
    async def main():
        player = RandomPlayer()
        opponent = RandomPlayer()
    
        try:
            await player.battle_against(opponent, n_battles=1)
        finally:
            await player.ps_client.stop_listening()
            await opponent.ps_client.stop_listening()
    
    
    if __name__ == "__main__":
        asyncio.run(main())
  6. Apply action masking in Stable-Baselines3

    master

    poke-env environments provide observations as dictionaries containing "observation" and "action_mask" keys. To prevent a reinforcement learning agent from selecting illegal moves, you must implement a custom policy in Stable-Baselines3 that applies this mask to the action logits.

    1. Custom Features Extractor: Create a BaseFeaturesExtractor that pulls the "observation" tensor from the dictionary.
    2. Masked Policy: Subclass ActorCriticPolicy (or similar) to intercept the "action_mask" and apply it as -inf to the action logits in _get_action_dist_from_latent. This ensures illegal actions have a probability of zero.
    class MaskedActorCriticPolicy(ActorCriticPolicy):
        def __init__(self, *args, **kwargs):
            super().__init__(
                *args,
                **kwargs,
                net_arch=[64, 64],
                features_extractor_class=FeaturesExtractor,
            )
    
        def forward(self, obs, deterministic=False):
            self._mask = obs["action_mask"]
            return super().forward(obs, deterministic)
    
        def evaluate_actions(self, obs, actions):
            self._mask = obs["action_mask"]
            return super().evaluate_actions(obs, actions)
    
        def _get_action_dist_from_latent(self, latent_pi):
            action_logits = self.action_net(latent_pi)
            mask = torch.where(self._mask == 1, 0, float("-inf"))
            return self.action_dist.proba_distribution(action_logits + mask)
  7. Explore Poke-env modules and submodules

    master

    Poke-env is organized into main modules for bot building and standalone submodules for specific Showdown interactions:

    Main Modules

    • battle: Battle management and state.
    • player: Player logic and implementation.
    • pokemon: Pokémon data and attributes.
    • move: Move data and properties.
    • env: Gymnasium-style environments.
    • other_environment: Alternative environment interfaces.

    Standalone Submodules

    • Data: Access and manipulate Pokémon data.
    • PS Client: Interact directly with Pokémon Showdown servers.
    • Teambuilder: Parse and generate Showdown teams.
    • Concurrency utilities: Tools for managing asynchronous operations.
    • Damage calculator: Calculate expected damage outputs.
    • Stats utilities: Utilities for handling Pokémon statistics.
    • Exceptions: Custom exception classes for the library.
  8. Use the Battle object to interact with Pokemon Showdown battles

    master

    The Battle module provides the core abstractions for interacting with an ongoing Pokemon Showdown battle. Depending on the battle format, you should use one of the following classes:

    • Battle: The standard class for single battles.
    • DoubleBattle: A specialized class for double battle formats.

    These classes inherit from AbstractBattle, which defines the common interface for all battle types. You can use these objects to inspect the current state of the battle, such as active Pokemon, health, and field effects, and to issue commands to the bots.

  9. Implement a custom agent by overriding choose_move

    master

    To create a custom agent, inherit from Player and override the choose_move method. This method receives a Battle object representing the current state and must return a valid battle order.

    Key components for implementation:

    • battle.available_moves: A list of Move objects available this turn.
    • self.create_order(move_or_pokemon, ...): A helper to generate valid battle messages. You can pass a Move or a Pokemon object. For moves, you can specify parameters like terastallize=True.
    • self.choose_random_move(battle): A fallback method that returns a valid random action (move or switch) if no specific move is chosen.
    from poke_env.player import Player
    
    class MaxDamagePlayer(Player):
        def choose_move(self, battle):
            if battle.available_moves:
                # Logic to pick the best move
                best_move = max(battle.available_moves, key=lambda move: move.base_power)
                
                # Example of using terastallize
                if battle.can_tera:
                    return self.create_order(best_move, terastallize=True)
                return self.create_order(best_move)
            else:
                # Fallback to a random valid action
                return self.choose_random_move(battle)
    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)
  10. Enable strict battle tracking in Player

    master

    The Player class provides a strict_battle_tracking option to aggressively validate internal battle-state consistency against Pokemon Showdown requests. This is primarily used for debugging parser or state issues rather than standard training runs.

    When to use it

    • Debugging battle-state desynchronization issues.
    • Validating custom battle parsing logic or advanced agents.
    • Developing against multiple formats and requiring early failure on inconsistencies.

    Caveats

    • Enabling this mode may raise assertions if Showdown requests and the locally tracked state disagree.
    • In scenarios involving heavy use of illusions, special handling may be required in your test setup.
    • For production runs where resilience is preferred over strict validation, keep strict_battle_tracking=False.
    from poke_env.player import RandomPlayer
    
    player = RandomPlayer(
        battle_format="gen9randombattle",
        strict_battle_tracking=True,
    )