179 lines
3.9 KiB
Python
179 lines
3.9 KiB
Python
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"<Card value={self.value} suit={self.suit} color={self.color} face={self.face}>"
|
|
|
|
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}]"
|
|
)
|