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)