Source code for maverick.state
from typing import Optional, Any
import warnings
from pydantic import BaseModel, Field, field_serializer, field_validator
from .card import Card
from .deck import Deck
from .enums import (
GameStage,
PlayerStateType,
Street,
)
from .protocol import PlayerLike
from ._registered_players import registered_players
__all__ = ["GameState"]
[docs]
class GameState(BaseModel):
"""
Represents the complete state of a Texas Hold'em game.
This class encapsulates all information about the current state of the game,
including players, community cards, pot, and betting information.
Fields
------
stage : GameStage
The current state of the game (e.g., WAITING_FOR_PLAYERS, IN_PROGRESS).
.. versionadded:: 0.2.0
street : Optional[Street]
The current betting round (e.g., PRE_FLOP, FLOP, TURN, RIVER).
players : list[PlayerLike]
The list of players in the game.
current_player_index : Optional[int]
The index of the player whose turn it is to act.
deck : Optional[Deck]
The deck of cards used in the game.
community_cards : list[Card]
The community cards on the table.
pot : int
The total amount of chips in the pot.
current_bet : int
The current highest bet that players need to match.
min_bet : int
The minimum bet amount for the current betting round.
last_raise_size : int
The size of the last raise made in the current betting round.
small_blind : int
The amount of the small blind.
big_blind : int
The amount of the big blind.
ante : int
The ante amount for the game.
hand_number : int
The current hand number in the game.
button_position : Optional[int]
The position (seat index) of the dealer button at the table.
small_blind_position : Optional[int]
The position (seat index) of the small blind at the table.
.. versionadded:: 0.3.0
big_blind_position : Optional[int]
The position (seat index) of the big blind at the table.
.. versionadded:: 0.3.0
"""
stage: GameStage = GameStage.WAITING_FOR_PLAYERS
street: Optional[Street] = None
players: list[PlayerLike] = Field(default_factory=list)
current_player_index: Optional[int] = None
# Cards
deck: Optional[Deck] = None
community_cards: list[Card] = Field(default_factory=list)
# Betting
pot: int = 0
current_bet: int = 0
min_bet: int = 0
last_raise_size: int = 0 # Tracks the minimum raise increment (last bet/raise size)
small_blind: int = Field(default=10, ge=1)
big_blind: int = Field(default=20, ge=1)
ante: int = Field(default=0, ge=0)
# Hand tracking
hand_number: int = 0
button_position: Optional[int] = None
small_blind_position: Optional[int] = None
big_blind_position: Optional[int] = None
model_config = {
"arbitrary_types_allowed": True,
}
@property
def state_type(self) -> GameStage:
"""Get the current game state type.
.. deprecated:: 0.2.0
Use `stage` attribute instead.
"""
warnings.warn(
"'state_type' is deprecated and will be removed in v1.0.0; use stage 'instead'.",
DeprecationWarning,
stacklevel=2,
)
return self.stage
@field_serializer("players")
def _ser_players(self, players: list[PlayerLike]) -> list[dict]:
result = []
for p in players:
d = p.to_dict()
d["__class__.__name__"] = p.__class__.__name__
result.append(d)
return result
@field_validator("players", mode="before")
@classmethod
def _parse_players(cls, players: list[Any]) -> list[PlayerLike]:
result = []
for p in players:
if isinstance(p, PlayerLike):
# accept already-built objects
result.append(p)
else:
# re-instantiate from dict
if not "__class__.__name__" in p:
raise KeyError(
"Serialized player data missing '__class__.__name__' key"
)
cls = registered_players[p["__class__.__name__"]]
if not cls:
raise ValueError(
f"Unrecognized player class '{p['__class__.__name__']}'"
)
result.append(cls(**p))
return result
[docs]
def get_active_players(self) -> list[PlayerLike]:
"""Return list of players who haven't folded and have chips."""
return [
p
for p in self.players
if p.state.state_type == PlayerStateType.ACTIVE and p.state.stack > 0
]
[docs]
def get_players_in_hand(self) -> list[PlayerLike]:
"""Return list of players still in the hand (not folded)."""
return [
p
for p in self.players
if p.state.state_type in [PlayerStateType.ACTIVE, PlayerStateType.ALL_IN]
]
[docs]
def is_betting_round_complete(self) -> bool:
"""Betting round is complete when no further action is possible/required."""
in_hand = self.get_players_in_hand()
# If only one player remains, hand is effectively over
if len(in_hand) <= 1:
return True
can_act = self.get_active_players()
# If less than 2 players can act, betting is complete
if len(can_act) < 2:
return True
# Everyone who can act must have acted since the last reopen
if not all(p.state.acted_this_street for p in can_act):
return False
# Everyone who can act must have matched the current bet
if not all(p.state.current_bet == self.current_bet for p in can_act):
return False
return True