Configuring and Running Games#
Configuring Games#
Setting up a custom poker game consists of providing rules for dealing, stakes and showdown evaluation through classes listed in this section of the API Reference.
By default, when you set up a simple game like
from maverick import Game
game = Game(small_blind=10, big_blind=20, max_hands=2)
the library sets up a No-Limit Texas Hold’em game and is equivalent to the more detailed configuration
from maverick.rules import PokerRules, DealingRules, StakesRules, ShowdownRules
rules = PokerRules(
name="NLHE",
dealing=DealingRules(
hole_cards=2,
),
stakes=StakesRules(
small_blind=10,
big_blind=20,
ante=0,
),
showdown=ShowdownRules(
hole_cards_required=0,
),
)
game = Game(rules=rules, max_hands=40)
game.rules == Game(small_blind=10, big_blind=20, max_hands=2).rules
True
This would be a configuration of a No-Limit Omaha game:
rules = PokerRules(
name="Omaha",
dealing=DealingRules(
hole_cards=4,
),
stakes=StakesRules(
small_blind=10,
big_blind=20,
ante=0,
),
showdown=ShowdownRules(
hole_cards_required=2,
),
)
game = Game(rules=rules, max_hands=40)
The PokerRules class is also a subclass of PyDantic’s BaseModel class.
game.rules.model_dump()
{'name': 'Omaha',
'dealing': {'max_players': 9,
'min_players': 2,
'hole_cards': 4,
'board_cards_total': 5,
'board_deal_pattern': {<Street.PRE_FLOP: 0>: 0,
<Street.FLOP: 1>: 3,
<Street.TURN: 2>: 1,
<Street.RIVER: 3>: 1}},
'stakes': {'small_blind': 10, 'big_blind': 20, 'ante': 0},
'showdown': {'hole_cards_required': 2},
'rules_version': '1.0'}
Limitations#
You can only set up No-Limit games.
There is only one type of ante behaviour, which is that all players place the ante amount. This is not the only type of ante used in poker events such as WSOP, but this is the most common and the only one implemented in the library.
Running Games#
At this point you should be familiar with calling the start method of an instance of Game and you know that it drains the event queue. If you want more control, you can also play a game one event at a time using step-by-step execution.
Step-by-Step Execution of a Game#
from maverick import (
PlayerLike,
PlayerState,
GameEventType
)
from maverick.players import FoldBot, CallBot, AggressiveBot
import logging
# Configure logging such that we only get the log messages of the game
logging.basicConfig(level=logging.INFO, format="%(name)s: %(message)s", force=True)
logging.getLogger().setLevel(logging.WARNING)
logging.getLogger("maverick").setLevel(logging.INFO)
# Create game with blinds
game = Game(small_blind=10, big_blind=20, max_hands=40)
# Create and add players with different strategies
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)
game._initialize_game()
game._event_queue.append(GameEventType.GAME_STARTED)
print("Game initialized and GAME_STARTED event queued")
print(f"Has pending events: {game.has_events()}")
maverick: Player CallBot joined the game.
maverick: Player AggroBot joined the game.
maverick: Player FoldBot joined the game.
Game initialized and GAME_STARTED event queued
Has pending events: True
You can see the pending events:
game._event_queue
deque([<GameEventType.GAME_STARTED: 1>])
Consume one event:
_ = game.step()
maverick: ============================== Hand 1 ==============================
print(f"Has pending events: {game.has_events()}")
Has pending events: True
game._event_queue
deque([<GameEventType.HAND_STARTED: 3>])
Looking at the game state, you can tell that the deck is already initialized, but the players don’t have their private cards (the holding) yet.
game.state.model_dump()
{'stage': <GameStage.STARTED: 3>,
'street': <Street.PRE_FLOP: 0>,
'players': [{'uid': '0379fbd05f4d43059520e30cb13f1284',
'name': 'CallBot',
'state': {'seat': 0,
'state_type': <PlayerStateType.ACTIVE: 1>,
'stack': 1000,
'holding': None,
'current_bet': 0,
'total_contributed': 0,
'acted_this_street': False},
'__class__.__name__': 'CallBot'},
{'uid': '65481e67b24c4e4abece8df8f9ea476a',
'name': 'AggroBot',
'state': {'seat': 1,
'state_type': <PlayerStateType.ACTIVE: 1>,
'stack': 1000,
'holding': None,
'current_bet': 0,
'total_contributed': 0,
'acted_this_street': False},
'__class__.__name__': 'AggressiveBot'},
{'uid': 'fa7a246d5e9e4794b89db375c0cc5b76',
'name': 'FoldBot',
'state': {'seat': 2,
'state_type': <PlayerStateType.ACTIVE: 1>,
'stack': 1000,
'holding': None,
'current_bet': 0,
'total_contributed': 0,
'acted_this_street': False},
'__class__.__name__': 'FoldBot'}],
'current_player_index': None,
'deck': {'cards': [{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.KING: 13>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.QUEEN: 12>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.TEN: 10>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.SEVEN: 7>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.THREE: 3>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.TEN: 10>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.SIX: 6>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.TWO: 2>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.ACE: 14>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.QUEEN: 12>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.KING: 13>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.TWO: 2>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.ACE: 14>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.FOUR: 4>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.TEN: 10>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.TWO: 2>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.THREE: 3>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.KING: 13>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.SIX: 6>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.FOUR: 4>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.SIX: 6>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.EIGHT: 8>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.SIX: 6>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.JACK: 11>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.JACK: 11>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.ACE: 14>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.NINE: 9>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.EIGHT: 8>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.EIGHT: 8>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.QUEEN: 12>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.TWO: 2>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.THREE: 3>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.SEVEN: 7>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.SEVEN: 7>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.THREE: 3>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.NINE: 9>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.TEN: 10>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.FIVE: 5>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.SEVEN: 7>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.NINE: 9>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.QUEEN: 12>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.NINE: 9>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.FIVE: 5>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.JACK: 11>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.EIGHT: 8>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.KING: 13>},
{'suit': <Suit.HEARTS: 'H'>, 'rank': <Rank.FOUR: 4>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.FOUR: 4>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.JACK: 11>},
{'suit': <Suit.CLUBS: 'C'>, 'rank': <Rank.FIVE: 5>},
{'suit': <Suit.DIAMONDS: 'D'>, 'rank': <Rank.ACE: 14>},
{'suit': <Suit.SPADES: 'S'>, 'rank': <Rank.FIVE: 5>}]},
'community_cards': [],
'pot': 0,
'current_bet': 0,
'min_bet': 0,
'last_raise_size': 0,
'small_blind': 10,
'big_blind': 20,
'ante': 0,
'hand_number': 1,
'button_position': 0,
'small_blind_position': 1,
'big_blind_position': 2}
Game UID#
Every time a game is started, a unique identifier is generated and exposed through the read-only game_uid property. This allows you to track and distinguish individual game sessions, which is especially useful when a single Game instance is reused across multiple sessions.
Before start() is called, game_uid is None. A new identifier is generated each time the GAME_STARTED event is processed.
from maverick import Game
from maverick.players import FoldBot
from maverick import PlayerState
game = Game(small_blind=10, big_blind=20, max_hands=1, log_events=False)
print(f"game_uid before start: {game.game_uid}") # None
game.add_player(FoldBot(name="Alice", state=PlayerState(stack=500)))
game.add_player(FoldBot(name="Bob", state=PlayerState(stack=500)))
game.start()
print(f"game_uid after start: {game.game_uid}") # 32-character hex UUID
game_uid before start: None
game_uid after start: 3178c474f7f742f6814c63ca22483634