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

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 value of 0. 

23 

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 

30 

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 

43 

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 

48 

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 

56 

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