Create Game History for an LLM

Create Game History for an LLM#

If you plan to register LLMs as players, you might want to pass the full game history. This way, the LLM can recognize behavioral patterns of players and make better decisions, perhaps even exploit the weaknesses of the players. In this notebook we define an EventListener class that maintains a game history that you can pass to an LLM.

from maverick.players import FoldBot, CallBot, AggressiveBot
from maverick import (
    PlayerLike,
    PlayerState,
    Game,
    GameEvent,
    GameEventType,
    PlayerAction,
    ActionType,
)
class EventListener:
    def __init__(self, game: Game=None):
        self.game = game
        self.events = []
        self.id_to_player = {}
        self.max_line_length = 80
        self.dump = []

        self.listen(game)
        
    def listen(self, game: Game=None) -> None:
        if game is None:
            return
        
        self.game = game
        self._subscribe_to_events()
        self._register_players()
            
    def _subscribe_to_events(self) -> None:
        for event_type in GameEventType:
            self.game.subscribe(event_type, self._handle_event)

    def _register_players(self) -> None:
        self.id_to_player = {player.id: player for player in self.game.state.players}
    
    @property
    def history(self) -> str:
        return "\n".join(self.events)
    
    def _log_betting_round_started(self) -> None:
        if len(self.game.state.community_cards) == 0:
            self.events.append("Community cards: None")
        else:
            self.events.append(f"Community cards: {[card.code() for card in self.game.state.community_cards]}")
        
        current_bet = self.game.state.current_bet
        current_pot = self.game.state.pot
        last_raise_size = self.game.state.last_raise_size
        self.events.append((
            f"Current pot size: {current_pot}. "
            f"Current bet: {current_bet}. "
            f"Minimum raise size: {last_raise_size}."
        ))
        
        self.events.append("")
    
    def _log_section_header(self, title: str, chr:str, *, add_empty_line:bool=True) -> None:
        len_title = len(title) + 2 if len(title) > 0 else 0
        len_prefix_suffix = (self.max_line_length - len_title) // 2
        title = f" {title} " if len_title > 0 else ""
        msg = chr * len_prefix_suffix + f"{title}" + chr * len_prefix_suffix
        if len(msg) < self.max_line_length:
            msg += chr
        elif len(msg) > self.max_line_length:
            msg = msg[:self.max_line_length]
        msg = msg[:self.max_line_length]
        self.events.append(msg)
        if add_empty_line:
            self.events.append("")
            
    def _handle_event(self, event: GameEvent, game: Game) -> None:
        """Count each type of event."""
        self.dump.append(event.model_dump(mode="json"))
        
        hand_number = game.state.hand_number

        match event.type:
            case GameEventType.GAME_STARTED:
                self._log_section_header("", "=", add_empty_line=False)
                self._log_section_header(f"Game Started", "=", add_empty_line=False)
                self._log_section_header("", "=")
                
                self.events.append("Game type: No-Limit Texas Hold'em")
                self.events.append(f"Small blind: {game.state.small_blind}")
                self.events.append(f"Big blind: {game.state.big_blind}")
                self.events.append(f"Ante: {game.state.ante}")
                self.events.append("")
                
                self.events.append("Players at the table:")
                for player in self.game.state.players:
                    self.events.append(f"    {player.name}: Starting stack {player.state.stack}")
                self.events.append("")
            
            case GameEventType.HAND_STARTED:
                if len(self.events) > 0 and len(self.events[-1].strip()) > 0:
                    self.events.append("")
                
                self._log_section_header(f"Hand {hand_number}", "=")
                
                button_player = game.button
                self.events.append(f"{button_player.name} is on the button.")
                
                small_blind_player = game.small_blind
                self.events.append(f"{small_blind_player.name} is the small blind.")
                
                big_blind_player = game.big_blind
                self.events.append(f"{big_blind_player.name} is the big blind.")
                
                utg_position = game.table.next_occupied_seat(big_blind_player.state.seat, active=True)
                utg_player = game.table[utg_position]
                self.events.append(f"{utg_player.name} is under the gun.")
                
                self.events.append("")
                
                self.events.append(f"Player stacks at the beginning of hand {hand_number}:")
                for player in game.state.players:
                    self.events.append(f"    {player.name}: {player.state.stack}")
                self.events.append("")
                
            case GameEventType.BETTING_ROUND_STARTED:
                street_name = game.state.street.name
                self._log_section_header(f"Hand: {hand_number} | Street: {street_name}", "-")
                self._log_betting_round_started()

            case GameEventType.PLAYER_ACTION_TAKEN:
                action: PlayerAction = event.action
                player_id = event.player_id
                player = self.id_to_player[player_id]
                current_bet = game.state.current_bet
                current_pot = game.state.pot
                last_raise_size = game.state.last_raise_size

                match action.action_type:
                    case ActionType.FOLD:
                        self.events.append(f"[{player.name}] folds. Remaining stack: {player.state.stack}.")
                    case ActionType.CALL:
                        self.events.append(f"[{player.name}] calls. Remaining stack: {player.state.stack}.")
                    case ActionType.RAISE:
                        self.events.append(f"[{player.name}] raises bet to {current_bet}. Remaining stack: {player.state.stack}.")
                    case ActionType.CHECK:
                        self.events.append(f"[{player.name}] checks. Remaining stack: {player.state.stack}.")
                    case ActionType.BET:
                        self.events.append(f"[{player.name}] bets {action.amount}. Remaining stack: {player.state.stack}.")
                        
                self.events.append((
                    f"Current pot size: {current_pot}. "
                    f"Current bet: {current_bet}. "
                    f"Minimum raise size: {last_raise_size}."
                ))
                        
            case GameEventType.BETTING_ROUND_COMPLETED:
                self.events.append("")
                self.events.append(f"Player standings after the betting round:")
                for player in game.state.players:
                    player_pot = player.state.total_contributed
                    state_type = player.state.state_type.name
                    self.events.append(f"    {player.name}: State={state_type}, Pot={player_pot}, Stack={player.state.stack}")
                self.events.append("")

            case GameEventType.SHOWDOWN_STARTED:
                self._log_section_header(f"Hand: {hand_number} | SHOWDOWN", "-")
                
            case GameEventType.PLAYER_CARDS_REVEALED:
                player_id = event.player_id
                player = self.id_to_player[player_id]
                payload = event.payload
                self.events.append((
                    f"{player.name} reveals: {payload['holding']}. "
                    f"Best hand: {payload['best_hand']} ({payload['best_hand_type']}). "
                    f"Hand score: {payload['best_score']}."
                ))
            
            case GameEventType.SHOWDOWN_COMPLETED:
                self.events.append("")
                self.events.append(f"Player stacks after showdown:")
                for player in game.state.players:
                    self.events.append(f"    {player.name}: {player.state.stack}")
                self.events.append("")
                
            case GameEventType.POT_WON:
                player_id = event.player_id
                player = self.id_to_player[player_id]
                amount = event.payload.get("amount", 0)
                self.events.append(f"{player.name} wins {amount}.")
                
            case GameEventType.PLAYER_ELIMINATED:
                player_id = event.player_id
                player = self.id_to_player[player_id]
                self.events.append(f"{player.name} has been eliminated from the game.")

            case GameEventType.HAND_ENDED:
                pass
game = Game(small_blind=10, big_blind=20, max_hands=10)

players: list[PlayerLike] = [
    CallBot(name="CallBot", state=PlayerState(stack=1000)),
    AggressiveBot(name="AggroBot", state=PlayerState(stack=1000)),
    FoldBot(name="FoldBot", state=PlayerState(stack=1000)),
]

for player in players:
    game.add_player(player)


event_listener = EventListener(game)

game.start()

You can turn the list of events that happened during the game as a Pandas DataFrame:

print(event_listener.history)
================================================================================
================================= Game Started =================================
================================================================================

Game type: No-Limit Texas Hold'em
Small blind: 10
Big blind: 20
Ante: 0

Players at the table:
    CallBot: Starting stack 1000
    AggroBot: Starting stack 1000
    FoldBot: Starting stack 1000

==================================== Hand 1 ====================================

CallBot is on the button.
AggroBot is the small blind.
FoldBot is the big blind.
CallBot is under the gun.

Player stacks at the beginning of hand 1:
    CallBot: 1000
    AggroBot: 1000
    FoldBot: 1000

-------------------------- Hand: 1 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[CallBot] calls. Remaining stack: 980.
Current pot size: 50. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 960.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 980.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 960.
Current pot size: 100. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=960
    AggroBot: State=ACTIVE, Pot=40, Stack=960
    FoldBot: State=FOLDED, Pot=20, Stack=980

---------------------------- Hand: 1 | Street: FLOP ----------------------------

Community cards: ['Ts', 'Td', '2c']
Current pot size: 100. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 920.
Current pot size: 140. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 920.
Current pot size: 180. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=920
    AggroBot: State=ACTIVE, Pot=80, Stack=920
    FoldBot: State=FOLDED, Pot=20, Stack=980

---------------------------- Hand: 1 | Street: TURN ----------------------------

Community cards: ['Ts', 'Td', '2c', '6d']
Current pot size: 180. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 880.
Current pot size: 220. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 880.
Current pot size: 260. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=880
    AggroBot: State=ACTIVE, Pot=120, Stack=880
    FoldBot: State=FOLDED, Pot=20, Stack=980

--------------------------- Hand: 1 | Street: RIVER ----------------------------

Community cards: ['Ts', 'Td', '2c', '6d', '9h']
Current pot size: 260. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 840.
Current pot size: 300. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 840.
Current pot size: 340. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=840
    AggroBot: State=ACTIVE, Pot=160, Stack=840
    FoldBot: State=FOLDED, Pot=20, Stack=980

------------------------------ Hand: 1 | SHOWDOWN ------------------------------

CallBot reveals: ['8d', 'Ah']. Best hand: ['8d', 'Ah', 'Ts', 'Td', '9h'] (PAIR). Hand score: 210.140908.
AggroBot reveals: ['8h', 'Jh']. Best hand: ['8h', 'Jh', 'Ts', 'Td', '9h'] (PAIR). Hand score: 210.110908.
CallBot wins 340.

Player stacks after showdown:
    CallBot: 1180
    AggroBot: 840
    FoldBot: 980

==================================== Hand 2 ====================================

AggroBot is on the button.
FoldBot is the small blind.
CallBot is the big blind.
AggroBot is under the gun.

Player stacks at the beginning of hand 2:
    CallBot: 1180
    AggroBot: 840
    FoldBot: 980

-------------------------- Hand: 2 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[AggroBot] raises bet to 40. Remaining stack: 800.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 970.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1140.
Current pot size: 90. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1140
    AggroBot: State=ACTIVE, Pot=40, Stack=800
    FoldBot: State=FOLDED, Pot=10, Stack=970

---------------------------- Hand: 2 | Street: FLOP ----------------------------

Community cards: ['2s', '8s', 'Ts']
Current pot size: 90. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1140.
Current pot size: 90. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 760.
Current pot size: 130. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1100.
Current pot size: 170. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1100
    AggroBot: State=ACTIVE, Pot=80, Stack=760
    FoldBot: State=FOLDED, Pot=10, Stack=970

---------------------------- Hand: 2 | Street: TURN ----------------------------

Community cards: ['2s', '8s', 'Ts', '3h']
Current pot size: 170. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1100.
Current pot size: 170. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 720.
Current pot size: 210. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1060.
Current pot size: 250. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1060
    AggroBot: State=ACTIVE, Pot=120, Stack=720
    FoldBot: State=FOLDED, Pot=10, Stack=970

--------------------------- Hand: 2 | Street: RIVER ----------------------------

Community cards: ['2s', '8s', 'Ts', '3h', '2c']
Current pot size: 250. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1060.
Current pot size: 250. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 680.
Current pot size: 290. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1020.
Current pot size: 330. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1020
    AggroBot: State=ACTIVE, Pot=160, Stack=680
    FoldBot: State=FOLDED, Pot=10, Stack=970

------------------------------ Hand: 2 | SHOWDOWN ------------------------------

CallBot reveals: ['Js', '7c']. Best hand: ['Js', '2s', '8s', 'Ts', '2c'] (PAIR). Hand score: 202.111008.
AggroBot reveals: ['Td', '2h']. Best hand: ['Td', '2h', '2s', 'Ts', '2c'] (FULL_HOUSE). Hand score: 702.1.
AggroBot wins 330.

Player stacks after showdown:
    CallBot: 1020
    AggroBot: 1010
    FoldBot: 970

==================================== Hand 3 ====================================

FoldBot is on the button.
CallBot is the small blind.
AggroBot is the big blind.
FoldBot is under the gun.

Player stacks at the beginning of hand 3:
    CallBot: 1020
    AggroBot: 1010
    FoldBot: 970

-------------------------- Hand: 3 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[FoldBot] folds. Remaining stack: 970.
Current pot size: 30. Current bet: 20. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1000.
Current pot size: 40. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 970.
Current pot size: 60. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 980.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=980
    AggroBot: State=ACTIVE, Pot=40, Stack=970
    FoldBot: State=FOLDED, Pot=0, Stack=970

---------------------------- Hand: 3 | Street: FLOP ----------------------------

Community cards: ['2d', 'Ts', 'Tc']
Current pot size: 80. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 980.
Current pot size: 80. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 930.
Current pot size: 120. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 940.
Current pot size: 160. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=940
    AggroBot: State=ACTIVE, Pot=80, Stack=930
    FoldBot: State=FOLDED, Pot=0, Stack=970

---------------------------- Hand: 3 | Street: TURN ----------------------------

Community cards: ['2d', 'Ts', 'Tc', 'Td']
Current pot size: 160. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 940.
Current pot size: 160. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 890.
Current pot size: 200. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 900.
Current pot size: 240. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=900
    AggroBot: State=ACTIVE, Pot=120, Stack=890
    FoldBot: State=FOLDED, Pot=0, Stack=970

--------------------------- Hand: 3 | Street: RIVER ----------------------------

Community cards: ['2d', 'Ts', 'Tc', 'Td', '8s']
Current pot size: 240. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 900.
Current pot size: 240. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 850.
Current pot size: 280. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 860.
Current pot size: 320. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=860
    AggroBot: State=ACTIVE, Pot=160, Stack=850
    FoldBot: State=FOLDED, Pot=0, Stack=970

------------------------------ Hand: 3 | SHOWDOWN ------------------------------

CallBot reveals: ['3h', 'Js']. Best hand: ['Js', 'Ts', 'Tc', 'Td', '8s'] (THREE_OF_A_KIND). Hand score: 410.1108.
AggroBot reveals: ['9d', '4h']. Best hand: ['9d', 'Ts', 'Tc', 'Td', '8s'] (THREE_OF_A_KIND). Hand score: 410.0908.
CallBot wins 320.

Player stacks after showdown:
    CallBot: 1180
    AggroBot: 850
    FoldBot: 970

==================================== Hand 4 ====================================

CallBot is on the button.
AggroBot is the small blind.
FoldBot is the big blind.
CallBot is under the gun.

Player stacks at the beginning of hand 4:
    CallBot: 1180
    AggroBot: 850
    FoldBot: 970

-------------------------- Hand: 4 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[CallBot] calls. Remaining stack: 1160.
Current pot size: 50. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 810.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 950.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1140.
Current pot size: 100. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1140
    AggroBot: State=ACTIVE, Pot=40, Stack=810
    FoldBot: State=FOLDED, Pot=20, Stack=950

---------------------------- Hand: 4 | Street: FLOP ----------------------------

Community cards: ['9h', '6c', 'Ad']
Current pot size: 100. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 770.
Current pot size: 140. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1100.
Current pot size: 180. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1100
    AggroBot: State=ACTIVE, Pot=80, Stack=770
    FoldBot: State=FOLDED, Pot=20, Stack=950

---------------------------- Hand: 4 | Street: TURN ----------------------------

Community cards: ['9h', '6c', 'Ad', '5h']
Current pot size: 180. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 730.
Current pot size: 220. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1060.
Current pot size: 260. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1060
    AggroBot: State=ACTIVE, Pot=120, Stack=730
    FoldBot: State=FOLDED, Pot=20, Stack=950

--------------------------- Hand: 4 | Street: RIVER ----------------------------

Community cards: ['9h', '6c', 'Ad', '5h', '8d']
Current pot size: 260. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 690.
Current pot size: 300. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1020.
Current pot size: 340. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1020
    AggroBot: State=ACTIVE, Pot=160, Stack=690
    FoldBot: State=FOLDED, Pot=20, Stack=950

------------------------------ Hand: 4 | SHOWDOWN ------------------------------

CallBot reveals: ['5c', 'Ah']. Best hand: ['5c', 'Ah', '9h', 'Ad', '5h'] (TWO_PAIR). Hand score: 314.0509.
AggroBot reveals: ['6h', 'Qs']. Best hand: ['6h', 'Qs', '9h', '6c', 'Ad'] (PAIR). Hand score: 206.141209.
CallBot wins 340.

Player stacks after showdown:
    CallBot: 1360
    AggroBot: 690
    FoldBot: 950

==================================== Hand 5 ====================================

AggroBot is on the button.
FoldBot is the small blind.
CallBot is the big blind.
AggroBot is under the gun.

Player stacks at the beginning of hand 5:
    CallBot: 1360
    AggroBot: 690
    FoldBot: 950

-------------------------- Hand: 5 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[AggroBot] raises bet to 40. Remaining stack: 650.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 940.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1320.
Current pot size: 90. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1320
    AggroBot: State=ACTIVE, Pot=40, Stack=650
    FoldBot: State=FOLDED, Pot=10, Stack=940

---------------------------- Hand: 5 | Street: FLOP ----------------------------

Community cards: ['4s', 'Td', '7s']
Current pot size: 90. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1320.
Current pot size: 90. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 610.
Current pot size: 130. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1280.
Current pot size: 170. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1280
    AggroBot: State=ACTIVE, Pot=80, Stack=610
    FoldBot: State=FOLDED, Pot=10, Stack=940

---------------------------- Hand: 5 | Street: TURN ----------------------------

Community cards: ['4s', 'Td', '7s', 'Kh']
Current pot size: 170. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1280.
Current pot size: 170. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 570.
Current pot size: 210. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1240.
Current pot size: 250. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1240
    AggroBot: State=ACTIVE, Pot=120, Stack=570
    FoldBot: State=FOLDED, Pot=10, Stack=940

--------------------------- Hand: 5 | Street: RIVER ----------------------------

Community cards: ['4s', 'Td', '7s', 'Kh', '6d']
Current pot size: 250. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1240.
Current pot size: 250. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 530.
Current pot size: 290. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1200.
Current pot size: 330. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1200
    AggroBot: State=ACTIVE, Pot=160, Stack=530
    FoldBot: State=FOLDED, Pot=10, Stack=940

------------------------------ Hand: 5 | SHOWDOWN ------------------------------

CallBot reveals: ['Ah', 'As']. Best hand: ['Ah', 'As', 'Td', '7s', 'Kh'] (PAIR). Hand score: 214.131007.
AggroBot reveals: ['7c', '9s']. Best hand: ['7c', '9s', 'Td', '7s', 'Kh'] (PAIR). Hand score: 207.131009.
CallBot wins 330.

Player stacks after showdown:
    CallBot: 1530
    AggroBot: 530
    FoldBot: 940

==================================== Hand 6 ====================================

FoldBot is on the button.
CallBot is the small blind.
AggroBot is the big blind.
FoldBot is under the gun.

Player stacks at the beginning of hand 6:
    CallBot: 1530
    AggroBot: 530
    FoldBot: 940

-------------------------- Hand: 6 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[FoldBot] folds. Remaining stack: 940.
Current pot size: 30. Current bet: 20. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1510.
Current pot size: 40. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 490.
Current pot size: 60. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1490.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1490
    AggroBot: State=ACTIVE, Pot=40, Stack=490
    FoldBot: State=FOLDED, Pot=0, Stack=940

---------------------------- Hand: 6 | Street: FLOP ----------------------------

Community cards: ['8d', '5s', '3c']
Current pot size: 80. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1490.
Current pot size: 80. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 450.
Current pot size: 120. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1450.
Current pot size: 160. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1450
    AggroBot: State=ACTIVE, Pot=80, Stack=450
    FoldBot: State=FOLDED, Pot=0, Stack=940

---------------------------- Hand: 6 | Street: TURN ----------------------------

Community cards: ['8d', '5s', '3c', '3s']
Current pot size: 160. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1450.
Current pot size: 160. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 410.
Current pot size: 200. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1410.
Current pot size: 240. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1410
    AggroBot: State=ACTIVE, Pot=120, Stack=410
    FoldBot: State=FOLDED, Pot=0, Stack=940

--------------------------- Hand: 6 | Street: RIVER ----------------------------

Community cards: ['8d', '5s', '3c', '3s', '8s']
Current pot size: 240. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1410.
Current pot size: 240. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 370.
Current pot size: 280. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1370.
Current pot size: 320. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1370
    AggroBot: State=ACTIVE, Pot=160, Stack=370
    FoldBot: State=FOLDED, Pot=0, Stack=940

------------------------------ Hand: 6 | SHOWDOWN ------------------------------

CallBot reveals: ['Jd', 'Qs']. Best hand: ['Qs', '8d', '3c', '3s', '8s'] (TWO_PAIR). Hand score: 308.03119999999996.
AggroBot reveals: ['2d', '6d']. Best hand: ['6d', '8d', '3c', '3s', '8s'] (TWO_PAIR). Hand score: 308.0306.
CallBot wins 320.

Player stacks after showdown:
    CallBot: 1690
    AggroBot: 370
    FoldBot: 940

==================================== Hand 7 ====================================

CallBot is on the button.
AggroBot is the small blind.
FoldBot is the big blind.
CallBot is under the gun.

Player stacks at the beginning of hand 7:
    CallBot: 1690
    AggroBot: 370
    FoldBot: 940

-------------------------- Hand: 7 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[CallBot] calls. Remaining stack: 1670.
Current pot size: 50. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 330.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 920.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1650.
Current pot size: 100. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1650
    AggroBot: State=ACTIVE, Pot=40, Stack=330
    FoldBot: State=FOLDED, Pot=20, Stack=920

---------------------------- Hand: 7 | Street: FLOP ----------------------------

Community cards: ['Ks', '3c', 'Qh']
Current pot size: 100. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 290.
Current pot size: 140. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1610.
Current pot size: 180. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1610
    AggroBot: State=ACTIVE, Pot=80, Stack=290
    FoldBot: State=FOLDED, Pot=20, Stack=920

---------------------------- Hand: 7 | Street: TURN ----------------------------

Community cards: ['Ks', '3c', 'Qh', '7d']
Current pot size: 180. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 250.
Current pot size: 220. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1570.
Current pot size: 260. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1570
    AggroBot: State=ACTIVE, Pot=120, Stack=250
    FoldBot: State=FOLDED, Pot=20, Stack=920

--------------------------- Hand: 7 | Street: RIVER ----------------------------

Community cards: ['Ks', '3c', 'Qh', '7d', '3s']
Current pot size: 260. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 210.
Current pot size: 300. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1530.
Current pot size: 340. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1530
    AggroBot: State=ACTIVE, Pot=160, Stack=210
    FoldBot: State=FOLDED, Pot=20, Stack=920

------------------------------ Hand: 7 | SHOWDOWN ------------------------------

CallBot reveals: ['9s', '4s']. Best hand: ['9s', 'Ks', '3c', 'Qh', '3s'] (PAIR). Hand score: 203.131209.
AggroBot reveals: ['8c', 'Th']. Best hand: ['Th', 'Ks', '3c', 'Qh', '3s'] (PAIR). Hand score: 203.13121.
AggroBot wins 340.

Player stacks after showdown:
    CallBot: 1530
    AggroBot: 550
    FoldBot: 920

==================================== Hand 8 ====================================

AggroBot is on the button.
FoldBot is the small blind.
CallBot is the big blind.
AggroBot is under the gun.

Player stacks at the beginning of hand 8:
    CallBot: 1530
    AggroBot: 550
    FoldBot: 920

-------------------------- Hand: 8 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[AggroBot] raises bet to 40. Remaining stack: 510.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 910.
Current pot size: 70. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1490.
Current pot size: 90. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1490
    AggroBot: State=ACTIVE, Pot=40, Stack=510
    FoldBot: State=FOLDED, Pot=10, Stack=910

---------------------------- Hand: 8 | Street: FLOP ----------------------------

Community cards: ['6s', '5c', '9d']
Current pot size: 90. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1490.
Current pot size: 90. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 470.
Current pot size: 130. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1450.
Current pot size: 170. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1450
    AggroBot: State=ACTIVE, Pot=80, Stack=470
    FoldBot: State=FOLDED, Pot=10, Stack=910

---------------------------- Hand: 8 | Street: TURN ----------------------------

Community cards: ['6s', '5c', '9d', 'Js']
Current pot size: 170. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1450.
Current pot size: 170. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 430.
Current pot size: 210. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1410.
Current pot size: 250. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1410
    AggroBot: State=ACTIVE, Pot=120, Stack=430
    FoldBot: State=FOLDED, Pot=10, Stack=910

--------------------------- Hand: 8 | Street: RIVER ----------------------------

Community cards: ['6s', '5c', '9d', 'Js', '4c']
Current pot size: 250. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1410.
Current pot size: 250. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 390.
Current pot size: 290. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1370.
Current pot size: 330. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1370
    AggroBot: State=ACTIVE, Pot=160, Stack=390
    FoldBot: State=FOLDED, Pot=10, Stack=910

------------------------------ Hand: 8 | SHOWDOWN ------------------------------

CallBot reveals: ['3d', 'Ad']. Best hand: ['Ad', '6s', '5c', '9d', 'Js'] (HIGH_CARD). Hand score: 100.1411090605.
AggroBot reveals: ['As', 'Qd']. Best hand: ['As', 'Qd', '6s', '9d', 'Js'] (HIGH_CARD). Hand score: 100.1412110906.
AggroBot wins 330.

Player stacks after showdown:
    CallBot: 1370
    AggroBot: 720
    FoldBot: 910

==================================== Hand 9 ====================================

FoldBot is on the button.
CallBot is the small blind.
AggroBot is the big blind.
FoldBot is under the gun.

Player stacks at the beginning of hand 9:
    CallBot: 1370
    AggroBot: 720
    FoldBot: 910

-------------------------- Hand: 9 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[FoldBot] folds. Remaining stack: 910.
Current pot size: 30. Current bet: 20. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1350.
Current pot size: 40. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 680.
Current pot size: 60. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1330.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1330
    AggroBot: State=ACTIVE, Pot=40, Stack=680
    FoldBot: State=FOLDED, Pot=0, Stack=910

---------------------------- Hand: 9 | Street: FLOP ----------------------------

Community cards: ['9d', '7s', 'Qd']
Current pot size: 80. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1330.
Current pot size: 80. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 640.
Current pot size: 120. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1290.
Current pot size: 160. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1290
    AggroBot: State=ACTIVE, Pot=80, Stack=640
    FoldBot: State=FOLDED, Pot=0, Stack=910

---------------------------- Hand: 9 | Street: TURN ----------------------------

Community cards: ['9d', '7s', 'Qd', 'Kh']
Current pot size: 160. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1290.
Current pot size: 160. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 600.
Current pot size: 200. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1250.
Current pot size: 240. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1250
    AggroBot: State=ACTIVE, Pot=120, Stack=600
    FoldBot: State=FOLDED, Pot=0, Stack=910

--------------------------- Hand: 9 | Street: RIVER ----------------------------

Community cards: ['9d', '7s', 'Qd', 'Kh', 'Qs']
Current pot size: 240. Current bet: 0. Minimum raise size: 0.

[CallBot] checks. Remaining stack: 1250.
Current pot size: 240. Current bet: 0. Minimum raise size: 0.
[AggroBot] bets 40. Remaining stack: 560.
Current pot size: 280. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1210.
Current pot size: 320. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1210
    AggroBot: State=ACTIVE, Pot=160, Stack=560
    FoldBot: State=FOLDED, Pot=0, Stack=910

------------------------------ Hand: 9 | SHOWDOWN ------------------------------

CallBot reveals: ['5c', '4s']. Best hand: ['9d', '7s', 'Qd', 'Kh', 'Qs'] (PAIR). Hand score: 212.130907.
AggroBot reveals: ['6h', '3h']. Best hand: ['9d', '7s', 'Qd', 'Kh', 'Qs'] (PAIR). Hand score: 212.130907.
CallBot wins 160.
AggroBot wins 160.

Player stacks after showdown:
    CallBot: 1370
    AggroBot: 720
    FoldBot: 910

=================================== Hand 10 ====================================

CallBot is on the button.
AggroBot is the small blind.
FoldBot is the big blind.
CallBot is under the gun.

Player stacks at the beginning of hand 10:
    CallBot: 1370
    AggroBot: 720
    FoldBot: 910

------------------------- Hand: 10 | Street: PRE_FLOP --------------------------

Community cards: None
Current pot size: 30. Current bet: 20. Minimum raise size: 20.

[CallBot] calls. Remaining stack: 1350.
Current pot size: 50. Current bet: 20. Minimum raise size: 20.
[AggroBot] raises bet to 40. Remaining stack: 680.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[FoldBot] folds. Remaining stack: 890.
Current pot size: 80. Current bet: 40. Minimum raise size: 20.
[CallBot] calls. Remaining stack: 1330.
Current pot size: 100. Current bet: 40. Minimum raise size: 20.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=40, Stack=1330
    AggroBot: State=ACTIVE, Pot=40, Stack=680
    FoldBot: State=FOLDED, Pot=20, Stack=890

--------------------------- Hand: 10 | Street: FLOP ----------------------------

Community cards: ['Kd', 'Ks', 'Ts']
Current pot size: 100. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 640.
Current pot size: 140. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1290.
Current pot size: 180. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=80, Stack=1290
    AggroBot: State=ACTIVE, Pot=80, Stack=640
    FoldBot: State=FOLDED, Pot=20, Stack=890

--------------------------- Hand: 10 | Street: TURN ----------------------------

Community cards: ['Kd', 'Ks', 'Ts', '8c']
Current pot size: 180. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 600.
Current pot size: 220. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1250.
Current pot size: 260. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=120, Stack=1250
    AggroBot: State=ACTIVE, Pot=120, Stack=600
    FoldBot: State=FOLDED, Pot=20, Stack=890

--------------------------- Hand: 10 | Street: RIVER ---------------------------

Community cards: ['Kd', 'Ks', 'Ts', '8c', '4d']
Current pot size: 260. Current bet: 0. Minimum raise size: 0.

[AggroBot] bets 40. Remaining stack: 560.
Current pot size: 300. Current bet: 40. Minimum raise size: 40.
[CallBot] calls. Remaining stack: 1210.
Current pot size: 340. Current bet: 40. Minimum raise size: 40.

Player standings after the betting round:
    CallBot: State=ACTIVE, Pot=160, Stack=1210
    AggroBot: State=ACTIVE, Pot=160, Stack=560
    FoldBot: State=FOLDED, Pot=20, Stack=890

----------------------------- Hand: 10 | SHOWDOWN ------------------------------

CallBot reveals: ['6d', '7c']. Best hand: ['7c', 'Kd', 'Ks', 'Ts', '8c'] (PAIR). Hand score: 213.100807.
AggroBot reveals: ['Ad', '8h']. Best hand: ['Ad', '8h', 'Kd', 'Ks', '8c'] (TWO_PAIR). Hand score: 313.0814.
AggroBot wins 340.

Player stacks after showdown:
    CallBot: 1210
    AggroBot: 900
    FoldBot: 890
import pandas as pd
from maverick import Street, GameEventType, GameStage

df = pd.DataFrame(event_listener.dump)
df["street"] = pd.to_numeric(df["street"], errors="coerce").astype("UInt8")
df["stage"] = pd.to_numeric(df["stage"], errors="coerce").astype("UInt8")
df["hand_number"] = pd.to_numeric(df["hand_number"], errors="coerce").astype("UInt8")
df.rename(
    columns={
        "type": "event_type_value",
        "stage": "stage_value",
        "street": "street_value",
        "ts": "event_ts",
        "id": "event_id",
    },
    inplace=True,
)

# Map enum values to names for better readability
street_map = {s.value: s.name for s in Street}
df["street_name"] = df["street_value"].map(street_map)

# Map enum values to names for better readability
event_type_map = {e.value: e.name for e in GameEventType}
df["event_type_name"] = df["event_type_value"].map(event_type_map)

# Map enum values to names for better readability
game_stage_map = {s.value: s.name for s in GameStage}
df["game_stage_name"] = df["stage_value"].map(game_stage_map)

# Add a unique game_uid to group events from the same game together
df["game_uid"] = game.game_uid

df.head()
event_id event_ts event_type_value hand_number street_value stage_value player_id action payload street_name event_type_name game_stage_name game_uid
0 252042d5feb944ad8332d1a5992e57fa 1.773855e+09 1 0 <NA> 3 None None {} NaN GAME_STARTED STARTED 363de64bf62249e0835986da381d2faa
1 a46038fd42164886a7fb276afeea1e50 1.773855e+09 3 1 0 4 None None {} PRE_FLOP HAND_STARTED DEALING 363de64bf62249e0835986da381d2faa
2 8d9624b42a574164a62901dd6f1550a0 1.773855e+09 7 1 0 4 None None {} PRE_FLOP HOLE_CARDS_DEALT DEALING 363de64bf62249e0835986da381d2faa
3 a2418587377749bbbc95a32b9eaae538 1.773855e+09 17 1 0 4 None None {} PRE_FLOP BLINDS_POSTED DEALING 363de64bf62249e0835986da381d2faa
4 e1acbbed0ed146f0845238a32ca6f778 1.773855e+09 18 1 0 4 None None {} PRE_FLOP ANTES_POSTED DEALING 363de64bf62249e0835986da381d2faa