Coverage for src / beaverbunch / core / card.py: 100.0%
44 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-05 20:45 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-05 20:45 +0000
1from dataclasses import dataclass
2from enum import Enum
5class Suit(Enum):
6 """Represents the four suits in a standard deck of playing cards."""
7 CLUBS = "Clubs"
8 DIAMONDS = "Diamonds"
9 HEARTS = "Hearts"
10 SPADES = "Spades"
13class FaceCard(Enum):
14 """Represents the face cards in a standard deck of playing cards, which have special actions in the game."""
15 JACK = 11
16 QUEEN = 12
17 KING = 13
20@dataclass(frozen=True)
21class Card:
22 """Represents a playing card, which can be a standard card with a suit and value, or a Joker with no suit and a value of 0.
24 Attributes:
25 suit: The suit of the card (Clubs, Diamonds, Hearts, Spades), or None for a Joker.
26 value: The value of the card (1–13 for standard cards, where 1 is Ace and 11-13 are Jack, Queen, King; 0 for Joker).
27 """
28 suit: Suit | None # None for Joker
29 value: int # 1–13, or 0 for Joker
31 @property
32 def points(self) -> int:
33 """Calculates the point value of the card according to the game rules:"""
34 if self.value == 0: # Joker
35 return 50
36 if self.value == 10: # Ten
37 return 0
38 if self.value == 1: # Ace
39 return 1
40 if self.value >= 11: # Face cards
41 return 10
42 return self.value
44 @property
45 def is_joker(self) -> bool:
46 """Returns True if this card is a Joker (no suit and value of 0), False otherwise."""
47 return self.value == 0 and self.suit is None
49 @property
50 def bonus_action(self) -> FaceCard | None:
51 """Returns the FaceCard associated with this card if it has a bonus action, or None if it does not."""
52 try:
53 return FaceCard(self.value)
54 except ValueError:
55 return None
57 def __post_init__(self):
58 if self.is_joker:
59 return
60 if self.suit is None:
61 raise ValueError("Non-joker cards must have a suit")
62 if not isinstance(self.suit, Suit):
63 raise TypeError(f"suit must be a Suit enum, got {type(self.suit)}")
64 if not isinstance(self.value, int):
65 raise TypeError(f"value must be an int, got {type(self.value)}")
66 if not 1 <= self.value <= 13:
67 raise ValueError("Value must be between 1 and 13")