From ed716f4bad022dde507d94bd57f7bc3bc0e7675e Mon Sep 17 00:00:00 2001 From: Juho Kim Date: Mon, 4 Dec 2023 01:00:51 -0500 Subject: [PATCH] Version 0.3.1 --- CHANGELOG.rst | 7 + README.rst | 2 +- docs/conf.py | 2 +- pokerkit/__init__.py | 28 +- pokerkit/games.py | 1268 +++++++++++----------- pokerkit/lookups.py | 173 +-- pokerkit/state.py | 58 +- pokerkit/tests/test_lookups.py | 2 +- pokerkit/tests/test_state.py | 2 +- pokerkit/tests/test_utilities.py | 2 +- pokerkit/tests/test_wsop/test_2023_43.py | 2 +- setup.py | 3 +- 12 files changed, 752 insertions(+), 797 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index df11c72d..3f27656f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,13 @@ Changelog All notable changes to this project will be documented in this file. +Version 0.3.1 (December 4, 2023) +------------------------------- + +**Added** + +- Allow state configuration to be saved. + Version 0.3.0 (October 7, 2023) ------------------------------- diff --git a/README.rst b/README.rst index 8666952f..da504a95 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ Research Group. PokerKit supports an extensive array of poker variants and it provides a flexible architecture for users to define their custom games. These facilities are exposed via an intuitive unified high-level programmatic API. The library can be used in a variety of use cases, from poker AI development, tool -creation, to online poker casino implementation. PokerKit’s reliability has been +creation, to online poker casino implementation. PokerKit's reliability has been established through static type checking, extensive doctests, and unit tests, achieving 99% code coverage. diff --git a/docs/conf.py b/docs/conf.py index 01814877..8bf59824 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ project = 'PokerKit' copyright = '2023, University of Toronto Computer Poker Research Group' author = 'University of Toronto Computer Poker Research Group' -release = '0.3.0' +release = '0.3.1' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pokerkit/__init__.py b/pokerkit/__init__.py index d683777d..505e3f0e 100644 --- a/pokerkit/__init__.py +++ b/pokerkit/__init__.py @@ -24,7 +24,8 @@ 'CombinationHand', 'CompletionBettingOrRaisingTo', 'Deck', - 'DeuceToSevenLowball', + 'DeuceToSevenLowballMixin', + 'Draw', 'EightOrBetterLookup', 'EightOrBetterLowHand', 'Entry', @@ -32,6 +33,7 @@ 'FixedLimitBadugi', 'FixedLimitDeuceToSevenLowballTripleDraw', 'FixedLimitOmahaHoldemHighLowSplitEightOrBetter', + 'FixedLimitPokerMixin', 'FixedLimitRazz', 'FixedLimitSevenCardStud', 'FixedLimitSevenCardStudHighLowSplitEightOrBetter', @@ -40,6 +42,7 @@ 'GreekHoldemHand', 'Hand', 'HandKilling', + 'Holdem', 'HoleBoardCombinationHand', 'HoleCardsShowingOrMucking', 'HoleDealing', @@ -50,16 +53,18 @@ 'max_or_none', 'min_or_none', 'NoLimitDeuceToSevenLowballSingleDraw', + 'NoLimitPokerMixin', 'NoLimitShortDeckHoldem', 'NoLimitTexasHoldem', 'OmahaEightOrBetterLowHand', - 'OmahaHoldem', 'OmahaHoldemHand', + 'OmahaHoldemMixin', 'Opening', 'Operation', 'Poker', 'Pot', 'PotLimitOmahaHoldem', + 'PotLimitPokerMixin', 'Rank', 'RankOrder', 'RegularLookup', @@ -68,6 +73,7 @@ 'ShortDeckHoldemHand', 'ShortDeckHoldemLookup', 'shuffled', + 'SingleDraw', 'StandardHand', 'StandardHighHand', 'StandardLookup', @@ -76,27 +82,37 @@ 'State', 'Street', 'Suit', - 'TexasHoldem', + 'TexasHoldemMixin', + 'TripleDraw', + 'UnfixedLimitHoldem', 'ValuesLike', ) from pokerkit.games import ( - DeuceToSevenLowball, + DeuceToSevenLowballMixin, + Draw, FixedLimitBadugi, FixedLimitDeuceToSevenLowballTripleDraw, FixedLimitOmahaHoldemHighLowSplitEightOrBetter, + FixedLimitPokerMixin, FixedLimitRazz, FixedLimitSevenCardStud, FixedLimitSevenCardStudHighLowSplitEightOrBetter, FixedLimitTexasHoldem, + Holdem, NoLimitDeuceToSevenLowballSingleDraw, + NoLimitPokerMixin, NoLimitShortDeckHoldem, NoLimitTexasHoldem, - OmahaHoldem, + OmahaHoldemMixin, Poker, PotLimitOmahaHoldem, + PotLimitPokerMixin, SevenCardStud, - TexasHoldem, + SingleDraw, + TexasHoldemMixin, + TripleDraw, + UnfixedLimitHoldem, ) from pokerkit.hands import ( BadugiHand, diff --git a/pokerkit/games.py b/pokerkit/games.py index cb44c61f..d69056a5 100644 --- a/pokerkit/games.py +++ b/pokerkit/games.py @@ -3,10 +3,12 @@ from __future__ import annotations from abc import ABC +from typing import ClassVar from pokerkit.hands import ( BadugiHand, EightOrBetterLowHand, + Hand, OmahaEightOrBetterLowHand, OmahaHoldemHand, RegularLowHand, @@ -15,35 +17,277 @@ StandardLowHand, ) from pokerkit.state import BettingStructure, Opening, Automation, State, Street -from pokerkit.utilities import Deck, RankOrder, ValuesLike +from pokerkit.utilities import clean_values, Deck, RankOrder, ValuesLike class Poker(ABC): - """The abstract base class for poker games.""" + """The abstract base class for poker games. + + :param automations: The automations. + :param streets: The streets. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. + :param bring_in: The bring-in. + :param player_count: The number of players. + """ + + deck: ClassVar[Deck] + """The deck.""" + hand_types: ClassVar[tuple[type[Hand], ...]] + """The hand types.""" + betting_structure: ClassVar[BettingStructure] + """The betting structure.""" + + def __init__( + self, + automations: tuple[Automation, ...], + streets: tuple[Street, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, + bring_in: int, + player_count: int, + ) -> None: + self.automations: tuple[Automation, ...] = automations + """The automations.""" + self.streets: tuple[Street, ...] = streets + """The streets.""" + self.ante_trimming_status: bool = ante_trimming_status + """The ante trimming status. + + Usually, if you want uniform antes, set this to ``True``. + If you want non-uniform antes like big blind antes, set + this to ``False``. + """ + self.antes: tuple[int, ...] = clean_values(raw_antes, player_count) + """The antes.""" + self.blinds_or_straddles: tuple[int, ...] = clean_values( + raw_blinds_or_straddles, + player_count, + ) + """The blinds or straddles.""" + self.bring_in: int = bring_in + """The bring-in.""" + self.player_count: int = player_count + """The number of players.""" + + def __call__(self, raw_starting_stacks: ValuesLike) -> State: + return State( + self.automations, + self.deck, + self.hand_types, + self.streets, + self.betting_structure, + self.ante_trimming_status, + self.antes, + self.blinds_or_straddles, + self.bring_in, + raw_starting_stacks, + self.player_count, + ) + + @property + def button_status(self) -> bool: + """Return the button status. + + :return: The button status. + """ + return any( + street.opening == Opening.POSITION for street in self.streets + ) + + @property + def max_hole_card_count(self) -> int: + """Return the maximum number of hole cards. + + :return: The maximum number of hole cards. + """ + return sum( + len(street.hole_dealing_statuses) for street in self.streets + ) + + @property + def max_down_card_count(self) -> int: + """Return the maximum number of down cards. + + :return: The maximum number of down cards. + """ + return sum( + street.hole_dealing_statuses.count( + False, + ) for street in self.streets + ) + + @property + def max_up_card_count(self) -> int: + """Return the maximum number of up cards. + + :return: The maximum number of up cards. + """ + return sum( + street.hole_dealing_statuses.count(True) for street in self.streets + ) + + @property + def max_board_card_count(self) -> int: + """Return the maximum number of board cards. + + :return: The maximum number of board cards. + """ + return sum(street.board_dealing_count for street in self.streets) + + @property + def rank_orders(self) -> tuple[RankOrder, ...]: + """Return the rank orders. + + :return: The rank orders. + """ + return tuple( + hand_type.lookup.rank_order for hand_type in self.hand_types + ) + + +class FixedLimitPokerMixin: + """The mixin for fixed-limit poker games.""" + + betting_structure: ClassVar[BettingStructure] = ( + BettingStructure.FIXED_LIMIT + ) + max_completion_betting_or_raising_count: ClassVar[int | None] = 4 + + +class PotLimitPokerMixin: + """The mixin for pot-limit poker games.""" + + betting_structure: ClassVar[BettingStructure] = BettingStructure.POT_LIMIT + max_completion_betting_or_raising_count: ClassVar[int | None] = None + + +class NoLimitPokerMixin: + """The mixin for no-limit poker games.""" + + betting_structure: ClassVar[BettingStructure] = BettingStructure.NO_LIMIT + max_completion_betting_or_raising_count: ClassVar[int | None] = None + + +class Holdem(Poker, ABC): + """The abstract base class for hold'em games. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. + :param small_bet: The small bet. + :param big_bet: The big bet. + :param player_count: The number of players. + """ + + hole_dealing_count: ClassVar[int] + """The number of hole dealings.""" + max_completion_betting_or_raising_count: ClassVar[int | None] + """The maximum number of completions, bettings, or raisings.""" + + def __init__( + self, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, + small_bet: int, + big_bet: int, + player_count: int, + ) -> None: + super().__init__( + automations, + ( + Street( + False, + (False,) * self.hole_dealing_count, + 0, + False, + Opening.POSITION, + small_bet, + self.max_completion_betting_or_raising_count, + ), + Street( + True, + (), + 3, + False, + Opening.POSITION, + small_bet, + self.max_completion_betting_or_raising_count, + ), + Street( + True, + (), + 1, + False, + Opening.POSITION, + big_bet, + self.max_completion_betting_or_raising_count, + ), + Street( + True, + (), + 1, + False, + Opening.POSITION, + big_bet, + self.max_completion_betting_or_raising_count, + ), + ), + ante_trimming_status, + raw_antes, + raw_blinds_or_straddles, + 0, + player_count, + ) + - max_down_card_count: int - """The maximum number of down cards.""" - max_up_card_count: int - """The maximum number of up cards.""" - max_board_card_count: int - """The maximum number of board cards.""" - rank_orders: tuple[RankOrder, ...] - """The rank orders.""" - button_status: bool - """The button status.""" +class UnfixedLimitHoldem(Holdem, ABC): + """The abstract base class for unfixed-limit hold'em games. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. + :param min_bet: The minimum bet. + :param player_count: The number of players. + """ + + max_completion_betting_or_raising_count: ClassVar[int | None] = None + + def __init__( + self, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, + min_bet: int, + player_count: int, + ) -> None: + super().__init__( + automations, + ante_trimming_status, + raw_antes, + raw_blinds_or_straddles, + min_bet, + min_bet, + player_count, + ) -class TexasHoldem(Poker, ABC): - """The abstract base class for Texas hold'em games.""" +class TexasHoldemMixin: + """The mixin for Texas hold'em games.""" - max_down_card_count = 2 - max_up_card_count = 0 - max_board_card_count = 5 - rank_orders = (RankOrder.STANDARD,) - button_status = True + deck: ClassVar[Deck] = Deck.STANDARD + hand_types: ClassVar[tuple[type[Hand], ...]] = (StandardHighHand,) + hole_dealing_count: ClassVar[int] = 2 -class FixedLimitTexasHoldem(TexasHoldem): +class FixedLimitTexasHoldem(FixedLimitPokerMixin, TexasHoldemMixin, Holdem): """The class for fixed-limit Texas hold'em games.""" @classmethod @@ -51,11 +295,11 @@ def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a fixed-limit Texas hold'em game. @@ -101,67 +345,32 @@ def create_state( >>> state.stacks [204, 196] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The antes. + :param raw_blinds_or_straddles: The blinds or straddles. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (StandardHighHand,), - ( - Street( - False, - (False,) * 2, - 0, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 3, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - big_bet, - 4, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - big_bet, - 4, - ), - ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + small_bet, + big_bet, player_count, - ) + )(raw_starting_stacks) -class NoLimitTexasHoldem(TexasHoldem): +class NoLimitTexasHoldem( + NoLimitPokerMixin, + TexasHoldemMixin, + UnfixedLimitHoldem, +): """The class for no-limit Texas hold'em games.""" @classmethod @@ -169,10 +378,10 @@ def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, min_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a no-limit Texas hold'em game. @@ -253,83 +462,41 @@ def create_state( >>> state.stacks [572100, 1997500, 1109500] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The antes. + :param raw_blinds_or_straddles: The blinds or straddles. :param min_bet: The min bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (StandardHighHand,), - ( - Street( - False, - (False,) * 2, - 0, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 3, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - ), - BettingStructure.NO_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + min_bet, player_count, - ) + )(raw_starting_stacks) -class NoLimitShortDeckHoldem(TexasHoldem): +class NoLimitShortDeckHoldem(NoLimitPokerMixin, UnfixedLimitHoldem): """The class for no-limit short-deck hold'em games.""" - max_down_card_count = 2 - max_up_card_count = 0 - max_board_card_count = 5 - rank_orders = (RankOrder.SHORT_DECK_HOLDEM,) - button_status = True + deck: ClassVar[Deck] = Deck.SHORT_DECK_HOLDEM + hand_types: ClassVar[tuple[type[Hand], ...]] = (ShortDeckHoldemHand,) + hole_dealing_count: ClassVar[int] = 2 @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, min_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a no-limit short-deck hold'em game. @@ -411,88 +578,50 @@ def create_state( >>> state.stacks [489000, 226000, 684000, 400000, 0, 198000] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param min_bet: The min bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.SHORT_DECK_HOLDEM, - (ShortDeckHoldemHand,), - ( - Street( - False, - (False,) * 2, - 0, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 3, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - ), - BettingStructure.NO_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + min_bet, player_count, - ) + )(raw_starting_stacks) -class OmahaHoldem(Poker, ABC): - """The abstract base class for Omaha hold'em games.""" +class OmahaHoldemMixin: + """The mixin for Omaha hold'em games.""" - max_down_card_count = 4 - max_up_card_count = 0 - max_board_card_count = 5 - button_status = True + deck: ClassVar[Deck] = Deck.STANDARD + hole_dealing_count: ClassVar[int] = 4 -class PotLimitOmahaHoldem(OmahaHoldem): +class PotLimitOmahaHoldem( + PotLimitPokerMixin, + OmahaHoldemMixin, + UnfixedLimitHoldem, +): """The class for pot-limit Omaha hold'em games.""" - rank_orders = (RankOrder.STANDARD,) + hand_types: ClassVar[tuple[type[Hand], ...]] = (OmahaHoldemHand,) @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, min_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a pot-limit Omaha hold'em game. @@ -568,433 +697,436 @@ def create_state( >>> state.stacks [193792375, 0] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param min_bet: The min bet. - :param starting_stacks: The starting stacks. - :param player_count: The number of players. - :return: The created state. - """ - return State( - automations, - Deck.STANDARD, - (OmahaHoldemHand,), - ( - Street( - False, - (False,) * 4, - 0, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 3, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - min_bet, - None, - ), - ), - BettingStructure.POT_LIMIT, - ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, - player_count, - ) - - -class FixedLimitOmahaHoldemHighLowSplitEightOrBetter(OmahaHoldem): - """The class for fixed-limit Omaha hold'em high/low-split eight or - better low games. - """ - - rank_orders = RankOrder.STANDARD, RankOrder.EIGHT_OR_BETTER_LOW - - @classmethod - def create_state( - cls, - automations: tuple[Automation, ...], - ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, - small_bet: int, - big_bet: int, - starting_stacks: ValuesLike, - player_count: int, - ) -> State: - """Create a fixed-limit Omaha hold'em high/low-split eight or better - low game. - - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. - :param small_bet: The small bet. - :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (OmahaHoldemHand, OmahaEightOrBetterLowHand), - ( - Street( - False, - (False,) * 4, - 0, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 3, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - big_bet, - 4, - ), - Street( - True, - (), - 1, - False, - Opening.POSITION, - big_bet, - 4, - ), - ), - BettingStructure.FIXED_LIMIT, - ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, - player_count, - ) - - -class SevenCardStud(Poker, ABC): - """The abstract base class for seven card stud games.""" - - max_down_card_count = 3 - max_up_card_count = 4 - max_board_card_count = 0 - button_status = False + ante_trimming_status, + raw_antes, + raw_blinds_or_straddles, + min_bet, + player_count, + )(raw_starting_stacks) -class FixedLimitSevenCardStud(SevenCardStud): - """The class for fixed-limit seven card stud games.""" +class FixedLimitOmahaHoldemHighLowSplitEightOrBetter( + PotLimitPokerMixin, + OmahaHoldemMixin, + Holdem, +): + """The class for fixed-limit Omaha hold'em high/low-split eight or + better low games. + """ - rank_orders = (RankOrder.STANDARD,) + hand_types: ClassVar[tuple[type[Hand], ...]] = ( + OmahaHoldemHand, + OmahaEightOrBetterLowHand, + ) @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - bring_in: int, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: - """Create a fixed-limit seven card stud game. + """Create a fixed-limit Omaha hold'em high/low-split eight or better + low game. - :param antes: The antes. - :param bring_in: The bring-in. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( + automations, + ante_trimming_status, + raw_antes, + raw_blinds_or_straddles, + small_bet, + big_bet, + player_count, + )(raw_starting_stacks) + + +class SevenCardStud(Poker, ABC): + """The abstract base class for seven card stud games. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param bring_in: The bring-in. + :param small_bet: The small bet. + :param big_bet: The big bet. + :param player_count: The number of players. + """ + + max_completion_betting_or_raising_count: ClassVar[int | None] + """The maximum number of completions, bettings, or raisings.""" + low: ClassVar[bool] + """The low status.""" + + def __init__( + self, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + bring_in: int, + small_bet: int, + big_bet: int, + player_count: int, + ) -> None: + super().__init__( automations, - Deck.STANDARD, - (StandardHighHand,), ( Street( False, (False, False, True), 0, False, - Opening.LOW_CARD, + Opening.HIGH_CARD if self.low else Opening.LOW_CARD, small_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( - True, (True,), + True, + (True,), 0, False, - Opening.HIGH_HAND, + Opening.LOW_HAND if self.low else Opening.HIGH_HAND, small_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( True, (True,), 0, False, - Opening.HIGH_HAND, + Opening.LOW_HAND if self.low else Opening.HIGH_HAND, big_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( True, (True,), 0, False, - Opening.HIGH_HAND, + Opening.LOW_HAND if self.low else Opening.HIGH_HAND, big_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( True, (False,), 0, False, - Opening.HIGH_HAND, + Opening.LOW_HAND if self.low else Opening.HIGH_HAND, big_bet, - 4, + self.max_completion_betting_or_raising_count, ), ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, + raw_antes, None, bring_in, - starting_stacks, player_count, ) -class FixedLimitSevenCardStudHighLowSplitEightOrBetter(SevenCardStud): +class FixedLimitSevenCardStud(FixedLimitPokerMixin, SevenCardStud): + """The class for fixed-limit seven card stud games.""" + + deck: ClassVar[Deck] = Deck.STANDARD + hand_types: ClassVar[tuple[type[Hand], ...]] = (StandardHighHand,) + low: ClassVar[bool] = False + + @classmethod + def create_state( + cls, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + bring_in: int, + small_bet: int, + big_bet: int, + raw_starting_stacks: ValuesLike, + player_count: int, + ) -> State: + """Create a fixed-limit seven card stud game. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param bring_in: The bring-in. + :param small_bet: The small bet. + :param big_bet: The big bet. + :param raw_starting_stacks: The raw starting stacks. + :param player_count: The number of players. + :return: The created state. + """ + return cls( + automations, + ante_trimming_status, + raw_antes, + bring_in, + small_bet, + big_bet, + player_count, + )(raw_starting_stacks) + + +class FixedLimitSevenCardStudHighLowSplitEightOrBetter( + FixedLimitPokerMixin, + SevenCardStud, +): """The class for fixed-limit seven card stud high/low-split eight or better low games. """ - rank_orders = RankOrder.STANDARD, RankOrder.EIGHT_OR_BETTER_LOW + deck: ClassVar[Deck] = Deck.STANDARD + hand_types: ClassVar[tuple[type[Hand], ...]] = ( + StandardHighHand, + EightOrBetterLowHand, + ) + low: ClassVar[bool] = False @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, + raw_antes: ValuesLike, bring_in: int, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a fixed-limit seven card stud high/low-split eight or better low game. - :param antes: The antes. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. :param bring_in: The bring-in. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (StandardHighHand, EightOrBetterLowHand), - ( - Street( - False, - (False, False, True), - 0, - False, - Opening.LOW_CARD, - small_bet, - 4, - ), - Street( - True, - (True,), - 0, - False, - Opening.HIGH_HAND, - small_bet, - 4, - ), - Street( - True, - (True,), - 0, - False, - Opening.HIGH_HAND, - big_bet, - 4, - ), - Street( - True, - (True,), - 0, - False, - Opening.HIGH_HAND, - big_bet, - 4, - ), - Street( - True, - (False,), - 0, - False, - Opening.HIGH_HAND, - big_bet, - 4, - ), - ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, - None, + raw_antes, bring_in, - starting_stacks, + small_bet, + big_bet, player_count, - ) + )(raw_starting_stacks) -class FixedLimitRazz(SevenCardStud): +class FixedLimitRazz(FixedLimitPokerMixin, SevenCardStud): """The class for fixed-limit razz games.""" - rank_orders = (RankOrder.REGULAR,) + deck: ClassVar[Deck] = Deck.REGULAR + hand_types: ClassVar[tuple[type[Hand], ...]] = (RegularLowHand,) + low: ClassVar[bool] = True @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, + raw_antes: ValuesLike, bring_in: int, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a fixed-limit razz game. - :param antes: The antes. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. :param bring_in: The bring-in. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( + automations, + ante_trimming_status, + raw_antes, + bring_in, + small_bet, + big_bet, + player_count, + )(raw_starting_stacks) + + +class Draw(Poker, ABC): + """The abstract base class for draw games.""" + + hole_dealing_count: ClassVar[int] + """The number of hole dealings.""" + max_completion_betting_or_raising_count: ClassVar[int | None] + """The maximum number of completions, bettings, or raisings.""" + + +class SingleDraw(Draw, ABC): + """The abstract base class for single draw games. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. + :param min_bet: The min bet. + :param player_count: The number of players. + """ + + def __init__( + self, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, + min_bet: int, + player_count: int, + ) -> None: + super().__init__( automations, - Deck.REGULAR, - (RegularLowHand,), ( Street( False, - (False, False, True), + (False,) * self.hole_dealing_count, 0, False, - Opening.HIGH_CARD, - small_bet, - 4, + Opening.POSITION, + min_bet, + self.max_completion_betting_or_raising_count, ), Street( True, - (True,), + (), + 0, + True, + Opening.POSITION, + min_bet, + self.max_completion_betting_or_raising_count, + ), + ), + ante_trimming_status, + raw_antes, + raw_blinds_or_straddles, + 0, + player_count, + ) + + +class TripleDraw(Draw, ABC): + """The abstract base class for triple draw games. + + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. + :param small_bet: The small bet. + :param big_bet: The big bet. + :param player_count: The number of players. + """ + + def __init__( + self, + automations: tuple[Automation, ...], + ante_trimming_status: bool, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, + small_bet: int, + big_bet: int, + player_count: int, + ) -> None: + super().__init__( + automations, + ( + Street( + False, + (False,) * self.hole_dealing_count, 0, False, - Opening.LOW_HAND, + Opening.POSITION, small_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( True, - (True,), + (), 0, - False, - Opening.LOW_HAND, - big_bet, - 4, + True, + Opening.POSITION, + small_bet, + self.max_completion_betting_or_raising_count, ), Street( True, - (True,), + (), 0, - False, - Opening.LOW_HAND, + True, + Opening.POSITION, big_bet, - 4, + self.max_completion_betting_or_raising_count, ), Street( True, - (False,), + (), 0, - False, - Opening.LOW_HAND, + True, + Opening.POSITION, big_bet, - 4, + self.max_completion_betting_or_raising_count, ), ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, - None, - bring_in, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + 0, player_count, ) -class DeuceToSevenLowball(Poker, ABC): +class DeuceToSevenLowballMixin: """The abstract base class for deuce-to-seven lowball games.""" - max_down_card_count = 5 - max_up_card_count = 0 - max_board_card_count = 0 - rank_orders = (RankOrder.STANDARD,) - button_status = True + deck: ClassVar[Deck] = Deck.STANDARD + hand_types: ClassVar[tuple[type[Hand], ...]] = (StandardLowHand,) + hole_dealing_count: ClassVar[int] = 5 -class NoLimitDeuceToSevenLowballSingleDraw(DeuceToSevenLowball): +class NoLimitDeuceToSevenLowballSingleDraw( + NoLimitPokerMixin, + DeuceToSevenLowballMixin, + SingleDraw, +): """The class for no-limit deuce-to-seven lowball single draw games. """ @@ -1003,56 +1135,38 @@ def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, min_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a no-limit deuce-to-seven lowball single draw game. - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param min_bet: The min bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (StandardLowHand,), - ( - Street( - False, - (False,) * 5, - 0, - False, - Opening.POSITION, - min_bet, - None, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - min_bet, - None, - ), - ), - BettingStructure.NO_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + min_bet, player_count, - ) + )(raw_starting_stacks) -class FixedLimitDeuceToSevenLowballTripleDraw(DeuceToSevenLowball): +class FixedLimitDeuceToSevenLowballTripleDraw( + FixedLimitPokerMixin, + DeuceToSevenLowballMixin, + TripleDraw, +): """The class for fixed-limit deuce-to-seven lowball triple draw games. """ @@ -1062,11 +1176,11 @@ def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a fixed-limit deuce-to-seven lowball triple draw game. @@ -1164,85 +1278,44 @@ def create_state( >>> state.stacks [0, 4190000, 5910000, 12095000] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.STANDARD, - (StandardLowHand,), - ( - Street( - False, - (False,) * 5, - 0, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - big_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - big_bet, - 4, - ), - ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + small_bet, + big_bet, player_count, - ) + )(raw_starting_stacks) -class FixedLimitBadugi(Poker): +class FixedLimitBadugi(FixedLimitPokerMixin, TripleDraw): """The class for fixed-limit badugi games.""" - max_down_card_count = 4 - max_up_card_count = 0 - max_board_card_count = 0 - rank_orders = (RankOrder.REGULAR,) - button_status = True + deck: ClassVar[Deck] = Deck.REGULAR + hand_types: ClassVar[tuple[type[Hand], ...]] = (BadugiHand,) + hole_dealing_count: ClassVar[int] = 4 @classmethod def create_state( cls, automations: tuple[Automation, ...], ante_trimming_status: bool, - antes: ValuesLike, - blinds_or_straddles: ValuesLike, + raw_antes: ValuesLike, + raw_blinds_or_straddles: ValuesLike, small_bet: int, big_bet: int, - starting_stacks: ValuesLike, + raw_starting_stacks: ValuesLike, player_count: int, ) -> State: """Create a fixed-limit badugi game. @@ -1360,61 +1433,22 @@ def create_state( >>> state.stacks [196, 220, 200, 184] - :param antes: The antes. - :param blinds_or_straddles: The blinds or straddles. + :param automations: The automations. + :param ante_trimming_status: The ante trimming status. + :param raw_antes: The raw antes. + :param raw_blinds_or_straddles: The raw blinds or straddles. :param small_bet: The small bet. :param big_bet: The big bet. - :param starting_stacks: The starting stacks. + :param raw_starting_stacks: The raw starting stacks. :param player_count: The number of players. :return: The created state. """ - return State( + return cls( automations, - Deck.REGULAR, - (BadugiHand,), - ( - Street( - False, - (False,) * 4, - 0, - False, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - small_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - big_bet, - 4, - ), - Street( - True, - (), - 0, - True, - Opening.POSITION, - big_bet, - 4, - ), - ), - BettingStructure.FIXED_LIMIT, ante_trimming_status, - antes, - blinds_or_straddles, - 0, - starting_stacks, + raw_antes, + raw_blinds_or_straddles, + small_bet, + big_bet, player_count, - ) + )(raw_starting_stacks) diff --git a/pokerkit/lookups.py b/pokerkit/lookups.py index f5d3ddc0..97fb307d 100644 --- a/pokerkit/lookups.py +++ b/pokerkit/lookups.py @@ -11,6 +11,7 @@ from itertools import combinations, filterfalse from math import prod from operator import contains +from typing import ClassVar from pokerkit.utilities import Card, CardsLike, Rank, RankOrder @@ -106,6 +107,8 @@ class Lookup(ABC): assert len(__primes) >= len(tuple(Rank)) __multipliers = dict(zip(Rank, __primes)) + rank_order: ClassVar[RankOrder] + """The rank order.""" __entries: dict[tuple[int, bool], Entry] = field( default_factory=dict, init=False, @@ -213,32 +216,30 @@ def __get_key(self, cards: CardsLike) -> tuple[int, bool]: def _add_multisets( self, - rank_order: RankOrder, counter: Counter[int], suitednesses: tuple[bool, ...], label: Label, ) -> None: - hashes = self.__hash_multisets(rank_order, counter) + hashes = self.__hash_multisets(self.rank_order, counter) for hash_ in reversed(hashes): self.__add(hash_, suitednesses, label) def _add_straights( self, - rank_order: RankOrder, count: int, suitednesses: tuple[bool, ...], label: Label, ) -> None: self.__add( - self.__hash(rank_order[-1:] + rank_order[: count - 1]), + self.__hash(self.rank_order[-1:] + self.rank_order[: count - 1]), suitednesses, label, ) - for i in range(len(rank_order) - count + 1): + for i in range(len(self.rank_order) - count + 1): self.__add( - self.__hash(rank_order[i:i + count]), + self.__hash(self.rank_order[i:i + count]), suitednesses, label, ) @@ -279,56 +280,26 @@ class StandardLookup(Lookup): """ + rank_order: ClassVar[RankOrder] = RankOrder.STANDARD + def __post_init__(self) -> None: + self._add_multisets(Counter({1: 5}), (False,), Label.HIGH_CARD) + self._add_multisets(Counter({2: 1, 1: 3}), (False,), Label.ONE_PAIR) + self._add_multisets(Counter({2: 2, 1: 1}), (False,), Label.TWO_PAIR) self._add_multisets( - RankOrder.STANDARD, - Counter({1: 5}), - (False,), - Label.HIGH_CARD, - ) - self._add_multisets( - RankOrder.STANDARD, - Counter({2: 1, 1: 3}), - (False,), - Label.ONE_PAIR, - ) - self._add_multisets( - RankOrder.STANDARD, - Counter({2: 2, 1: 1}), - (False,), - Label.TWO_PAIR, - ) - self._add_multisets( - RankOrder.STANDARD, Counter({3: 1, 1: 2}), (False,), Label.THREE_OF_A_KIND, ) - self._add_straights(RankOrder.STANDARD, 5, (False,), Label.STRAIGHT) + self._add_straights(5, (False,), Label.STRAIGHT) + self._add_multisets(Counter({1: 5}), (True,), Label.FLUSH) + self._add_multisets(Counter({3: 1, 2: 1}), (False,), Label.FULL_HOUSE) self._add_multisets( - RankOrder.STANDARD, - Counter({1: 5}), - (True,), - Label.FLUSH, - ) - self._add_multisets( - RankOrder.STANDARD, - Counter({3: 1, 2: 1}), - (False,), - Label.FULL_HOUSE, - ) - self._add_multisets( - RankOrder.STANDARD, Counter({4: 1, 1: 1}), (False,), Label.FOUR_OF_A_KIND, ) - self._add_straights( - RankOrder.STANDARD, - 5, - (True,), - Label.STRAIGHT_FLUSH, - ) + self._add_straights(5, (True,), Label.STRAIGHT_FLUSH) @dataclass @@ -355,61 +326,26 @@ class ShortDeckHoldemLookup(Lookup): """ + rank_order: ClassVar[RankOrder] = RankOrder.SHORT_DECK_HOLDEM + def __post_init__(self) -> None: + self._add_multisets(Counter({1: 5}), (False,), Label.HIGH_CARD) + self._add_multisets(Counter({2: 1, 1: 3}), (False,), Label.ONE_PAIR) + self._add_multisets(Counter({2: 2, 1: 1}), (False,), Label.TWO_PAIR) self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, - Counter({1: 5}), - (False,), - Label.HIGH_CARD, - ) - self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, - Counter({2: 1, 1: 3}), - (False,), - Label.ONE_PAIR, - ) - self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, - Counter({2: 2, 1: 1}), - (False,), - Label.TWO_PAIR, - ) - self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, Counter({3: 1, 1: 2}), (False,), Label.THREE_OF_A_KIND, ) - self._add_straights( - RankOrder.SHORT_DECK_HOLDEM, - 5, - (False,), - Label.STRAIGHT, - ) - self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, - Counter({3: 1, 2: 1}), - (False,), - Label.FULL_HOUSE, - ) + self._add_straights(5, (False,), Label.STRAIGHT) + self._add_multisets(Counter({3: 1, 2: 1}), (False,), Label.FULL_HOUSE) + self._add_multisets(Counter({1: 5}), (True,), Label.FLUSH) self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, - Counter({1: 5}), - (True,), - Label.FLUSH, - ) - self._add_multisets( - RankOrder.SHORT_DECK_HOLDEM, Counter({4: 1, 1: 1}), (False,), Label.FOUR_OF_A_KIND, ) - self._add_straights( - RankOrder.SHORT_DECK_HOLDEM, - 5, - (True,), - Label.STRAIGHT_FLUSH, - ) + self._add_straights(5, (True,), Label.STRAIGHT_FLUSH) @dataclass @@ -420,13 +356,10 @@ class EightOrBetterLookup(Lookup): please use :class:`pokerkit.hands.EightOrBetterLowHand`. """ + rank_order: ClassVar[RankOrder] = RankOrder.EIGHT_OR_BETTER_LOW + def __post_init__(self) -> None: - self._add_multisets( - RankOrder.EIGHT_OR_BETTER_LOW, - Counter({1: 5}), - (False, True), - Label.HIGH_CARD, - ) + self._add_multisets(Counter({1: 5}), (False, True), Label.HIGH_CARD) @dataclass @@ -453,39 +386,19 @@ class RegularLookup(Lookup): """ + rank_order: ClassVar[RankOrder] = RankOrder.REGULAR + def __post_init__(self) -> None: + self._add_multisets(Counter({1: 5}), (False, True), Label.HIGH_CARD) + self._add_multisets(Counter({2: 1, 1: 3}), (False,), Label.ONE_PAIR) + self._add_multisets(Counter({2: 2, 1: 1}), (False,), Label.TWO_PAIR) self._add_multisets( - RankOrder.REGULAR, - Counter({1: 5}), - (False, True), - Label.HIGH_CARD, - ) - self._add_multisets( - RankOrder.REGULAR, - Counter({2: 1, 1: 3}), - (False,), - Label.ONE_PAIR, - ) - self._add_multisets( - RankOrder.REGULAR, - Counter({2: 2, 1: 1}), - (False,), - Label.TWO_PAIR, - ) - self._add_multisets( - RankOrder.REGULAR, Counter({3: 1, 1: 2}), (False,), Label.THREE_OF_A_KIND, ) + self._add_multisets(Counter({3: 1, 2: 1}), (False,), Label.FULL_HOUSE) self._add_multisets( - RankOrder.REGULAR, - Counter({3: 1, 2: 1}), - (False,), - Label.FULL_HOUSE, - ) - self._add_multisets( - RankOrder.REGULAR, Counter({4: 1, 1: 1}), (False,), Label.FOUR_OF_A_KIND, @@ -514,14 +427,11 @@ class BadugiLookup(Lookup): """ + rank_order: ClassVar[RankOrder] = RankOrder.REGULAR + def __post_init__(self) -> None: for i in range(4, 0, -1): - self._add_multisets( - RankOrder.REGULAR, - Counter({1: i}), - (i == 1,), - Label.HIGH_CARD, - ) + self._add_multisets(Counter({1: i}), (i == 1,), Label.HIGH_CARD) @dataclass @@ -546,10 +456,7 @@ class KuhnPokerLookup(Lookup): """ + rank_order: ClassVar[RankOrder] = RankOrder.KUHN_POKER + def __post_init__(self) -> None: - self._add_multisets( - RankOrder.KUHN_POKER, - Counter({1: 1}), - (True,), - Label.HIGH_CARD, - ) + self._add_multisets(Counter({1: 1}), (True,), Label.HIGH_CARD) diff --git a/pokerkit/state.py b/pokerkit/state.py index ca1ae060..82ecad62 100644 --- a/pokerkit/state.py +++ b/pokerkit/state.py @@ -76,10 +76,11 @@ class Opening(StrEnum): @dataclass class _LowHandOpeningLookup(Lookup): + rank_order = RankOrder.REGULAR + def __post_init__(self) -> None: for i in range(1, 5): self._add_multisets( - RankOrder.REGULAR, Counter({1: i}), (False, True), Label.HIGH_CARD, @@ -87,41 +88,30 @@ def __post_init__(self) -> None: for i in range(3): self._add_multisets( - RankOrder.REGULAR, Counter({2: 1, 1: i}), (False,), Label.ONE_PAIR, ) - self._add_multisets( - RankOrder.REGULAR, - Counter({2: 2}), - (False,), - Label.TWO_PAIR, - ) + self._add_multisets(Counter({2: 2}), (False,), Label.TWO_PAIR) for i in range(2): self._add_multisets( - RankOrder.REGULAR, Counter({3: 1, 1: i}), (False,), Label.THREE_OF_A_KIND, ) - self._add_multisets( - RankOrder.REGULAR, - Counter({4: 1}), - (False,), - Label.FOUR_OF_A_KIND, - ) + self._add_multisets(Counter({4: 1}), (False,), Label.FOUR_OF_A_KIND) @dataclass class _HighHandOpeningLookup(Lookup): + rank_order = RankOrder.STANDARD + def __post_init__(self) -> None: for i in range(1, 5): self._add_multisets( - RankOrder.STANDARD, Counter({1: i}), (False, True), Label.HIGH_CARD, @@ -129,33 +119,21 @@ def __post_init__(self) -> None: for i in range(3): self._add_multisets( - RankOrder.STANDARD, Counter({2: 1, 1: i}), (False,), Label.ONE_PAIR, ) - self._add_multisets( - RankOrder.STANDARD, - Counter({2: 2}), - (False,), - Label.TWO_PAIR, - ) + self._add_multisets(Counter({2: 2}), (False,), Label.TWO_PAIR) for i in range(2): self._add_multisets( - RankOrder.STANDARD, Counter({3: 1, 1: i}), (False,), Label.THREE_OF_A_KIND, ) - self._add_multisets( - RankOrder.STANDARD, - Counter({4: 1}), - (False,), - Label.FOUR_OF_A_KIND, - ) + self._add_multisets(Counter({4: 1}), (False,), Label.FOUR_OF_A_KIND) @dataclass(frozen=True) @@ -3023,26 +3001,38 @@ def card_key(rank_order: RankOrder, card: Card) -> tuple[int, Suit]: min_up_cards = [ min_or_none( self.get_up_cards(i), - key=partial(card_key, RankOrder.STANDARD), + key=partial( + card_key, + _HighHandOpeningLookup.rank_order, + ), ) for i in self.player_indices ] self.opener_index = min_up_cards.index( min_or_none( min_up_cards, - key=partial(card_key, RankOrder.STANDARD), + key=partial( + card_key, + _HighHandOpeningLookup.rank_order, + ), ), ) case Opening.HIGH_CARD: max_up_cards = [ max_or_none( self.get_up_cards(i), - key=partial(card_key, RankOrder.REGULAR), + key=partial( + card_key, + _LowHandOpeningLookup.rank_order, + ), ) for i in self.player_indices ] self.opener_index = max_up_cards.index( max_or_none( max_up_cards, - key=partial(card_key, RankOrder.REGULAR), + key=partial( + card_key, + _LowHandOpeningLookup.rank_order, + ), ), ) case Opening.LOW_HAND: diff --git a/pokerkit/tests/test_lookups.py b/pokerkit/tests/test_lookups.py index a2ba009d..737daae9 100644 --- a/pokerkit/tests/test_lookups.py +++ b/pokerkit/tests/test_lookups.py @@ -126,4 +126,4 @@ def test_get_entry(self) -> None: if __name__ == '__main__': - main() + main() # pragma: no cover diff --git a/pokerkit/tests/test_state.py b/pokerkit/tests/test_state.py index 8a54d13b..ddff1f3e 100644 --- a/pokerkit/tests/test_state.py +++ b/pokerkit/tests/test_state.py @@ -578,4 +578,4 @@ def test_reshuffling(self) -> None: if __name__ == '__main__': - main() + main() # pragma: no cover diff --git a/pokerkit/tests/test_utilities.py b/pokerkit/tests/test_utilities.py index 6f23ba2d..0d53b353 100644 --- a/pokerkit/tests/test_utilities.py +++ b/pokerkit/tests/test_utilities.py @@ -60,4 +60,4 @@ def test_members(self) -> None: if __name__ == '__main__': - main() + main() # pragma: no cover diff --git a/pokerkit/tests/test_wsop/test_2023_43.py b/pokerkit/tests/test_wsop/test_2023_43.py index 5c2719af..af913c1f 100644 --- a/pokerkit/tests/test_wsop/test_2023_43.py +++ b/pokerkit/tests/test_wsop/test_2023_43.py @@ -5823,4 +5823,4 @@ def test_03_50_24(self) -> None: if __name__ == '__main__': - main() + main() # pragma: no cover diff --git a/setup.py b/setup.py index 09280f3e..4244fb02 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='pokerkit', - version='0.3.0', + version='0.3.1', description='An open-source Python library for poker simulations and hand evaluations', long_description=open('README.rst').read(), long_description_content_type='text/x-rst', @@ -28,6 +28,7 @@ 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], keywords=[ 'artificial-intelligence',