Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ability to create a copy of the TexasHoldEm object #199

Merged
merged 3 commits into from
Jun 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ A python package for Texas Hold 'Em Poker providing:

| Version Name | Latest Tag | Release Notes | Patch Notes | Documentation | Release Date | End Support Date |
| ------------ | ---------- | ------------- | ----------- | ------------- | ------------ | ---------------- |
| 0.10 | v0.10.0-alpha.0 | [Release Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.10-alpha.0) | [Patch Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.10-alpha.0) | [Documentation](https://texasholdem.readthedocs.io/en/0.10/) | 18 June 2023 | |
| 0.9 | v0.9.0 | [Release Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.9.0) | [Patch Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.9.0) | [Documentation](https://texasholdem.readthedocs.io/en/0.9/) | 14 March 2023 | |
| 0.8 | v0.8.1 | [Release Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.8.0) | [Patch Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.8.1) | [Documentation](https://texasholdem.readthedocs.io/en/0.8/) | 6 November 2022 | 30 June 2023 |
| 0.7 | v0.7.3 | [Release Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.7.0) | [Patch Notes](https://github.com/SirRender00/texasholdem/releases/tag/v0.7.3) | [Documentation](https://texasholdem.readthedocs.io/en/0.7/) | 16 April 2022 | 30 June 2023 |
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog-0.10-alpha.0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Changes in 0.10-alpha.0
==========================

Features
---------
- New method :class:`~texasholdem.game.game.TexasHoldEm.copy` which will construct a copy of the game state.
- Added available actions printing in the TextGUI.
2 changes: 1 addition & 1 deletion docs/changelog-0.9.0.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Development
Changes in 0.9.0
==========================

Features
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
author = "Evyn Machi"

# The full version, including alpha/beta/rc tags
release = "v0.9.0"
release = "v0.10-alpha.0"


# -- General configuration ---------------------------------------------------
Expand Down
5 changes: 0 additions & 5 deletions docs/development.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
Development
==========================

Features
---------

- Added available actions printing in the TextGUI
550 changes: 265 additions & 285 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "texasholdem"
version = "0.9.0"
version = "0.10-alpha.0"
description = "A texasholdem python package"
authors = ["Evyn Machi <evyn.machi@gmail.com>"]
keywords = ['texasholdem', 'holdem', 'poker']
Expand Down
22 changes: 22 additions & 0 deletions tests/game/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- Call player fixture
- And a method containing assert checks for the prehand for a game
"""
import random

import pytest

from texasholdem.game.game import TexasHoldEm
Expand Down Expand Up @@ -338,10 +340,30 @@ def before(self, game: TexasHoldEm) -> bool:
return False


class CopyChecker(GamePredicate):
"""
Checks that if the raise option is unset, then raise should be invalid
"""

game_copy = None
shuffle = True

def before(self, game: TexasHoldEm) -> bool:
self.shuffle = random.randint(0, 1) == 1
self.game_copy = game.copy(shuffle=self.shuffle)

def after(self, game: TexasHoldEm) -> bool:
last_action = game.hand_history.combined_actions()[-1]
self.game_copy.take_action(last_action.action_type, total=last_action.total)

return self.game_copy.hand_history.to_string() != game.hand_history.to_string()


GAME_PREDICATES = (
EmptyPots(),
LastRaiseChecker(),
MinRaiseChecker(),
RaiseOptionChecker(),
AvailableMoveChecker(),
CopyChecker(),
)
17 changes: 17 additions & 0 deletions texasholdem/card/deck.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
import random
from typing import List

Expand Down Expand Up @@ -61,3 +62,19 @@ def _get_full_deck() -> List[Card]:
Deck._FULL_DECK.append(Card(rank + suit))

return list(Deck._FULL_DECK)

def copy(self, shuffle: bool = True) -> Deck:
"""
Make a copy of the deck

Args:
shuffle (bool): Shuffle the deck when copying, defaults to true
Returns:
Deck: A copy of the deck

"""
deck = Deck()
deck.cards = self.cards.copy()
if shuffle:
deck.shuffle()
return deck
108 changes: 108 additions & 0 deletions texasholdem/game/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

"""
from __future__ import annotations

import itertools
import os
from typing import Iterator, Callable, Dict, Tuple, Optional, Union, List, Iterable
from collections import deque
Expand Down Expand Up @@ -385,6 +387,8 @@ def player_iter(
if loc is None:
loc = self.current_player

loc = loc % self.max_players

start, stop, step = loc, loc + self.max_players, 1
if reverse:
start, stop, step = stop, start, -step
Expand Down Expand Up @@ -1272,3 +1276,107 @@ def _import_history(history: History) -> Iterator[TexasHoldEm]:

game.take_action(action, total=total)
yield game

def copy(self, shuffle: bool = True):
"""
Arguments:
shuffle (bool): Shuffle the deck, defaults to true.
Returns:
TexasHoldEm: A copy of the game.

"""
# pylint: disable=protected-access
game = TexasHoldEm(
buyin=self.buyin,
big_blind=self.big_blind,
small_blind=self.small_blind,
max_players=len(self.players),
)

# general info
game.num_hands = self.num_hands

# button and blinds
game.btn_loc = self.btn_loc
game.sb_loc = self.sb_loc
game.bb_loc = self.bb_loc

if not self.is_hand_running():
# read chips
for i in game.player_iter(0):
game.players[i].chips = self.players[i].chips
return game

# replay last hand

# set button location
game.btn_loc = next(self.player_iter(loc=self.btn_loc - 1, reverse=True))

# read chips
for i in game.player_iter(0):
game.players[i].chips = self.hand_history.prehand.player_chips[i]

# cards
deck = self._deck.copy(shuffle=shuffle)
deck.cards = [
card
for card in deck.cards
if card
not in itertools.chain.from_iterable(
self.hand_history.prehand.player_cards.values()
)
]

# stack deck
if self.hand_history.settle:
deck.cards = list(self.hand_history.settle.new_cards) + deck.cards

# player actions in a stack
player_actions: List[Tuple[int, ActionType, Optional[int]]] = []
for bet_round in (
self.hand_history.river,
self.hand_history.turn,
self.hand_history.flop,
self.hand_history.preflop,
):
if bet_round:
deck.cards = [
card for card in deck.cards if card not in bet_round.new_cards
]
deck.cards = bet_round.new_cards + deck.cards
for action in reversed(bet_round.actions):
player_actions.insert(
0, (action.player_id, action.action_type, action.total)
)

# stack player cards on top of deck
for i in self.player_iter(
loc=self.btn_loc, filter_states=(PlayerState.SKIP,), reverse=True
):
deck.cards = self.hands[i] + deck.cards

# swap decks
game._deck = deck

# start hand (deck will deal)
game.start_hand()

while player_actions:
player_id, action, total = player_actions.pop(0)

if player_id != game.current_player:
raise ValueError(
f"Unexpected error when copying: action player {player_id} "
f"is not current player {game.current_player}"
)

game.take_action(action, total=total)

return game

def __copy__(self):
return self.copy(shuffle=False)

def __deepcopy__(self, memodict: dict = None):
memodict = memodict if memodict else {}
return self.copy(shuffle=False)