Game Simulation
PokerKit provides tools for poker game simulations. Its customizability allows users to define and utilize almost every poker variant that exists.
Each poker variant often introduces unique game rules and hand types not seen in other variants. The versatility of PokerKit allows for the customization of poker variants, allowing users to define their own unique games, adapt an existing variant, or implement a variant not currently supported by PokerKit out of the box. This flexibility is achieved without compromising the robustness of the implementation, backed by extensive unit tests and doctests to ensure error-free operations. Naturally, common variants are already defined out of the box, so, for most use cases, users will not have to define their own variants.
PokerKit stands out with its ability to cater to an assortment of use cases, offering varying degrees of control over the game state. For instance, in use cases for poker AI agent development, where the agents’ focus lies primarily in action selection during betting rounds, minute details such as showdown order, posting blinds, posting antes, and bet collection may not be pertinent. On the other hand, an online poker casino requires granular control over each game aspect. These include dealing hole cards one by one, burning cards, deciding to muck or show during the showdown (even when unnecessary), killing hands after the showdown, pushing chips to the winner’s stack, and even the winner collecting the chips into their stack. Depending on the use case, PokerKit provides users with varying levels of automation tailored to their specific needs.
Initialization
There are two ways the user can initialize a poker state to simulate them. The first method involves using pre-defined games from which the user just needs to specify the initial chip configurations. The second method involves defining every parameter such that all the various aspects of poker games such as the variant and the initial chip configurations.
Pre-Defined Games
PokerKit offers virtually unlimited poker variants to be played. However, defining poker variants can be quite an overwhelming task for a new user due the sheer number and complexities of the state parameters. Therefore, PokerKit offers pre-defined poker variants where the user can just supply a more intuitive subset of arguments such as antes, blinds, starting stacks, et cetera, which are as follows:
Variant |
Class |
|---|---|
Fixed-limit badugi |
|
Fixed-limit deuce-to-seven lowball triple draw |
|
Fixed-limit Omaha hold’em hi-low split-eight or better low |
|
Fixed-limit razz |
|
Fixed-limit seven card stud |
|
Fixed-limit seven card stud hi-low split-eight or better low |
|
Fixed-limit Texas hold’em |
|
No-limit deuce-to-seven single draw |
|
No-limit royal hold’em |
|
No-limit short-deck hold’em |
|
No-limit Texas hold’em |
|
Pot-limit Omaha hold’em |
|
Kuhn poker |
|
Rhode Island hold’em |
|
Royal Rhode Island hold’em |
These pre-defined games can be created as shown below:
from pokerkit import (
Automation,
FixedLimitDeuceToSevenLowballTripleDraw,
NoLimitTexasHoldem,
)
state = FixedLimitDeuceToSevenLowballTripleDraw.create_state(
# automations
(
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
),
True, # False for big blind ante, True otherwise
0, # ante
(75000, 150000), # blinds or straddles
150000, # small bet
300000, # big bet
(1180000, 4340000, 5910000, 10765000), # starting stacks
4, # number of players
)
state = NoLimitTexasHoldem.create_state(
# automations
(
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
),
True, # False for big blind ante, True otherwise
500, # ante
(1000, 2000), # blinds or straddles
2000, # min bet
(1125600, 2000000, 553500), # starting stacks
3, # number of players
)
In certain use cases, one may want to create a template from which just the starting stacks and the number of players can be specified. In PokerKit, this can be done by creating an instance of the game that acts as a state factory from which states are initialized. The code below, doing exactly that, is equivalent to the previous code.
from pokerkit import (
Automation,
FixedLimitDeuceToSevenLowballTripleDraw,
NoLimitTexasHoldem,
)
game = FixedLimitDeuceToSevenLowballTripleDraw(
# automations
(
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
),
True, # False for big blind ante, True otherwise
0, # ante
(75000, 150000), # blinds or straddles
150000, # small bet
300000, # big bet
)
state = game(
(1180000, 4340000, 5910000, 10765000), # starting stacks
4, # number of players
)
game = NoLimitTexasHoldem(
# automations
(
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
),
True, # False for big blind ante, True otherwise
500, # ante
(1000, 2000), # blinds or straddles
2000, # min bet
)
state = game(
(1125600, 2000000, 553500), # starting stacks
3, # number of players
)
The exact parameters that must be specified differ depending on the variant being played. Button games accept antes and blinds/straddles. Stud games accept bring-ins. Fixed-limit games accept small/big bets while pot/no-limit games accept a min-bet. For a specific list of parameters, the API references should be consulted.
Why Specify the Number of Players?
The ways the chip values (e.g., antes, blinds/straddles, and starting stacks) can be supplied in PokerKit are quite liberal. For instance, the statements below are identical.
state = game(
(200, 200, 200, 200), # starting stacks
4, # number of players
)
state = game(
[200, 200, 200, 200], # starting stacks
4, # number of players
)
state = game(
{0: 200, 1: 200, 2: 200, 3: 200}, # starting stacks
4, # number of players
)
state = game(
200, # starting stacks
4, # number of players
)
In the fourth statement, PokerKit interprets a single numeric value for the starting stacks parameter as saying identical values for all players. Since just a single number does not say anything about the number of players like a list, tuple, or dict (sometimes), the number of players must also be supplied.
Defining States from Scratch
If you want to create a state of a variant not listed above, you will have to define a custom variant. A variant is composed of the following definitions:
Definition |
Description |
|
Deck |
Deck used in the game. Please note that most variants use a 52-card deck. |
|
Hand Types |
Hand types. Most variants use one, but split-pot games use two. No poker variants use three or more. |
|
Streets |
Card Burning Status |
Whether or not to burn a card, should be |
Hole Dealings |
Whether or not and how to deal hole cards (face-up/down), should be an empty tuple for none. |
|
Board Dealings |
How many board cards to deal, if at all, should be zero for none. |
|
Is Draw Stage |
Whether it is a draw round, should be |
|
Opening |
How to decide who acts first. Typically, in button games, it’s by position. In stud games, lowest/highest up card or hand. |
|
Min Completion/Bet/Raise |
Minimum completion/bet/raise size. |
|
Max # of Completion/Bet/Raise |
Maximum number of completions/bets/raises, should be |
|
Betting Structure |
Betting limits such as no-limit, pot-limit, or fixed-limit. |
|
In addition to the above parameters that define the variant, more parameters are required to be specified to initialize a state.
Ante Trimming Status |
Simply put, |
Antes |
Antes, should be zero for none. |
Blinds/Straddles |
Forced bets like blinds/straddles, should be zero(s) for stud games. |
Bring-in |
Bring-in, should be zero for non-stud games. |
Starting Stacks |
Starting stacks. |
Number of Players |
Number of players. |
For the full list of parameters, please see the API references of pokerkit.state.State.
The below definition shows a definition of a Kuhn poker state:
from pokerkit import (
Automation,
BettingStructure,
Deck,
KuhnPokerHand,
Opening,
State,
Street,
)
state = State(
# automations
(
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_DEALING,
Automation.BOARD_DEALING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
),
Deck.KUHN_POKER, # deck
(KuhnPokerHand,), # hand types (high/low-split will have two types)
# streets
(
Street(
False, # card burning
(False,), # hole card dealing statuses (False for face-down)
0, # board dealing count
False, # standing pat or discarding
Opening.POSITION, # who opens the betting?
1, # min bet
1, # maximum number of completions/bettings/raisings
),
),
BettingStructure.FIXED_LIMIT, # betting structure
True, # ``False`` for big blind ante, otherwise ``True``
(1,) * 2, # ante
(0,) * 2, # blind or straddles
0, # bring-in
(2,) * 2, # starting stacks
2, # number of players
)
If you would like to see other variants pre-defined, please create an issue in the project’s repository or contact the library authors.
What Is Ante Trimming Status?
To unambiguously define the ante configuration, this value is necessary. It means the following: say players ended up paying different ante amounts at the start of the game; if the player who put less ante wins, is the player entitled to win the full antes of others that put more? This means that this parameter is only relevant when different ante amounts is paid by the players. This situation arises in two different situations: when non-uniform antes like BB or BTN ante (used in tournaments or 6+ poker) are used or when a player has a very low starting stack that falls below the ante amount. If the answer to this question is yes, False must be supplied, or, if no, the supplied value must be True.
Simply put, if non-uniform antes are used, False must be supplied no matter what. If uniform antes are used, the programmer has a choice, but the value supplied should most likely be True unless the programmer believes that the player who is short-stacked to the point of not being able to post the full ante can win the full antes of others as opposed to just a portion of it.
Position
Note that PokerKit does not consider “seat numbers” but instead “positions” that are relevant to the gameplay. For non-heads-up button games, this means that the zeroth player will always be the small blind, the first player will always be the big blind, the second player will always be UTG, and so on until the last player who will always be in the button and in position. The notion of position does not really exist in stud games. In this case, the player to the immediate left of the dealer should be in the zeroth position while the player to the immediate right of the dealer should be in the last position.
Note that, in this ordering, the player in the n’th position is the n’th person to be dealt the hole cards at the beginning of the hand.
Automation
The PokerKit state allows the state to be modified in a really fine-grained way, down to posting antes, blinds, straddles, burning cards before dealing, dealing hole cards, dealing board cards, standing pat, discarding, folding, checking, calling, posting bring-ins, completing, betting, raising, showing hole cards, mucking, dealer killing losing hands, collecting bets, pushing chips to the winners, and winners putting the chips they won back into their stack.
This fine-grained state transition is necessary for use in online casinos. However, depending on the use cases, many of these operations are completely irrelevant and can be automatically handled without any user input, as users can specify which operations they care about and therefore be manual, as opposed to being automated.
For example, if you are trying to create a poker AI, you are not worried about having the option of mucking the best hand, purposely showing the worst hand, burning a card, pushing the chips to the winners, collecting chips a player won, collecting bets after each street, et cetera. But, you want to handle user actions like fold, check, call, bring-in, complete, bet, and raise. Also, you might want to control what cards are dealt to each player and to the board. The below automations will suit the aforesaid use cases.
from pokerkit import Automation
# Automate everything except player actions.
# Manual:
# - Standing pat
# - Discarding
# - Folding
# - Checking
# - Calling
# - Posting bring-in
# - Completing
# - Betting
# - Raising
# - Selecting the number of runouts (cash-game only)
automations = (
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_DEALING,
Automation.BOARD_DEALING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
)
# Automate everything except player actions and dealings.
# Manual:
# - Player:
# - Standing pat
# - Discarding
# - Folding
# - Checking
# - Calling
# - Posting bring-in
# - Completing
# - Betting
# - Raising
# - Selecting the number of runouts (cash-game only)
# - Dealer:
# - Deal hole cards
# - Deal board cards
automations = (
Automation.ANTE_POSTING,
Automation.BET_COLLECTION,
Automation.BLIND_OR_STRADDLE_POSTING,
Automation.CARD_BURNING,
Automation.HOLE_CARDS_SHOWING_OR_MUCKING,
Automation.HAND_KILLING,
Automation.CHIPS_PUSHING,
Automation.CHIPS_PULLING,
)
In cash-games only, if you are not interested in letting players select the number of runouts after going all-in, you can add the following entry to force it to a single runout.
automations = (
...,
Automation.RUNOUT_COUNT_SELECTION, # Cash-game only
...,
)
If you are trying to create an online poker room, you need to represent all these fine changes to create a smooth user experience. In such a case, nothing must be automated, as below.
from pokerkit import Automation
# Automate nothing.
# Manual:
# - Player:
# - Ante posting
# - Blind/straddle posting
# - Standing pat
# - Discarding
# - Folding
# - Checking
# - Calling
# - Posting bring-in
# - Completing
# - Betting
# - Raising
# - Selecting the number of runouts (cash-game only)
# - Showdown (show/muck)
# - Incorporating won chips into the stack
# - Dealer:
# - Bet collection
# - Card burning
# - Deal hole cards
# - Deal board cards
# - Killing losing hands
# - Distributing chips
automations = ()
Even if you are trying to use this for video poker and you are happy with PokerKit dealing cards at random (hopefully), you should still pass an empty automation as this parameter. This is because the dealing of each card is still a change in the state and therefore you might want to represent the state changing as each card is dealt.
# Automate nothing (control even what cards are dealt)
automations = ()
pokerkit.state.Automation describes a complete list of operations that can be automated.
Deck
When you supply a deck to the state, the state automatically shuffles it so the cards can be dealt at random when required. pokerkit.utilities.Deck describes a complete list of decks. Decks are simply tuples of cards and you can define your own as needed.
Most poker games use a standard 52-card deck, accessible as pokerkit.utilities.Deck.STANDARD or pokerkit.utilities.Deck.REGULAR. Both are composed of 52 cards and have no difference in content. The only difference between the two is that they are sorted differently. The standard deck uses the ace-high rank ordering while the regular deck has ace-low rank ordering. Obviously, after shuffling, there is no real difference. Simply choose whatever you prefer. In pre-defined games within PokerKit, regular decks are only used for variants that always consider aces to low.
Hand Types
Hand types denote how hands are evaluated in a game setting. Most games like Texas hold ‘em or short-deck use just one hand type, but some games like high-low split games use two hand types. Technically, you can define more than two in PokerKit, but no mainstream variant uses more than two.
from pokerkit import *
# Texas hold'em, et cetera
hand_types = (StandardHighHand,)
# Omaha hold'em Hi-lo split 8-or-better
hand_types = OmahaHoldemHand, OmahaEightOrBetterLowHand
Hand types are defined in pokerkit.hands.
Streets
Streets describe each betting round and the dealing(s) before it. When you define a street, internal checks are carried out to make sure the definition is sound. Examples of streets for selections of variants are shown below:
from pokerkit import *
# No-limit Texas hold'em
streets = (
Street(
card_burning_status=False,
hole_dealing_statuses=(False, False),
board_dealing_count=0,
draw_status=False,
opening=Opening.POSITION,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=None,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(),
board_dealing_count=3,
draw_status=False,
opening=Opening.POSITION,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=None,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(),
board_dealing_count=1,
draw_status=False,
opening=Opening.POSITION,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=None,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(),
board_dealing_count=1,
draw_status=False,
opening=Opening.POSITION,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=None,
),
)
# Fixed-limit razz
streets = (
Street(
card_burning_status=False,
hole_dealing_statuses=(False, False, True),
board_dealing_count=0,
draw_status=False,
opening=Opening.HIGH_CARD,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=4,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(True,),
board_dealing_count=0,
draw_status=False,
opening=Opening.LOW_HAND,
min_completion_betting_or_raising_amount=2,
max_completion_betting_or_raising_count=4,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(True,),
board_dealing_count=0,
draw_status=False,
opening=Opening.LOW_HAND,
min_completion_betting_or_raising_amount=4,
max_completion_betting_or_raising_count=4,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(True,),
board_dealing_count=0,
draw_status=False,
opening=Opening.LOW_HAND,
min_completion_betting_or_raising_amount=4,
max_completion_betting_or_raising_count=4,
),
Street(
card_burning_status=True,
hole_dealing_statuses=(False,),
board_dealing_count=0,
draw_status=False,
opening=Opening.LOW_HAND,
min_completion_betting_or_raising_amount=4,
max_completion_betting_or_raising_count=4,
),
)
Card Burning
You might want to burn cards before any cards are dealt such as when dealing flops, turns, or rivers in Texas hold’em, or before dealing hole cards after drawing in draw games (True if this is so).
Hole Card Dealing Statuses
Most poker variants deal cards face down, but this is not the case for stud games. In seven card stud, cards are dealt “down down up”, “up”, “up”, “up”, and, finally, “down” (up is True and down is False). This parameter allows the user to specify how to deal with hole cards (an empty tuple if none). Of course, this means the length of this argument is equal to the number of hole cards dealt for that street to each active player.
Board Dealing Count
This parameter denotes how many board cards are dealt (0 if none). For multi-board games like some bomb pots, this number of cards is dealt for each board.
Standing Pat or Discarding Status
This parameter denotes whether the players can discard hole cards before betting as done in draw games (True if this is so). If this is set, then one must not specify any hole-dealings since a draw stage is always followed by card replenishing phase (identical to hole dealing in some aspects).
Opening
This parameter specifies how to choose the first player to act. The complete list of openings is shown in pokerkit.state.Opening.
All button games without exception have openers that are decided by position. The position takes account of blinds or straddles, if any. It happens to be that all button games do not use bring-ins.
Games that do have bring-ins, such as stud games, have openers that are either decided by the face-up card (first betting round) or the strength of the open hand (subsequent betting rounds). Depending on whether the game is a low game or not, the low or high card/hand is used to pick the opener. Suits are only used to break ties when comparing cards, not hands. When some hands are tied, the player with the lowest player index opens the pot as per the 2023 WSOP Tournament Rules.
In some regions, the suits are also used to break ties for hands (which rarely happens). PokerKit does not support this approach to the opener selection.
Minimum Completion, Betting, or Raising Amount
Simply put, this value denotes the min-bet in no-limit games (typically the big blind) or small/big bets in limit games (typically the big blind or double it, respectively). It should be a positive value.
Maximum Completion, Betting, or Raising Count
This value denotes how many times a bet/raise can be made. In no-limit games, this value is unlimited, for which the user can supply None. Otherwise, if the number of bets/raises is limited, as for typical fixed-limit games, an integral value must be supplied. In the 2023 WSOP Tournament Rules, this value is typically 4, which is the value we use for pre-defined fixed-limit games.
When tournaments reach heads-up play, the 2023 WSOP Tournament Rules states that unlimited bets/raises are possible. Our pre-defined games do not follow this exception. If you want this behavior, you must define your own variant. As a side note, the game played in Michael Bowling’s Science paper “Heads-up Limit Hold’em Poker Is Solved” uses the rule of 4 bet/raises max.
Betting Structure
The betting structure denotes whether a game is fixed-limit, pot-limit, or no-limit. The complete list of possible values is shown in pokerkit.state.BettingStructure.
Ante Trimming Status
Simply put, it should be False for non-uniform antes and True for uniform antes. See the previous section on this parameter on why it is necessary to specify this separately from the ante amounts.
“Raw” Antes
This parameter states the antes. Just like it is for starting stacks, as explained in the previous section, PokerKit is quite intelligent when interpreting this value. If you just put in a single value like 2.00, all players will be anted exactly 2.00. If you put in [0, 2] or {1: 2}, it will be interpreted as a big-blind ante. Similarly, {-1: 2} is the button ante. This parameter is “raw” in that it must be cleaned by PokerKit.
“Raw” Blinds or Straddles
This parameter states the blinds or straddles. It is “raw” in that it must be cleaned by PokerKit just like “raw” antes. Standard small and big blinds can be supplied as [0.5, 1]. With a UTG straddle, it would be [0.5, 1, 2]. With double straddles, [0.5, 1, 2, 4]. With a button straddle, {0: 0.5, 1: 1, -1: 2}. If the small and big blinds are equal, then it would be [2, 2]. The possibilities are endless. If the game does not use blinds or straddles, the user must supply 0 or its equivalent (e.g., [0, 0, 0, 0]), meaning no player is blinded or straddled.
Bring-In
Some games use bring-ins. If this is supplied, it must be a positive value like 1.5. Otherwise, simply supply 0.
The usage of blinds/straddles and bring-ins is mutually exclusive. In other words, no variant uses both at the same time. If one is used, the other must be zeroed out, meaning it is not relevant.
“Raw” Starting Stacks
This parameter states the starting stacks. Again, the values are interpreted intelligently by PokerKit. If a user’s starting stack is unknown, his/her starting stack can be set to be math.inf.
Player Count
This parameter simply states the number of players.
Optional: Mode
This is an optional parameter. It denotes what ruleset to use (tournament/cash-game).
There are two modes available to be set: the tournament and cash-game mode. Tournaments use a stricter rule-set than typical cash-games. For more details, please consult pokerkit.state.Mode.
Optional: Starting Board Count
This is an optional parameter. It denotes how many boards to be dealt.
For most poker games, it should be 1. Of course, for double board games, it should be 2. Triple/Quadruple/etc. board games are almost unheard of. Therefore, this value should mostly be 1 or sometimes 2.
You may wonder why the word “starting” was used here. This is because we expand on the concept of board. When multiple runouts are selected, the number of boards are multiplied by the number of runouts. As such, it may be different from the selected number of boards at the start of the hand.
Optional: Divmod
This is an optional parameter. It is a callable that divides up a pot among the winners who are entitled to win the pot. By default, PokerKit divides up the pot differently depending on the numeric type being used (integral (int, etc.) or real (float, etc.)). If PokerKit deems that the values in the poker state are integral, the pot is divided evenly using floor division (//). The remainder (akin to odd-chips) is given to the player most out of position. If PokerKit deems that the values in the poker state are real, the pot is divided up using true division (/) among the winners. To be safe, if you want to always handle the chip values as integers, make sure all numerical values supplied to PokerKit states are integral. Obviously, if you want to handle all the chip values as real values, supply them as floats, fractions, et cetera.
The user may want to use dollar values with two decimal places. PokerKit is designed to automatically handle as such if Python’s built-in decimal type (and potentially more) values are supplied as chip amounts.
The programmer can also define a custom function that divides up the pot to tailor it to specific use cases. For the function signature, check our default pokerkit.utilities.divmod() function.
Optional: Rake
This is an optional parameter. It is a callable that takes a rake from the pot(s). By default, PokerKit states take no rake. The default pokerkit.utilities.rake() function can accept parameters to take a non-zero rake, have a rake-cap, or facilitate the no-flop-no-drop rule. You can use functools.partial with it and supply it as the rake function. Of course, the user can define their own to do something more complex like min-rake or max-rake per pot depending on the poker state.
Methods and Attributes
The simulation mechanism in PokerKit is architected around the concept of states, encapsulating all the vital information about the current game through its attributes. Below are more generic items that are not associated with any particular phase/operation. The ones that are introduced later when each individual phase/operation is discussed.
Helper Method/Attribute |
Description |
|---|---|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
(Defined during initialization and described above in this page) |
|
Cleaned ante amounts. |
|
Cleaned blind/straddle/post-bet amounts. If a value is a post bet, it must be negated (i.e., |
|
Cleaned starting stack chip amounts. |
|
Shuffled deck from which cards are drawn. |
|
Community cards. |
|
Cards that were mucked during folds or mucks during the showdown. |
|
Cards that were burnt before dealing operations. |
|
Whether or not a player at the corresponding position is still in the hand (i.e., active). |
|
Outstanding bets. |
|
Stack sizes. |
|
Payoffs (i.e., rewards). |
|
Private cards. |
|
Whether each private card is an up card or a down card. |
|
Cards that were discarded for each player. |
|
Index denoting which street is currently active. |
|
At what street to return to when multiple runouts are enabled. |
|
Number of times to return to a specific street to perform multiple runouts. |
|
All active players are all-in. |
|
Everyone except one folded. |
|
Whether or not the hand is active (ongoing). |
|
History of operations carried out so far. |
|
Number of hand types. |
|
Indices of hand types. |
|
Boolean values for each stage whether or not it is a drawing stage. |
|
Indices of players. |
|
Number of streets. |
|
Indices of streets. |
|
Current street. |
|
The index of the player who is in turn to act (draw, betting street, showdown). This attribute exists to combine the three following attributes: |
|
Number of boards (maybe more than |
|
Indices of boards. |
|
|
The n’th board. |
Return the hole cards of a player, but censor the down cards (i.e., convert to an unknown card |
|
Down cards of a player. |
|
Up cards of a player. |
|
Evaluated hand of a player for a hand type. |
|
Evaluated open/up hand of a player for a hand type. |
|
Evaluated open/up hand of a player for all hand types. |
|
Whether or not a player may be able to win a portion of the pots (i.e., no shown hand can prevent the player from winning a portion of the pot(s)). |
|
Cards that are tapped into when replenishing an emptied deck. |
|
Cards that are in play (i.e., visible to at least one active player). |
|
Cards that are not in play (i.e., not visible to any active player). |
|
Cards that are “recommended” to be dealt from (handles deck replenishing if the deck is/will be out of cards). |
|
Effective stack of a player. |
|
The amounts (unraked) of each main (and side) pot(s). |
|
The total pot amount (unraked). |
|
All main (and side) pot(s). Each pot contains the amount (raked or unraked) and the players entitled to win it. |
Transitions
PokerKit structures the game flow into distinct phases, each supporting a different set of operations (dealing, betting, collecting bets, showing hands, et cetera). The phase transition diagram is shown below.
Phases
Depending on the game state, each phase may be skipped. For instance, if the user has specified no antes, the ante posting phase will be omitted. Likewise, if no bets were placed during the betting phase, the bet collection phase will be bypassed. A phase transition occurs upon the completion of a phase. This transition is internally managed by the game framework, facilitating a seamless game flow to the end user. During each phase of PokerKit’s game simulation, the user can invoke various methods to execute operations. Each operation belongs to a specific phase and can only be enacted when the corresponding phase is active.
Phase |
Description |
|---|---|
Ante Posting |
During the ante-posting phase, each player has the option to execute an ante-posting operation. The parameters supplied to the state during its creation may dictate no antes, uniform antes, or non-uniform antes, such as big blind antes. If no player is due to post an ante, this phase is bypassed. |
Bet Collection |
The collection of bets on the table occurs after any phase that allows players to bet. If any bet is present, the bet collection operation must be performed before proceeding to the subsequent phase. This phase only occurs after ante-posting or betting. When no bets are pending collection, this phase is skipped. |
Blind or Straddle Posting |
Forced bets like blinds or straddles must be posted before the start of the first street. PokerKit accommodates a variety of blind or straddle configurations, ranging from small and big blinds, to button blinds or even no blind at all. If the state is configured to exclude any forced bets, this phase is skipped. |
Dealing |
The dealing phase precedes the betting phase. During this phase, the user can deal with board or hole cards, contingent upon the state’s configuration. Options to burn a card or discard and draw cards are also available when applicable. This phase is bypassed if only one player remains in the hand. |
Betting |
During betting, players can execute actions such as folding, checking, calling, posting a bring-in, completing, betting, or raising. During state creation, the user must specify how to select the first player to act and the betting limits. This phase is bypassed if all players are all-in or if only one player remains in the hand. |
Showdown |
During the showdown, players reveal or muck their hands in accordance with the showdown order. The first to show is typically the last aggressor in the final street. If no one bet, the player who was the first to act in the final betting round must show first. Players can opt to show a losing hand or muck a winning hand, even though this is often disadvantageous. When dealing with all-in pots, players are obligated to show their hands in order to prevent chip-dumping. If this is the case, or if only one player remains in the pot, the showdown phase is bypassed. Runout-count selection are also performed here (only for cash-games). |
Hand Killing |
The dealer is responsible for “killing,” or discarding, hands that cannot win any portion of the pot. If no hand should be killed, this phase is bypassed. |
Chips Pushing |
The dealer is charged with pushing the chips to the winners. In button games, the pot size is always non-zero due to the mandatory presence of antes, forced bets, or bring-ins (as enforced by PokerKit). Thus, this phase is always carried out in button games. This might not be the case in non-button games like stud games without antes where everyone folds after the opener brings in or completes. |
Chips Pulling |
Players may incorporate the chips they’ve won back into their stack. In poker, at least one player is guaranteed to win the pot. Consequently, this phase is never skipped. |
Note that, depending on the number of betting rounds, the Dealing, Betting, and Bet Collection phases may be repeated.
Operations
The operation that mutates the states in PokerKit is each associated with a phase and returns information about what happened as the return value. Below are all the operations supported by PokerKit.
Phase |
Operation |
Method |
Return Class |
Ante Posting |
Ante Posting |
||
Bet Collection |
Bet Collection |
||
Blinds/Straddle Posting |
Blinds/Straddle Posting |
||
Dealing |
Card Burning |
||
Hole Dealing |
|||
Board Dealing |
|||
Standing Pat/Discarding |
|||
Betting |
Folding |
||
Checking/Calling |
|||
Bring in Posting |
|||
Completing/Betting/Raising to |
|||
Showdown |
Runout-Count Selection |
||
Hole Cards Showing/Mucking |
|||
Hand Killing |
Hand Killing |
||
Chips Pushing |
Chips Pushing |
||
Chips Pulling |
Chips Pulling |
||
Automation
Depending on your use case, many of these operations will not be of concern and can be automated.
Operation |
Automation |
Ante Posting |
|
Bet Collection |
|
Blinds/Straddle Posting |
|
Card Burning |
|
Hole Dealing |
|
Board Dealing |
|
Standing Pat/Discarding |
N/A |
Folding |
N/A |
Checking/Calling |
N/A |
Bring in Posting |
N/A |
Completing/Betting/Raising to |
N/A |
Runout-Count Selection |
|
Hole Cards Showing/Mucking |
|
Hand Killing |
|
Chips Pushing |
|
Chips Pulling |
|
Method Triplets
Each operation is coupled with two other associated methods: a verification method and an action query, forming a triplet per operation.
Verifier: The verification method validates if a move can be executed within the rules, considering the current game state and the variant in play. It raises an error with an error message or issues a warning if any violation of the game rule is detected.
Querier: Users can use a corresponding action query method (with optional arguments), which simply checks if the verification method triggers an error and returns a Boolean value indicating the validity of the action. These methods return
Trueif you can perform such an operation (with specified arguments, if any) orFalseif otherwise.Operator: The method that actually performs the operation initially runs the verification method, executing the operation only if no errors are raised. If the verification fails, the state remains unchanged. After an operation, descriptions of what was carried out like the player(s) involved, the amount (if relevant), what cards were involved (if any), how many chips (if applicable), et cetera are returned.
Warnings are shown when an operation does something suspicious. Warnings are not errors and therefore ignored by the query method. The user can configure Python to treat UserWarning as an error, as below:
from warnings import filterwarnings
filterwarnings('error')
Should this be the case, PokerKit’s action query methods will return False as it does for errors when warnings are issued.
PokerKit’s philosophy is that it should focus on maintaining the game state and enforcing rules. During verification or operation, error handling is left to the user, who may need to handle errors differently depending on the application. All the errors raised are ValueError (or UserWarning if configured as such).
Pseudocodes of these are shown below:
def verify_operation(self, ...):
...
if is_inoperable:
raise ValueError("...")
...
def can_operate(self, ...):
try:
self.verify_operation(...)
except ValueError:
return False
return True
def operate(self, ...):
self.verify_operation(...)
...
The table below shows the verifiers and queries for each operation.
Operation |
Verifier |
Querier |
|---|---|---|
Ante posting |
||
Bet collection |
||
Blind/straddle posting |
||
Card burning |
||
Hole dealing |
||
Board dealing |
||
Standing pat/discarding |
||
Folding |
||
Checking/calling |
||
Bring-in posting |
||
Completion/betting/raising to |
|
|
Runout-count selection |
||
Hole cards showing/mucking |
||
Hand killing |
||
Chips pushing |
||
Chips pulling |
Ante Posting Phase/Operation
The ante posting operation (pokerkit.state.State.post_ante()) posts a player ante. Each call corresponds to exactly one player posting an ante. Therefore, to progress from the ante posting phase, this operation must be invoked multiple times until the players due to post ante are anted.
The ante posting method accepts one positional argument, player_index, which defaults to the player with the lowest index who has not yet posted an ante.
Helper Method/Attribute |
Description |
|---|---|
List of Booleans on whether the player at that position is due to post an ante. |
|
Ante amount to be/that has been posted by each player (reduced from the original ante if the player does not have enough chips). |
|
Players pending ante posting, sorted by their position. |
The effective ante amount is almost always the full ante amount for that position unless the player is severely short-stacked below the ante amount.
Bet Collection Phase/Operation
The bet collection operation (pokerkit.state.State.collect_bets()) collects the outstanding bets and incorporates them into the pot. This method can only be called if there are outstanding bets. If the betting round finished without anyone posting the bet, this method cannot be called and this phase will be automatically skipped. This method accepts no positional argument.
Helper Method/Attribute |
Description |
|---|---|
|
Blind/Straddle Posting Phase/Operation
The blind/straddle posting operation (pokerkit.state.State.post_blind_or_straddle()) posts the forced bet of a player. Each call corresponds to exactly one player posting a forced bet. Therefore, to progress from the ante-posting phase, this operation must be invoked multiple times until the players due to post blind/straddle are posted.
The blind/straddle posting method accepts one argument, player_index which defaults to the player with the lowest index who has not yet posted a forced bet.
Helper Method/Attribute |
Description |
|---|---|
List of Booleans on whether the player at that position is due to post a blind or straddle. |
|
Blind or straddle amount to be/that has been posted by each player (reduced from the original forced bet if the player does not have enough chips). |
|
Players pending blind/straddle posting, sorted by their position. |
The effective blind/straddle amount is almost always the full blind/straddle amount for that position unless the player is severely short-stacked below the said amount.
Dealing Phase
In this phase, cards are moved from one place to another. There are a few places where cards can be in. All cards are initially in the shuffled deck. As operations are carried out, they are moved from one place to another. The cards that are drawn usually come from the deck. But, there are cases in certain variants where one may run out of cards. If this happens, the cards in the muck, burn, and the discards are combined, shuffled, and incorporated into the back of the deck. In stud games, even this incorporation might not be enough to cover the hole cards of all players. In this case, the hole dealings are replaced with board dealings to be shared by all players, as per the 2023 WSOP Tournament Rules.
Helper Method/Attribute |
Description |
|---|---|
Whether or not there is a pending card burn. |
|
Pending hole card dealings (up/down) for each player. |
|
Numbers of cards that are pending to be dealt to the board. |
|
Boolean values on whether the player should stand pat/discard. |
|
Next player being dealt the hole cards by the dealer. |
|
Next player standing pat or discarding. |
Card Burning Operation
The card burning operation (pokerkit.state.State.burn_card()) burns a single card. It can be called only once per dealing phase. This is only called if the corresponding street attribute configures as such. The method for this operation accepts an optional card to be burned as a positional argument. If this is not supplied, the card that is burnt is the card at the front of the shuffled deck. This method can be automated. But, note that, if you automate this method and specify what card to be dealt as hole/board cards, you may later encounter warnings when you attempt to deal cards that are already burnt. As such, if you intend to specify what cards to deal with, you should not automate this and explicitly burn "??" denoting an unknown card.
Hole Dealing Operation
This operation (pokerkit.state.State.deal_hole()) concerns itself with the private card dealings. Each invocation deals with one player only, and this player is automatically selected based on position. The caller can optionally supply the cards to be dealt to the dealee as a positional argument (which may be unknown like "????".
If the cards are not provided, they are drawn from the shuffled deck. The number of cards drawn from the deck (and therefore dealt) per invocation varies depending on the situation. In a draw round, the number of cards drawn per call equals the number of discarded cards for the player being dealt. In other words, each method call completely replenishes a player’s hole. If the state is not in a draw round, each method call draws one card only. In this context, the method must be called “N” times the number of active players where “N” is the number of hole cards to be dealt for each person until the hole dealings are complete for that phase.
Board Dealing Operation
This operation (pokerkit.state.State.deal_board()) concerns itself with the community card dealings. The caller can optionally supply the cards to be dealt to the board as a positional argument. If the cards are not provided, they are drawn from the shuffled deck. The number of cards drawn from the deck (and therefore dealt) per invocation equals the number of cards pending to be dealt (3 for flop or 1 for turn and river in flop/turn/river games).
Standing Pat/Discarding Operation
This operation (pokerkit.state.State.stand_pat_or_discard()) corresponds to a player standing pat or discarding. If the player decides to discard some cards, the method call must specify what cards are being discarded as a positional argument. If the cards are not specified, the method interprets the method call as standing pat.
If at least one card was discarded by a player, the player must later be dealt the corresponding number of hole cards to replenish the hand by the dealer in the same dealing phase.
Specifying Cards
Just like in the case of chips, PokerKit supports different ways cards can be described. A single card is considered as a singleton iterable containing the same card. An iterable of cards is considered, exactly as that. String representations of cards are also accepted and are parsed, for more information about the string card representations, see the next page on hand evaluations.
Betting Phase
In this phase, the players have a chance to put chips into the pot.
Helper Method/Attribute |
Description |
|---|---|
The person who opened the betting round or completed/bet/raised the last. |
|
Whether a bring-in posting is pending. If it is, the next actor cannot fold or check but instead must either bring-in or complete. |
|
Whether a player faces a bring-in and hence a completion is pending. |
|
List of actors in order. The zeroth player index is the current actor. |
|
Last completion, betting, or raising (difference between final and previous raise) amount, reset after each betting phase. |
|
Number of completions, bettings, or raisings in the betting phase. |
|
Current actor. |
|
Calling amount. 0 if checking. |
|
Bring-in amount to be posted. Potentially less than the defined value if the opener has insufficient chips. |
|
|
Minimum completion/betting/raising to amount. |
|
Pot completion/bet/raising to amount. |
|
Maximum completion/betting/raising to amount. |
Folding Operation
This operation (pokerkit.state.State.fold()) corresponds to a folding action. It accepts no positional argument. The actor is automatically kept track of by the state. Non-standard folds (i.e., even when there are no outstanding bets or raises) are only allowed in a cash-game mode (typically forbidden in tournaments).
Checking/Calling Operation
This operation (pokerkit.state.State.check_or_call()) corresponds to a checking/calling action. It accepts no positional argument. The actor is automatically kept track of by the state.
Bringing-in Operation
This operation (pokerkit.state.State.post_bring_in()) corresponds to posting a bring-in. It accepts no positional argument. The actor is automatically kept track of by the state (person with the lowest/highest up card).
Completion/Betting/Raising to
This operation (pokerkit.state.State.complete_bet_or_raise_to()) corresponds to completions, bets, or raises. It accepts one optional positional argument: the chip amount. If left unspecified, the state interprets it as a completion, min-bet, or min-raise. The actor is automatically kept track of by the state (person with the lowest/highest up card).
Showdown Phase
In this phase, the players may show or muck their hole cards in an attempt to win the pot. This phase is not performed when all but one player is folded. When there are multiple players in showdown, the winner must show their entire hand to win. Also, the number of runouts can be selected by each player (only for cash-games).
Helper Method/Attribute |
Description |
|---|---|
Whether a player is pending runout count selection or not. |
|
Concensus on the number of runouts. |
|
Whether the runout selection has been completed. |
|
The player indices of those who can select the number of runouts. |
|
Showdown order. |
|
Current actor pending showdown. |
Runout-Count Selection Operation
This operation (pokerkit.state.State.select_runout_count()) corresponds to the selection of the number of runouts. It is enabled only when there are pending cards to be dealt to the board and the hand is in an all-in situation (no more betting action can be taken). The concensus is to be reached to decide the number of runouts. By default or if there is a disagreement, just a single runout is made. There is no set order people can select the runout-count. Some commentators say that the runout-count selection should be done prior to showing the cards, but PokerKit also allow this to be done after the showing of the cards. The number of runouts to be done (after concensus is reached) is reflected in pokerkit.state.runout_count. If nobody showed any preference, this value is kept as None.
Hole Cards Showing/Mucking Operation
This operation (pokerkit.state.State.show_or_muck_hole_cards()) corresponds to showing or mucking of one’s cards. It accepts one optional positional argument on whether to show or muck. If True, this denotes that the player is showing all his/her hole cards. If False, this denotes that the player is mucking his hand. If some card values are supplied, the caller is saying that the player is showing the supplied cards. This is useful when the hole cards previously dealt were unknown (e.g., "????"). Note that even when known cards are held by the player, the caller can override what cards are shown with this method call. If no argument is supplied, whether or not the player shows or mucks their hand is decided automatically. If they hold a hand that may win a piece of the pot as it ties with or wins over all other shown hands or is in an all-in situation, the player will opt to show. Otherwise, the player will automatically muck.
In tournament mode, no matter what happens, if shown, the shown hole cards must all be known (i.e., contains no question marks). This restriction does not apply in cash-game mode (i.e., not a tournament). For instance, a player may opt to keep their hands (or part of their hole cards) face-down even when all-in and there are community cards still left to come (this is not allowed in tournaments to prevent chip dumping). Or, when a player has a chance to show their hands, they may only want to show part of their hand (again, typically prohibited in tournaments).
In poker games, the showdown order is determined through action. The last bettor/raiser must show first. In practice, it is encouraged for people who know they won for sure to show first. This means that the “correct” showdown order may deviate from the actually practiced showdown order.
Hand Killing Phase/Operation
This operation (pokerkit.state.State.kill_hand()) corresponds to the hand killing. Typically, in in-person poker games, before pushing the chips to the winners, the dealer “kills” hands that are lost. The same holds for states in PokerKit. Hands that are not entitled to any portion of the pot(s) are mucked. Each call mucks a single hand. This operation method does not accept any argument.
Helper Method/Attribute |
Description |
|---|---|
List of Booleans on whether the hand at that position is not entitled to win any portion of the pot(s) and thus should be killed. |
|
Hands that should be killed, ordered by position. |
Chips Pushing Phase/Operation
This operation (pokerkit.state.State.push_chips()) corresponds to pushing the chips to the winner(s). Each call pushes one split pot (main/side). If there are multiple hand types (or boards) and/or multiple players with differing starting stack sizes that are all-in and are entitled to the piece of the pot, multiple split pots (main + side) will exist and therefore, this method must be invoked multiple times until all pots are pushed to the players.
Helper Method/Attribute |
Description |
|---|---|
The total amount pushed from the pot to each player. |
Chips Pulling Phase/Operation
The pushed chips in this operation (pokerkit.state.State.pull_chips()) are incorporated into the player stacks. Each method call performs this operation for a single player. If multiple players won at least one portion of the pot, this method must be called multiple times. This method accepts no argument.
Helper Method/Attribute |
Description |
|---|---|
List of Booleans on whether the player at that position is pending incorporation of the won chips into their stack. |
|
Players that can pull chips, ordered by position. |
Benchmarks
A diverse variant of games (9 variants) were played as part of the simulation benchmark on a single core of Intel Core i7-1255U with 16GB of RAM and Python 3.11.5.
Metric |
PokerKit |
|---|---|
Speed (# hands/s) |
847.31 |