Skip to content

Commit

Permalink
Write unit tests and fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
AussieSeaweed committed Oct 7, 2023
1 parent 5cfbb89 commit 887423a
Show file tree
Hide file tree
Showing 9 changed files with 768 additions and 191 deletions.
3 changes: 1 addition & 2 deletions cardroom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
All cardroom tools are imported here.
"""

__all__ = 'Scheduler', 'Table', 'Tournament',
__all__ = 'Scheduler', 'Table'

from cardroom.table import Table
from cardroom.tournament import Tournament
from cardroom.utilities import Scheduler
141 changes: 78 additions & 63 deletions cardroom/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Table:
"""The raw blinds or straddles."""
bring_in: int
"""The bring-in."""
buy_in_range: range
"""The buy-in range."""
timebank: float
"""The timebank."""
timebank_increment: float
Expand Down Expand Up @@ -160,17 +162,43 @@ def turn_index(self) -> int | None:

return turn_index

def is_occupied(self, seat_index: int) -> bool:
"""Return the occupation status of the seat.
:param seat_index: The seat index.
:return: The occupation status of the seat.
"""
return self.player_names[seat_index] is not None

def is_active(self, seat_index: int) -> bool:
"""Return the active status of the player at the seat.
:param seat_index: The seat index.
:return: The active status of the player at the seat.
"""
return (
self.player_names[seat_index] is not None
self.is_occupied(seat_index)
and self.inactive_timestamps[seat_index] is None
)

def is_kickable(self, seat_index: int) -> bool:
"""Return the kickable status of the player at the seat.
:param seat_index: The seat index.
:return: The kickable status of the player at the seat.
"""
inactive_timestamp = self.inactive_timestamps[seat_index]

return (
self.is_occupied(seat_index)
and inactive_timestamp is not None
and (
inactive_timestamp
+ timedelta(seconds=self.idle_timeout)
<= datetime.now()
)
)

def get_seat_index(self, player_name_or_player_index: str | int) -> int:
"""Return the seat index of the player.
Expand Down Expand Up @@ -206,7 +234,9 @@ def _connect(self, player_name: str, seat_index: int, amount: int) -> None:
raise ValueError('seat index out of bounds')
elif amount < 0:
raise ValueError('negative amount')
elif self.player_names[seat_index] is not None:
elif amount not in self.buy_in_range:
raise ValueError('invalid buy-in amount')
elif self.is_occupied(seat_index):
raise ValueError('occupied seat')

self.player_names[seat_index] = player_name
Expand Down Expand Up @@ -251,7 +281,9 @@ def deactivate(self, player_name: str) -> None:

def _deactivate(self, player_name: str) -> None:
seat_index = self.get_seat_index(player_name)
self.inactive_timestamps[seat_index] = datetime.now()

if self.inactive_timestamps[seat_index] is None:
self.inactive_timestamps[seat_index] = datetime.now()

def buy_in_rebuy_top_off_or_rat_hole(
self,
Expand All @@ -278,6 +310,8 @@ def _buy_in_rebuy_top_off_or_rat_hole(
) -> None:
if amount < 0:
raise ValueError('negative amount')
elif amount not in self.buy_in_range:
raise ValueError('invalid buy-in amount')

self._activate(player_name)

Expand Down Expand Up @@ -358,7 +392,7 @@ def show_or_muck_hole_cards(
)

def _play(self) -> None:
if self.state:
if self.state is not None:
if self.state.status:
raise ValueError('state active')

Expand Down Expand Up @@ -392,7 +426,6 @@ def _play(self) -> None:

self.buy_in_rebuy_top_off_or_rat_holing_amounts[i] = None

for i in self.seat_indices:
if self.starting_stacks[i] == 0:
player_name = self.player_names[i]

Expand All @@ -402,24 +435,6 @@ def _play(self) -> None:

self.starting_stacks[i] = None

inactive_timestamp = self.inactive_timestamps[i]

if (
inactive_timestamp is not None
and (
inactive_timestamp
+ timedelta(seconds=self.idle_timeout)
<= datetime.now()
)
):
self.player_names[i] = None
self.player_indices[i] = None
self.starting_stacks[i] = None
self.buy_in_rebuy_top_off_or_rat_holing_amounts[i] = None
self.active_timestamps[i] = None
self.inactive_timestamps[i] = None
self.timebanks[i] = None

active_seat_indices = deque(filter(self.is_active, self.seat_indices))

if len(active_seat_indices) >= 2:
Expand Down Expand Up @@ -453,25 +468,28 @@ def _play(self) -> None:

starting_stacks.append(starting_stack)

self.state = State(
self.deck,
self.hand_types,
self.streets,
self.betting_structure,
(),
self.ante_trimming_status,
self.raw_antes,
self.raw_blinds_or_straddles,
self.bring_in,
starting_stacks,
len(starting_stacks),
)
self.state = self._create_state(starting_stacks)
else:
for i in self.seat_indices:
self.player_indices[i] = None

self.button_index = None

def _create_state(self, starting_stacks: list[int]) -> State:
return State(
(),
self.deck,
self.hand_types,
self.streets,
self.betting_structure,
self.ante_trimming_status,
self.raw_antes,
self.raw_blinds_or_straddles,
self.bring_in,
starting_stacks,
len(starting_stacks),
)

_scheduler: Scheduler = field(init=False, default_factory=Scheduler)

def run(self) -> None:
Expand Down Expand Up @@ -582,6 +600,16 @@ def nested_function(count: int) -> Operation | None:

def _update(self) -> None:
if self.state is None or not self.state.status:
for i in self.seat_indices:
if self.is_kickable(i):
self.player_names[i] = None
self.player_indices[i] = None
self.starting_stacks[i] = None
self.buy_in_rebuy_top_off_or_rat_holing_amounts[i] = None
self.active_timestamps[i] = None
self.inactive_timestamps[i] = None
self.timebanks[i] = None

self._call(self.play_timeout, self._play)
else:
if self.state.can_post_ante():
Expand Down Expand Up @@ -652,48 +680,35 @@ def _update(self) -> None:

if self.state.can_stand_pat_or_discard():
if timeout is None:
timeout = self.standing_pat_timeout
timeout = self.standing_pat_timeout + timebank

self._call_state(
None,
timeout + timebank,
self.state.stand_pat_or_discard,
)
self._call_state(None, timeout, State.stand_pat_or_discard)
elif self.state.can_fold():
if timeout is None:
timeout = self.folding_timeout
timeout = self.folding_timeout + timebank

self._call_state(
None,
timeout + timebank,
self.state.fold,
)
self._call_state(None, timeout, State.fold)
elif self.state.can_check_or_call():
if timeout is None:
timeout = self.checking_timeout
timeout = self.checking_timeout + timebank

self._call_state(
None,
timeout + timebank,
self.state.check_or_call,
)
self._call_state(None, timeout, State.check_or_call)
elif self.state.can_post_bring_in():
if timeout is None:
timeout = self.bring_in_posting_timeout
timeout = self.bring_in_posting_timeout + timebank

self._call_state(
None,
timeout + timebank,
self.state.post_bring_in,
)
self._call_state(None, timeout, State.post_bring_in)
elif self.state.can_show_or_muck_hole_cards():
if timeout is None:
timeout = self.hole_cards_showing_or_mucking_timeout
timeout = (
self.hole_cards_showing_or_mucking_timeout
+ timebank
)

self._call_state(
None,
timeout + timebank,
self.state.show_or_muck_hole_cards,
timeout,
State.show_or_muck_hole_cards,
)
else:
raise AssertionError
3 changes: 3 additions & 0 deletions cardroom/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
""":mod:`cardroom.tests` is the package for the unit tests in the
Cardroom library.
"""
Loading

0 comments on commit 887423a

Please sign in to comment.