import abc
import os
import logging
from typing import Optional, Iterable, Tuple, Union
from deprecated.sphinx import versionadded
from texasholdem.game.action_type import ActionType
from texasholdem.game.game import TexasHoldEm
logger = logging.getLogger(__name__)
[docs]
class AbstractGUI(abc.ABC):
"""
This class provides a recommended outline of the methods that every TexasHoldEm GUI
should implement. It also comes with a few convenience methods for implementations,
including :meth:`run_step` which runs a complete step of the game (display state,
take input, show state, etc.) and :meth:`replay_history` which allows the user
to step through the given history.
Arguments:
game (TexasHoldEm, optional): The game object to attach to, all methods will
default to this game. (Not necessary if only showing the history)
visible_players (Iterable[int], optional): The players whose cards should be
displayed whenever the :meth:`display_state` method is called, defaults to every
player.
enable_animation (bool): If set to True, will play animations, default True.
no_wait (bool): If set to True, disables waiting mechanisms and will not block.
Attributes:
game (TexasHoldEm, optional): The game object to attach to, all methods will
default to this game. (Not necessary if only showing the history)
visible_players (list[int], optional): The players whose cards should be displayed
whenever the :meth:`display_state` method is called, defaults to every player.
enable_animation (bool): If set to True, will play animations, default True.
no_wait (bool): If set to True, disables waiting mechanisms and will not block.
"""
def __init__(
self,
game: Optional[TexasHoldEm] = None,
visible_players: Optional[Iterable[int]] = None,
enable_animation: bool = True,
no_wait: bool = False,
):
self.game = game
self.visible_players = visible_players
self.no_wait = no_wait
self.enable_animation = enable_animation
# All players visible by default
if visible_players is None and game:
self.set_visible_players(range(self.game.max_players))
[docs]
@versionadded(version="0.7.0")
def set_visible_players(self, visible_players: Iterable[int]):
"""
Make the given players' cards visible.
Arguments:
visible_players (Iterable[int]): The players whose cards should be visible when the
:meth:`display_state` method is called.
Raises:
ValueError: If any of the player ids given are invalid.
"""
if not visible_players:
self.visible_players = visible_players
return
sorted_players = sorted(list(visible_players))
if len(sorted_players) > self.game.max_players:
raise ValueError(
"Expected length of visible players to be <= number of players. "
f"Expected <= {self.game.max_players}, Got {len(sorted_players)}."
)
for player_id, criteria in (
(sorted_players[0], lambda i: i < 0),
(sorted_players[-1], lambda i: i > self.game.max_players - 1),
):
if criteria(player_id):
raise ValueError(f"Unexpected player id {player_id}")
self.visible_players = sorted_players
[docs]
@versionadded(version="0.7.0")
def hide(self):
"""
Hide the GUI.
"""
raise NotImplementedError()
[docs]
@versionadded(version="0.7.0")
def refresh(self):
"""
Refresh the GUI.
"""
[docs]
@versionadded(version="0.7.0")
def wait_until_prompted(self):
"""
Wait until the user gives the appropriate signal.
"""
[docs]
@versionadded(version="0.7.0")
def display_state(self):
"""
Display the state of the game.
"""
raise NotImplementedError()
[docs]
@versionadded(version="0.7.0")
def display_error(self, error: str):
"""
Display any potential errors from users (malformed input, invalid action, etc.)
Arguments:
error (str): The error message
"""
[docs]
@versionadded(version="0.7.0")
def display_action(self):
"""
Display the most recent action
"""
[docs]
@versionadded(version="0.7.0")
def display_win(self):
"""
Display the winners of the hand, (and everyone's cards that didn't fold)
"""
raise NotImplementedError()
[docs]
@versionadded(version="0.7.0")
def run_step(self):
"""
Runs a complete GUI step of the hand:
- Display the game state
- Prompt for action from the user until valid
- Take the action and display it
- Display the winners if the hand ended
This is included as a convenience method built from the other necessary
abstract methods.
"""
if not self.game.is_hand_running():
return
self.display_state()
# Prompt for action input until valid
while True:
try:
self.prompt_input()
action, total = self.accept_input()
self.game.validate_move(action=action, total=total, throws=True)
break
except ValueError as err:
logger.warning("Caught error: %s.", str(err), exc_info=err)
self.display_error(str(err))
# Take the action in the game
self.game.take_action(action, total=total)
# Announce the move
self.display_action()
# display game after running action
self.display_state()
# Display the winners if the hand ended
if not self.game.is_hand_running():
self.display_win()
[docs]
@versionadded(version="0.7.0")
def replay_history(self, path: Union[str, os.PathLike]):
"""
Replays the given history, going forward when prompted.
This is included as a convenience method built from the other necessary
abstract methods.
"""
old_game = self.game
old_visible_players = self.visible_players
for state in TexasHoldEm.import_history(path):
self.game = state
self.set_visible_players(range(self.game.max_players))
self.refresh()
self.display_action()
self.display_state()
self.wait_until_prompted()
self.game = old_game
self.visible_players = old_visible_players