67 lines
1.7 KiB
Python
67 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from random import Random
|
|
|
|
SUITS = ("c", "d", "h", "s")
|
|
RANK_LABELS = {
|
|
2: "2",
|
|
3: "3",
|
|
4: "4",
|
|
5: "5",
|
|
6: "6",
|
|
7: "7",
|
|
8: "8",
|
|
9: "9",
|
|
10: "T",
|
|
11: "J",
|
|
12: "Q",
|
|
13: "K",
|
|
14: "A",
|
|
}
|
|
LABEL_RANKS = {label: rank for rank, label in RANK_LABELS.items()}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Card:
|
|
rank: int
|
|
suit: str
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.rank not in RANK_LABELS:
|
|
raise ValueError(f"invalid rank: {self.rank}")
|
|
if self.suit not in SUITS:
|
|
raise ValueError(f"invalid suit: {self.suit}")
|
|
|
|
def __str__(self) -> str:
|
|
return f"{RANK_LABELS[self.rank]}{self.suit}"
|
|
|
|
@classmethod
|
|
def parse(cls, value: str) -> "Card":
|
|
if len(value) != 2:
|
|
raise ValueError(f"card must have two characters: {value!r}")
|
|
rank_label = value[0].upper()
|
|
suit = value[1].lower()
|
|
if rank_label not in LABEL_RANKS:
|
|
raise ValueError(f"invalid rank label: {rank_label}")
|
|
return cls(LABEL_RANKS[rank_label], suit)
|
|
|
|
|
|
class Deck:
|
|
def __init__(self, rng: Random | None = None) -> None:
|
|
self._rng = rng or Random()
|
|
self._cards = [Card(rank, suit) for suit in SUITS for rank in range(2, 15)]
|
|
self._rng.shuffle(self._cards)
|
|
|
|
def draw(self, count: int = 1) -> list[Card]:
|
|
if count < 1:
|
|
raise ValueError("count must be positive")
|
|
if len(self._cards) < count:
|
|
raise ValueError("deck does not have enough cards")
|
|
drawn = self._cards[-count:]
|
|
del self._cards[-count:]
|
|
return drawn
|
|
|
|
def burn(self) -> None:
|
|
self.draw(1)
|