PyPokerEngine Documentation

repository·master·Indexed 20 days ago

https://github.com/ishikota/pypokerengine

A poker engine for AI development in Python, optimized for Reinforcement Learning. It provides a framework for creating custom poker AIs by subclassing BasePokerPlayer, an Emulator class for simulating game states and outcomes, and utilities for estimating hole card win rates. Supports Python 2.7 and 3.5.

Tokens
7.8K
Snippets
16
Records
18
Agent score
70%

What's inside PyPokerEngine

  1. Use the Emulator for Reinforcement Learning

    master

    For Reinforcement Learning (RL) workflows, use the Emulator class to simulate game states and outcomes.

    Workflow:

    1. In receive_game_start_message, initialize an Emulator instance.
    2. Set game rules using emulator.set_game_rule(player_num, max_round, small_blind_amount, ante_amount) and emulator.set_blind_structure(blind_structure).
    3. Register player models using emulator.register_player(uuid, model).
    4. In declare_action, use restore_game_state(round_state) to get a usable state, then use the emulator to simulate actions:
      • emulator.apply_action(game_state, action): Applies a single action and returns (updated_state, events).
      • emulator.run_until_round_finish(game_state): Simulates until the end of the current round.
      • emulator.run_until_game_finish(game_state): Simulates until the end of the entire game.
    from pypokerengine.players import BasePokerPlayer
    from pypokerengine.api.emulator import Emulator
    from pypokerengine.utils.game_state_utils import restore_game_state
    
    class RLPlayer(BasePokerPlayer):
        def receive_game_start_message(self, game_info):
            # Setup Emulator
            self.emulator = Emulator()
            self.emulator.set_game_rule(
                game_info["player_num"], 
                game_info["rule"]["max_round"], 
                game_info["rule"]["small_blind_amount"], 
                game_info["rule"]["ante"]
            )
            self.emulator.set_blind_structure(game_info["rule"]["blind_structure"])
            
            for player_info in game_info["seats"]["players":
                self.emulator.register_player(player_info["uuid"], SomePlayerModel())
    
        def declare_action(self, valid_actions, hole_card, round_state):
            game_state = restore_game_state(round_state)
            # Simulate an action to decide
            updated_state, events = self.emulator.apply_action(game_state, "fold")
            # ... decision logic ...
            return "fold", 0
  2. Restore GameState from a round_state object

    master

    If you have a round_state object (the public information passed to BasePokerPlayer callbacks), you can reconstruct a full GameState using game_state_utils.restore_game_state.

    Because round_state does not contain private information like hole cards, you must manually attach them to the restored GameState. You can use attach_hole_card_from_deck to assign random cards or attach_hole_card to assign specific cards.

    from pypokerengine.utils.game_state_utils import restore_game_state, attach_hole_card_from_deck, attach_hole_card
    from pypokerengine.utils.card_utils import gen_cards
    
    # Restore state from public round_state
    game_state = restore_game_state(round_state)
    
    # Option A: Assign random cards to all players
    for player in game_state["table"].seats.players:
        game_state = attach_hole_card_from_deck(game_state, player.uuid)
    
    # Option B: Assign specific cards to a specific player
    for player in game_state["table"].seats.players:
        if player.uuid == "uuid-1":
            holecard = gen_cards(['SA', 'DA'])
            game_state = attach_hole_card(game_state, player.uuid, holecard)
        else:
            game_state = attach_hole_card_from_deck(game_state, player.uuid)
  3. Use the Emulator for fine-grained game control

    master

    While start_poker is used for standard game execution, the Emulator class allows for stepwise or round-by-round simulation. This is useful for Reinforcement Learning or testing AI decision-making by simulating potential future outcomes from a specific game state.

    To use the Emulator:

    1. Initialize an Emulator object.
    2. Configure game rules using set_game_rule.
    3. Register player models (implementations of BasePokerPlayer) using register_player.
    4. Prepare a game_state object (often by restoring a round_state and attaching hole cards).
    5. Progress the simulation using apply_action, run_until_round_finish, or run_until_game_finish.
    from pypokerengine.api.emulator import Emulator
    
    emulator = Emulator()
    emulator.set_game_rule(nb_player=2, max_round=10, sb_amount=5, ante_amount=0)
  4. How to create a custom Poker AI

    master

    To create a poker AI, you must subclass pypokerengine.players.BasePokerPlayer and implement its abstract methods. The core logic of your AI resides in the declare_action method.

    Key methods to implement:

    • declare_action(valid_actions, hole_card, round_state): The core decision-making method. It must return an (action, amount) tuple.
    • receive_game_start_message(game_info)
    • receive_round_start_message(round_count, hole_card, seats)
    • receive_street_start_message(street, round_state)
    • receive_game_update_message(action, round_state)
    • receive_round_result_message(winners, hand_info, round_state)
    from pypokerengine.players import BasePokerPlayer
    
    class FishPlayer(BasePokerPlayer):
        def declare_action(self, valid_actions, hole_card, round_state):
            # valid_actions format => [raise_action_info, call_action_info, fold_action_info]
            call_action_info = valid_actions[1]
            action, amount = call_action_info["action"], call_action_info["amount"]
            return action, amount
    
        def receive_game_start_message(self, game_info):
            pass
    
        def receive_round_start_message(self, round_count, hole_card, seats):
            pass
    
        def receive_street_start_message(self, street, round_state):
            pass
    
        def receive_game_update_message(self, action, round_state):
            pass
    
        def receive_round_result_message(self, winners, hand_info, round_state):
            pass
  5. Create a poker AI by subclassing BasePokerPlayer

    master

    To create a poker AI, you must create a class that inherits from pypokerengine.players.BasePokerPlayer and implement its abstract methods. The core logic of your AI resides in the declare_action method.

    Key methods to implement:

    • declare_action(self, valid_actions, hole_card, round_state): The core decision-making method. It must return an (action, amount) tuple.
    • receive_game_start_message(self, game_info)
    • receive_round_start_message(self, round_count, hole_card, seats)
    • receive_street_start_message(self, street, round_state)
    • receive_game_update_message(self, action, round_state)
    • receive_round_result_message(self, winners, hand_info, round_state)

    In declare_action, the valid_actions parameter is a list formatted as [raise_action_info, call_action_info, fold_action_info]. To perform a call, you can access valid_actions[1] to retrieve the action name and amount.

    from pypokerengine.players import BasePokerPlayer
    
    class FishPlayer(BasePokerPlayer):
        def declare_action(self, valid_actions, hole_card, round_state):
            # valid_actions format => [raise_action_info, call_action_info, fold_action_info]
            call_action_info = valid_actions[1]
            action, amount = call_action_info["action"], call_action_info["amount"]
            return action, amount
    
        def receive_game_start_message(self, game_info):
            pass
    
        def receive_round_start_message(self, round_count, hole_card, seats):
            pass
    
        def receive_street_start_message(self, street, round_state):
            pass
    
        def receive_game_update_message(self, action, round_state):
            pass
    
        def receive_round_result_message(self, winners, hand_info, round_state):
            pass
  6. Create a ConsolePlayer to play manually

    master

    To play poker against an AI manually, you can implement a ConsolePlayer by overriding the BasePokerPlayer class. This player type is designed to display game information via the console and accept user input for actions.

    Key methods to implement:

    • declare_action(valid_actions, hole_card, round_state): Returns the chosen action and amount based on user input.
    • receive_game_start_message(game_info): Triggered when the game begins.
    • receive_round_start_message(round_count, hole_card, seats): Triggered at the start of a new round.
    • receive_street_start_message(street, round_state): Triggered when a new street (e.g., preflop, flop) starts.
    • receive_game_update_message(new_action, round_state): Triggered when an action is taken by any player.
    • receive_round_result_message(winners, hand_info, round_state): Triggered when a round ends and winners are determined.

    Note: The implementation should include error handling for invalid console inputs to prevent crashes.

    import pypokerengine.utils.visualize_utils as U
    
    class ConsolePlayer(BasePokerPlayer):
    
        def declare_action(self, valid_actions, hole_card, round_state):
            print(U.visualize_declare_action(valid_actions, hole_card, round_state, self.uuid))
            action, amount = self._receive_action_from_console(valid_actions)
            return action, amount
    
        def receive_game_start_message(self, game_info):
            print(U.visualize_game_start(game_info, self.uuid))
            self._wait_until_input()
    
        def receive_round_start_message(self, round_count, hole_card, seats):
            print(U.visualize_round_start(round_count, hole_card, seats, self.uuid))
            self._wait_until_input()
    
        def receive_street_start_message(self, street, round_state):
            print(U.visualize_street_start(street, round_state, self.uuid))
            self._wait_until_input()
    
        def receive_game_update_message(self, new_action, round_state):
            print(U.visualize_game_update(new_action, round_state, self.uuid))
            self._wait_until_input()
    
        def receive_round_result_message(self, winners, hand_info, round_state):
            print(U.visualize_round_result(winners, hand_info, round_state, self.uuid))
            self._wait_until_input()
    
        def _wait_until_input(self):
            raw_input("Enter some key to continue ...")
    
        def _receive_action_from_console(self, valid_actions):
            action = raw_input("Enter action to declare >> ")
            if action == 'fold': amount = 0
            if action == 'call':  amount = valid_actions[1]['action']
            if action == 'raise':  amount = int(raw_input("Enter raise amount >> "))
            return action, amount
  7. Implement a custom poker AI by extending BasePokerPlayer

    master

    To create a custom poker AI, inherit from pypokerengine.players.BasePokerPlayer and implement the declare_action method. This method is the core logic of your AI where you decide which action to take based on the current game state.

    Key lifecycle methods to implement:

    • declare_action(self, valid_actions, hole_card, round_state): Returns a tuple of (action_name, amount). valid_actions is a list containing action info objects (e.g., valid_actions[0] for FOLD, valid_actions[1] for CALL).
    • receive_game_start_message(self, game_info): Called when the game begins; useful for capturing player_num from game_info.
    • Other optional methods: receive_round_start_message, receive_street_start_message, receive_game_update_message, and receive_round_result_message allow your AI to react to specific game events.
    from pypokerengine.players import BasePokerPlayer
    from pypokerengine.utils.card_utils import gen_cards, estimate_hole_card_win_rate
    
    NB_SIMULATION = 1000
    
    class HonestPlayer(BasePokerPlayer):
    
        def declare_action(self, valid_actions, hole_card, round_state):
            community_card = round_state['community_card']
            win_rate = estimate_hole_card_win_rate(
                    nb_simulation=NB_SIMULATION,
                    nb_player=self.nb_player,
                    hole_card=gen_cards(hole_card),
                    community_card=gen_cards(community_card)
                    )
            if win_rate >= 1.0 / self.nb_player:
                action = valid_actions[1]  # CALL action info
            else:
                action = valid_actions[0]  # FOLD action info
            return action['action'], action['amount']
    
        def receive_game_start_message(self, game_info):
            self.nb_player = game_info['player_num']
    
        # Implement other required/optional methods as needed...
        def receive_round_start_message(self, round_count, hole_card, seats): pass
        def receive_street_start_message(self, street, round_state): pass
        def receive_game_update_message(self, action, round_state): pass
        def receive_round_result_message(self, winners, hand_info, round_state): pass
  8. Run a poker game with AI and Human players

    master

    To start a poker game, use setup_config to define game parameters and start_poker to execute the simulation. You can register different player algorithms (AI or manual ConsolePlayer) using config.register_player.

    When playing with a ConsolePlayer, set verbose=0 in start_poker so that the player's own visualization logic handles the console output without interference from the engine's internal logging.

    from pypokerengine.api.game import setup_config, start_poker
    
    # Configure game rules
    config = setup_config(max_round=10, initial_stack=100, small_blind_amount=5)
    
    # Register players
    config.register_player(name="fish_player", algorithm=FishPlayer())
    config.register_player(name="human_player", algorithm=ConsolePlayer())
    
    # Start the game
    game_result = start_poker(config, verbose=0)
  9. Play an AI vs AI poker game

    master

    To run a simulation between multiple AI players, follow these steps:

    1. Define game rules using setup_config (e.g., max_round, initial_stack, small_blind_amount).
    2. Register your AI instances with the Config object using config.register_player(name, algorithm).
    3. Start the game using start_poker(config, verbose=1). Setting verbose=1 will output game logs to the console.

    The start_poker function returns a dictionary containing the game rules and the final state of all players (including their names, stacks, and UUIDs).

    from pypokerengine.api.game import setup_config, start_poker
    
    # 1. Define game rules
    config = setup_config(max_round=10, initial_stack=100, small_blind_amount=5)
    
    # 2. Register AI players
    config.register_player(name="p1", algorithm=FishPlayer())
    config.register_player(name="p2", algorithm=FishPlayer())
    config.register_player(name="p3", algorithm=FishPlayer())
    
    # 3. Start the game and get results
    game_result = start_poker(config, verbose=1)
    print(game_result)
  10. Set up a game state object for simulation

    master

    A round_state object contains public information but lacks hole card data. To perform a simulation that includes private information, you must convert the round_state into a game_state object using restore_game_state and then manually attach hole cards.

    When setting up a state for an AI to evaluate its own position:

    • Use attach_hole_card to set the known hole cards for the player being evaluated.
    • Use attach_hole_card_from_deck to assign random hole cards to opponents to simulate unknown hands.
    from pypokerengine.utils.game_state_utils import \
            restore_game_state, attach_hole_card, attach_hole_card_from_deck
    
    def setup_game_state(round_state, my_hole_card):
        game_state = restore_game_state(round_state)
        for player_info in round_state['seats']:
            uuid = player_info['uuid']
            if uuid == self.uuid:
                # Hole card of my player should be fixed.
                game_state = attach_hole_card(game_state, uuid, my_hole_card)
            else:
                # Attach opponents' cards randomly from the deck.
                game_state = attach_hole_card_from_deck(game_state, uuid)