Skip to content

Commit

Permalink
Implement Rule 96 and 96a from the 2024 WSOP Tournament Rules
Browse files Browse the repository at this point in the history
  • Loading branch information
AussieSeaweed committed Jan 21, 2025
1 parent ae72a89 commit 5b36a6d
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 8 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Changelog

All notable changes to this project will be documented in this file.

Version 0.6.1 (January 20, 2025)
--------------------------------

**Changed**

- Implement Rule 96 and 96a (its exception) of the 2024 WSOP Tournament Rules which states the following: "In no-limit and pot-limit, all raises must be equal to or greater than the size of the previous bet or raise on that betting round. An all-in wager of less than a full raise does not reopen the betting to a Participant who has already acted." The exception to this rule is triggered when "two or more consecutive all-in wagers that exceed the minimum allowable bet or raise." I would like to thank `Phenom Poker <https://www.phenompoker.com/>`_ for reporting this issue.

Version 0.6.0 (January 17, 2025)
--------------------------------

Expand Down
64 changes: 57 additions & 7 deletions pokerkit/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4140,6 +4140,16 @@ def stand_pat_or_discard(
This attribute is incremented with each completion/bet/raises.
"""
acted_player_indices: set[int] = field(default_factory=set, init=False)
"""The indices of players who acted."""
consecutive_all_in_completion_betting_or_raising_amounts: list[int] = (
field(default_factory=list, init=False)
)
"""The consecutive completion, betting, or raising amounts.
This is used to track whether successive non-full wagers combine to
form a full one, in which case a new betting round is started.
"""

def _setup_betting(self) -> None:
pass
Expand Down Expand Up @@ -4239,6 +4249,8 @@ def card_key(rank_order: RankOrder, card: Card) -> tuple[int, Suit]:
self.completion_betting_or_raising_amount = 0
self.completion_betting_or_raising_count = 0

self.acted_player_indices.clear()
self.consecutive_all_in_completion_betting_or_raising_amounts.clear()
self._update_betting(
status=(
len(self.actor_indices) == 1
Expand Down Expand Up @@ -4282,7 +4294,11 @@ def _end_betting(self) -> None:
self._begin_bet_collection()

def _pop_actor_index(self) -> int:
return self.actor_indices.popleft()
actor_index = self.actor_indices.popleft()

self.acted_player_indices.add(actor_index)

return actor_index

@property
def actor_index(self) -> int | None:
Expand Down Expand Up @@ -4731,6 +4747,26 @@ def _verify_completion_betting_or_raising(self) -> None:

assert player_index is not None

if (
self.consecutive_all_in_completion_betting_or_raising_amounts
and (
sum(
(
self
.consecutive_all_in_completion_betting_or_raising_amounts # noqa: E501
),
)
< self.completion_betting_or_raising_amount
)
and player_index in self.acted_player_indices
):
raise ValueError(
(
'The player already acted and hence cannot raise in face'
' of a non-full all-in wager'
),
)

if (
self.stacks[player_index]
<= max(self.bets) - self.bets[player_index]
Expand Down Expand Up @@ -4945,12 +4981,6 @@ def complete_bet_or_raise_to(
self.bring_in_status = False
self.completion_status = False
self.actor_indices = deque(self.player_indices)
self.opener_index = player_index
self.completion_betting_or_raising_amount = max(
self.completion_betting_or_raising_amount,
completion_betting_or_raising_amount,
)
self.completion_betting_or_raising_count += 1

self.actor_indices.rotate(-player_index)
self.actor_indices.popleft()
Expand All @@ -4962,6 +4992,26 @@ def complete_bet_or_raise_to(

assert self.actor_indices

self.opener_index = player_index
self.completion_betting_or_raising_amount = max(
self.completion_betting_or_raising_amount,
completion_betting_or_raising_amount,
)
self.completion_betting_or_raising_count += 1

if self.stacks[player_index]:
(
self
.consecutive_all_in_completion_betting_or_raising_amounts
.clear()
)
else:
(
self
.consecutive_all_in_completion_betting_or_raising_amounts
.append(completion_betting_or_raising_amount)
)

operation = CompletionBettingOrRaisingTo(
player_index,
amount,
Expand Down
101 changes: 101 additions & 0 deletions pokerkit/tests/test_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
""":mod:`pokerkit.tests.test_papers` implements unit tests for
the various rules of poker.
"""

from copy import deepcopy
from unittest import TestCase, main

from pokerkit.games import NoLimitTexasHoldem
from pokerkit.state import Automation


class WSOPTournamentRulesTestCase(TestCase):
def test_2024_96(self) -> None:
state = NoLimitTexasHoldem.create_state(
tuple(Automation),
True,
0,
(100, 200),
200,
(650, 20000, 20000),
3,
)

state.complete_bet_or_raise_to(600)
state.complete_bet_or_raise_to(650)
state.check_or_call()

self.assertFalse(state.can_complete_bet_or_raise_to())

def test_2024_96_a(self) -> None:
state = NoLimitTexasHoldem.create_state(
tuple(Automation),
True,
1,
0,
1,
(20001, 20001, 20001, 1301, 1701),
5,
)

state.complete_bet_or_raise_to(500)
state.complete_bet_or_raise_to(1000)
state.check_or_call()
state.complete_bet_or_raise_to(1300)
state.complete_bet_or_raise_to(1700)

hypothetical_state = deepcopy(state)

hypothetical_state.check_or_call()

self.assertTrue(hypothetical_state.can_complete_bet_or_raise_to())
self.assertEqual(
hypothetical_state.min_completion_betting_or_raising_to_amount,
2200,
)
self.assertEqual(
hypothetical_state.max_completion_betting_or_raising_to_amount,
20000,
)

hypothetical_state.check_or_call()

self.assertTrue(hypothetical_state.can_complete_bet_or_raise_to())
self.assertEqual(
hypothetical_state.min_completion_betting_or_raising_to_amount,
2200,
)
self.assertEqual(
hypothetical_state.max_completion_betting_or_raising_to_amount,
20000,
)

hypothetical_state = deepcopy(state)

hypothetical_state.fold()

self.assertTrue(hypothetical_state.can_complete_bet_or_raise_to())
self.assertEqual(
hypothetical_state.min_completion_betting_or_raising_to_amount,
2200,
)
self.assertEqual(
hypothetical_state.max_completion_betting_or_raising_to_amount,
20000,
)

hypothetical_state.check_or_call()

self.assertTrue(hypothetical_state.can_complete_bet_or_raise_to())
self.assertEqual(
hypothetical_state.min_completion_betting_or_raising_to_amount,
2200,
)
self.assertEqual(
hypothetical_state.max_completion_betting_or_raising_to_amount,
20000,
)


if __name__ == '__main__':
main() # pragma: no cover
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name='pokerkit',
version='0.6.0',
version='0.6.1',
description='An open-source Python library for poker game simulations, hand evaluations, and statistical analysis',
long_description=open('README.rst').read(),
long_description_content_type='text/x-rst',
Expand Down

0 comments on commit 5b36a6d

Please sign in to comment.