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

1from dataclasses import dataclass 

2from enum import Enum 

3 

4 

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" 

11 

12 

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 

18 

19 

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. 

24 

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 

31 

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 

44 

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 

49 

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 

57 

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")