Source code for phevaluator.card

"""Module for card."""

from __future__ import annotations

CARD_DESCRIPTION_LENGTH = 2

# fmt: off
rank_map = {
    "2": 0, "3": 1, "4": 2, "5": 3, "6": 4, "7": 5, "8": 6, "9": 7,
    "T": 8, "J": 9, "Q": 10, "K": 11, "A": 12,
}
suit_map = {
    "C": 0, "D": 1, "H": 2, "S": 3,
    "c": 0, "d": 1, "h": 2, "s": 3,
}
# fmt: on

rank_reverse_map = {value: key for key, value in rank_map.items()}
suit_reverse_map = {value: key for key, value in suit_map.items() if key.islower()}


[docs] class Card: """An immutable card object. Attributes: __id (int): The integer that identifies the card. We can use an integer to represent a card. The two least significant bits represent the 4 suits, ranged from 0-3. The rest of it represent the 13 ranks, ranged from 0-12. More specifically, the ranks are: deuce = 0, trey = 1, four = 2, five = 3, six = 4, seven = 5, eight = 6, nine = 7, ten = 8, jack = 9, queen = 10, king = 11, ace = 12. And the suits are: club = 0, diamond = 1, heart = 2, spade = 3 So that you can use `rank * 4 + suit` to get the card ID. The complete card Id mapping can be found below. The rows are the ranks from 2 to Ace, and the columns are the suits: club, diamond, heart and spade. | | C | D | H | S | | ---: | ---: | ---: | ---: | ---: | | 2 | 0 | 1 | 2 | 3 | | 3 | 4 | 5 | 6 | 7 | | 4 | 8 | 9 | 10 | 11 | | 5 | 12 | 13 | 14 | 15 | | 6 | 16 | 17 | 18 | 19 | | 7 | 20 | 21 | 22 | 23 | | 8 | 24 | 25 | 26 | 27 | | 9 | 28 | 29 | 30 | 31 | | T | 32 | 33 | 34 | 35 | | J | 36 | 37 | 38 | 39 | | Q | 40 | 41 | 42 | 43 | | K | 44 | 45 | 46 | 47 | | A | 48 | 49 | 50 | 51 | __slots__ (list[str]): Explicitly declare data members Raises: ValueError: Construction with invalid string The string parameter of the constructor should be exactly 2 characters. >>> Card("9h") # OK >>> Card("9h ") # ERROR TypeError: Construction with unsupported type The parameter of the constructor should be one of the following types: [int, str, Card]. >>> Card(0) # OK. The 0 stands 2 of Clubs >>> Card("2c") # OK >>> Card("2C") # OK. Capital letter is also accepted. >>> Card(Card(0)) # OK >>> Card(0.0) # ERROR. float is not allowed TypeError: Setting attribute >>> c = Card("2c") >>> c.__id = 1 # ERROR >>> c._Card__id = 1 # ERROR TypeError: Deliting attribute >>> c = Card("2c") >>> del c.__id # ERROR >>> del c._Card__id # ERROR """ __slots__ = ["__id"] __id: int def __init__(self, other: int | str | Card) -> None: """Construct card object. If the passed argument is integer, it's set to `self.__id`. If the passed argument is string, its id is calculated and set to `self.__id`. Thus, the original string is discarded. e.g. Card("2C").describe_card() == "2c" If the passed argument is Card, it's copied. Args: other (int): The integer that identifies the card. other (str): The description of the card. e.g. "2c", "Ah" other (Card): The other card to copy. Examples: Those four variable are same. >>> c1 = Card(0) >>> c2 = Card("2c") >>> c3 = Card("2C") >>> c4 = Card(c1) >>> print(c1, c2, c3, c4) Card("2c") Card("2c") Card("2c") Card("2c") """ card_id = Card.to_id(other) # Note: use base class assignment because assignment to this class is protected # by `Card.__setattr__` # Note: use name mangling: `_Card__id` instead of `Card.__id`. object.__setattr__(self, "_Card__id", card_id) # equiv to `self.__id = card_id` @property def id_(self) -> int: """Return `self.__id`. Returns: int: """ return self.__id
[docs] @staticmethod def to_id(other: int | str | Card) -> int: """Return the Card ID integer as API. If the passed argument is integer, it's returned with doing nothing. If the passed argument is string, its id is calculated. If the passed argument is Card, `other.id_` is returned. Args: other (int): The integer that identifies the card. other (str): The description of the card. e.g. "2c", "Ah" other (Card): The other card to copy. Raises: ValueError: Passed invalid string TypeError: Passed unsupported type Returns: int: Card ID """ if isinstance(other, int): return other if isinstance(other, str): if len(other) != CARD_DESCRIPTION_LENGTH: msg = ( f"The length of value must be {CARD_DESCRIPTION_LENGTH}. " f"passed: {other}" ) raise ValueError(msg) rank, suit, *_ = tuple(other) return rank_map[rank] * 4 + suit_map[suit] if isinstance(other, Card): return other.id_ msg = f"Type of parameter must be int, str or Card. passed: {type(other)}" raise TypeError(msg)
[docs] def describe_rank(self) -> str: """Calculate card rank. Returns: str: The card rank Examples: >>> c1 = Card("2c") >>> c1.describe_rank() "2" >>> c2 = Card("Ah") >>> c2.describe_rank() "A" """ return rank_reverse_map[self.id_ // 4]
[docs] def describe_suit(self) -> str: """Calculate suit. It's lowercased. Returns: str: The suit of the card Examples: >>> c1 = Card("2c") >>> c1.describe_suit() "c" >>> c2 = Card("2H") >>> c2.describe_suit() "h" """ return suit_reverse_map[self.id_ % 4]
[docs] def describe_card(self) -> str: """Return card description. Returns: str: The card description. Examples: >>> c1 = Card("2c") >>> c1.describe_card() "2c" >>> c2 = Card("AH") >>> c2.describe_suit() "Ah" """ return self.describe_rank() + self.describe_suit()
def __eq__(self, other: object) -> bool: """Return equality. This is special method. Args: other (int): This is compared to `int(self)` other (str): This is compared to `str(self)`. It's case-insensitive. other (Card): `other.id_` is compared to `self.id_`. other (Any): This is compared to `self.id_` Returns: bool: The result of `self == other` Examples: >>> Card(0) == Card("2c") == Card("2C") True >>> 3 == Card(3) == 3 True >>> "Ah" == Card("Ah") == "Ah" True >>> "AH" == Card("Ah") == "AH" True """ if isinstance(other, int): return int(self) == other if isinstance(other, str): # case-insensitive return str(self).lower() == other.lower() if isinstance(other, Card): return self.id_ == other.id_ return self.id_ == other def __str__(self) -> str: """str: Special method for `str(self)`. e.g. '2c', 'Ah'.""" return self.describe_card() def __repr__(self) -> str: """str: Special method for `repr(self)`. e.g. Card("2c"), Card("Ah").""" return f'Card("{self.describe_card()}")' def __int__(self) -> int: """int: Special method for `int(self)`.""" return self.id_ def __hash__(self) -> int: """int: Special method for `hash(self)`.""" return hash(self.id_) def __setattr__(self, name: str, value: object) -> None: """Set an attribute. This causes TypeError since assignment is prevented.""" msg = "Card object does not support assignment to attribute" raise TypeError(msg) def __delattr__(self, name: str) -> None: """Delete an attribute. This causes TypeError since deletion is prevented.""" msg = "Card object does not support deletion of attribute" raise TypeError(msg)