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