Coverage for src / beaverbunch / core / card.py: 100.0%
44 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 19:37 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 19:37 +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
23 value of 0.
25 Attributes:
26 suit: The suit of the card (Clubs, Diamonds, Hearts, Spades), or None for a Joker.
27 value: The card's value (1–13 for standard cards, where 1 is Ace and 11-13 are Jack, Queen, King; 0 for Joker).
28 """
29 suit: Suit | None # None for Joker
30 value: int # 1–13, or 0 for Joker
32 @property
33 def points(self) -> int:
34 """Calculates the point value of the card according to the game rules:"""
35 if self.value == 0: # Joker
36 return 50
37 if self.value == 10: # Ten
38 return 0
39 if self.value == 1: # Ace
40 return 1
41 if self.value >= 11: # Face cards
42 return 10
43 return self.value
45 @property
46 def is_joker(self) -> bool:
47 """Returns True if this card is a Joker (no suit and value of 0), False otherwise."""
48 return self.value == 0 and self.suit is None
50 @property
51 def bonus_action(self) -> FaceCard | None:
52 """Returns the FaceCard associated with this card if it has a bonus action, or None if it does not."""
53 try:
54 return FaceCard(self.value)
55 except ValueError:
56 return None
58 def __post_init__(self):
59 if self.is_joker:
60 return
61 if self.suit is None:
62 raise ValueError("Non-joker cards must have a suit")
63 if not isinstance(self.suit, Suit):
64 raise TypeError(f"suit must be a Suit enum, got {type(self.suit)}")
65 if not isinstance(self.value, int):
66 raise TypeError(f"value must be an int, got {type(self.value)}")
67 if not 1 <= self.value <= 13:
68 raise ValueError("Value must be between 1 and 13")