from random import shuffle from typing import Any from typing import Dict from typing import List from typing import Iterable from typing import Optional class EmptyPileError(IndexError): pass class Card(object): _color: Optional[str] _face: Optional[str] _suit: Optional[str] _value: int def __init__( self, value: int, suit: Optional[str] = None, color: Optional[str] = None, face: Optional[str] = None, ): self._color = color self._face = face self._suit = suit self._value = value @property def color(self) -> Optional[str]: return self._color @property def face(self) -> Optional[str]: return self._face @property def suit(self) -> Optional[str]: return self._suit @property def value(self) -> int: return self._value def __lt__(self, other) -> bool: return self.value < other.value def __le__(self, other) -> bool: return self.value <= other.value def __gt__(self, other) -> bool: return self.value > other.value def __ge__(self, other) -> bool: return self.value >= other.value def __eq__(self, other) -> bool: return ( self.value == other.value and self.face == other.face and self.suit == other.suit and self.color == other.color ) def __repr__(self) -> str: return f"" def json(self) -> Dict[str, Any]: return { "value": self.value, "suit": self.suit, "color": self.color, "face": self.face, } class FaceDownCard(Card): def json(self) -> Dict[str, Any]: return { "value": "FACE_DOWN", } class WildCard(Card): def __init__(self): super().__init__(-1, face="Wild", suit="Wild", color="Wild") def __lt__(self, other) -> bool: return True def __le__(self, other) -> bool: return True def __gt__(self, other) -> bool: return True def __ge__(self, other) -> bool: return True def __eq__(self, other) -> bool: return True class Pile(object): # Pile of cards where 0 is the top and -1 is the bottom _pile: List[Card] def __init__(self, cards: Optional[List[Card]] = None): if cards is not None: self._pile = cards else: self._pile = [] def add_top(self, card: Card): self._pile.insert(0, card) def add_bottom(self, card: Card): self._pile.insert(-1, card) def peek_top(self) -> Optional[Card]: if self._pile: return self._pile[0] return None def draw_card(self) -> Card: if not self._pile: raise EmptyPileError("Cannot draw from pile because it's empty") return self._pile.pop(0) def draw_cards(self, num_cards: int = 1) -> Iterable[Card]: return [self.draw_card() for _ in range(0, num_cards)] def shuffle(self): shuffle(self._pile) def __getitem__(self, i): return self._cards[i] def __len__(self) -> int: return len(self._pile) def __str__(self) -> str: if self: return ( f"[[{len(self)-1} cards]]" ) else: return "[[empty pile]]" def json(self) -> List[Dict[str, Any]]: return [c.json() for c in self._pile] class FaceUpPile(Pile): def __str__(self) -> str: top_card = self.peek_top() if top_card: return ( f"[[{str(top_card)} +{len(self)-1} cards]]" ) else: return "[[empty pile]]" class OpenCardSet(Pile): def __str__(self) -> str: cards = ", ".join(( str(card) for card in self._pile )) return ( f"[{cards}]" )