# Playing Cards

For this question, we ask you to design a card game using the traditional 52-card deck. We divide this question in to three parts, so you can complete them in order.

## Part One

For the first part, you must design a `Game` class representing the game, and these following functions associated with the class.

• `add_card(suit, value)`: Creates a new card object with a suit from one of the following strings: `Hearts`, `Spades`, `Clubs`, `Diamonds`, and a value from one of the following strings: `A`, `2`~`10`, `J`, `Q`, `K`. This card is represented by `i`, where `i` is an integer indicating how many cards have been created before.
• `card_string(card)`: Returns the string representation of the card represented by `i`. It follows the format `<value> of <suit>`. For example, a card created by `add_card("Spades", "3")` should have a string representation of `3 of Spades`.
• `card_beats(card_a, card_b)`: Check if the card represented by `card_a` beats the one represented by `card_b`. A card beats another card if and only if it has a greater value. The value of the cards are ordered from `A` to `K`.

You may implement these however you like. However, preferably this should be easily expandable to accommodate new requirements.

### Solution

There are numerous approach we can take to design this problem. The sample solution will provide an object-oriented approach, since it allows us to easily add new types of cards to accommodate new requirements.

Different languages have different tools, but the most basic concept in object oriented programming is inheritance, which is a class deriving from a superclass and inheriting its methods. In this situation, a playing card from the 52 is a card. The reason for this design is that we can easily add other types of cards if we want.

Below is an implementation:

 `1` `+` ``from enum import Enum, auto`` `2` `+` `3` `+` ``class Card:`` `4` `+` `` @property`` `5` `+` `` def card_value(self) -> int:`` `6` `+` `` raise NotImplementedError()`` `7` `+` `8` `+` `` def __lt__(self, other):`` `9` `+` `` return self.card_value < other.card_value`` `10` `+` `11` `+` ``class Suit(Enum):`` `12` `+` `` CLUBS = auto()`` `13` `+` `` DIAMONDS = auto()`` `14` `+` `` HEARTS = auto()`` `15` `+` `` SPADES = auto()`` `16` `+` `17` `+` ``class PlayingCard(Card):`` `18` `+` `` SUITS = {`` `19` `+` `` "Clubs": Suit.CLUBS,`` `20` `+` `` "Diamonds": Suit.DIAMONDS,`` `21` `+` `` "Hearts": Suit.HEARTS,`` `22` `+` `` "Spades": Suit.SPADES,`` `23` `+` `` }`` `24` `+` `` SUIT_NAMES = {e: n for n, e in SUITS.items()}`` `25` `+` `` VALUES = {`` `26` `+` `` "A": 1,`` `27` `+` `` **{str(i): i for i in range(2, 11)},`` `28` `+` `` "J": 11,`` `29` `+` `` "Q": 12,`` `30` `+` `` "K": 13,`` `31` `+` `` }`` `32` `+` `` VALUE_NAMES = {e: n for n, e in VALUES.items()}`` `33` `+` `34` `+` `` def __init__(self, suit: str, value: str):`` `35` `+` `` super().__init__()`` `36` `+` `` self.__suit = self.SUITS[suit]`` `37` `+` `` self.__value = self.VALUES[value]`` `38` `+` `39` `+` `` @property`` `40` `+` `` def card_value(self) -> int:`` `41` `+` `` return self.__value`` `42` `+` `43` `+` `` def __str__(self) -> str:`` `44` `+` `` value = self.VALUE_NAMES[self.__value]`` `45` `+` `` suit = self.SUIT_NAMES[self.__suit]`` `46` `+` `` return f"{value} of {suit}"`` `47` `+` `1` `48` ``class Game:`` `2` `49` `` def __init__(self):`` `3` `-` `` # Implement initializer here`` `50` `+` `` self.__cards: list[Card] = []`` `4` `-` `` pass`` `5` `51` `` def add_card(self, suit: str, value: str) -> None:`` `6` `-` `` # Implement function here`` `52` `+` `` self.__cards.append(PlayingCard(suit, value))`` `7` `-` `` pass`` `8` `53` `` def card_string(self, card: int) -> str:`` `9` `-` `` # Implement function here`` `54` `+` `` return str(self.__cards[card])`` `10` `-` `` return ""`` `55` `+` `` def card_beats(self, card_a: Card, card_b: Card) -> bool:`` `11` `-` `` def card_beats(self, card_a: int, card_b: int) -> bool:`` `56` `+` `` return self.__cards[card_a] > self.__cards[card_b]`` `12` `-` `` # Implement function here`` `13` `-` `` return False`` `14` `-` `` `` `15` `57` ``if __name__ == '__main__':`` `16` `58` `` game = Game()`` `17` `59` `` suit, value = input().split()`` `18` `60` `` game.add_card(suit, value)`` `19` `61` `` print(game.card_string(0))`` `20` `62` `` suit, value = input().split()`` `21` `63` `` game.add_card(suit, value)`` `Expand 2 lines ...`

## Part Two

For this part, we ask you to implement the Jokers into the system.

In addition to the functionalities above, also implement the following functions:

• `add_joker(color)`: Creates a Joker card of with `color` of either `Red` or `Black`.
• Joker beats everything else except other jokers. This card is represented by `i`, where `i` is an integer indicating how many cards have been created before, including both normal cards and jokers.
• A joker's string representation is `Red Joker` or `Black Joker`, depending on the color.

You may copy the code from the previous question here.

### Solution

We add a `Joker` class that inherits the base `Card`. For the purpose of this question, its value is `14`, which is greater than other cards. We do not need to write extra logic for comparing Jokers with other cards, since that logic is already there under `Card`.

Below is the updated implementation:

 `1` `+` ``from enum import Enum, auto`` `2` `+` `3` `+` ``class Card:`` `4` `+` `` @property`` `5` `+` `` def card_value(self) -> int:`` `6` `+` `` raise NotImplementedError()`` `7` `+` `8` `+` `` def __lt__(self, other):`` `9` `+` `` return self.card_value < other.card_value`` `10` `+` `11` `+` ``class Suit(Enum):`` `12` `+` `` CLUBS = auto()`` `13` `+` `` DIAMONDS = auto()`` `14` `+` `` HEARTS = auto()`` `15` `+` `` SPADES = auto()`` `16` `+` `17` `+` ``class PlayingCard(Card):`` `18` `+` `` SUITS = {`` `19` `+` `` "Clubs": Suit.CLUBS,`` `20` `+` `` "Diamonds": Suit.DIAMONDS,`` `21` `+` `` "Hearts": Suit.HEARTS,`` `22` `+` `` "Spades": Suit.SPADES,`` `23` `+` `` }`` `24` `+` `` SUIT_NAMES = {e: n for n, e in SUITS.items()}`` `25` `+` `` VALUES = {`` `26` `+` `` "A": 1,`` `27` `+` `` **{str(i): i for i in range(2, 11)},`` `28` `+` `` "J": 11,`` `29` `+` `` "Q": 12,`` `30` `+` `` "K": 13,`` `31` `+` `` }`` `32` `+` `` VALUE_NAMES = {e: n for n, e in VALUES.items()}`` `33` `+` `34` `+` `` def __init__(self, suit: str, value: str):`` `35` `+` `` super().__init__()`` `36` `+` `` self.__suit = self.SUITS[suit]`` `37` `+` `` self.__value = self.VALUES[value]`` `38` `+` `39` `+` `` @property`` `40` `+` `` def card_value(self) -> int:`` `41` `+` `` return self.__value`` `42` `+` `43` `+` `` def __str__(self) -> str:`` `44` `+` `` value = self.VALUE_NAMES[self.__value]`` `45` `+` `` suit = self.SUIT_NAMES[self.__suit]`` `46` `+` `` return f'{value} of {suit}'`` `47` `+` `48` `+` ``class JokerColor(Enum):`` `49` `+` `` RED = auto()`` `50` `+` `` BLACK = auto()`` `51` `+` `52` `+` ``class Joker(Card):`` `53` `+` `` COLORS = {`` `54` `+` `` "Red": JokerColor.RED,`` `55` `+` `` "Black": JokerColor.BLACK,`` `56` `+` `` }`` `57` `+` `58` `+` `` COLOR_NAMES = {e: n for n, e in COLORS.items()}`` `59` `+` `60` `+` `` def __init__(self, color: str):`` `61` `+` `` super().__init__()`` `62` `+` `` self.__color = self.COLORS[color]`` `63` `+` `64` `+` `` @property`` `65` `+` `` def card_value(self):`` `66` `+` `` return 14`` `67` `+` `68` `+` `` def __str__(self) -> str:`` `69` `+` `` return f"{self.COLOR_NAMES[self.__color]} Joker"`` `70` `+` `1` `71` ``class Game:`` `2` `72` `` def __init__(self):`` `3` `-` `` # Implement initializer here`` `73` `+` `` self.__cards: list[Card] = []`` `4` `-` `` pass`` `5` `74` `` def add_card(self, suit: str, value: str) -> None:`` `6` `-` `` # Implement function here`` `75` `+` `` self.__cards.append(PlayingCard(suit, value))`` `7` `-` `` pass`` `8` `76` `` def card_string(self, card: int) -> str:`` `9` `-` `` # Implement function here`` `77` `+` `` return str(self.__cards[card])`` `10` `-` `` return ""`` `11` `78` `` def card_beats(self, card_a: int, card_b: int) -> bool:`` `12` `-` `` # Implement function here`` `79` `+` `` return self.__cards[card_a] > self.__cards[card_b]`` `13` `-` `` return False`` `14` `80` `` def add_joker(self, color: str) -> None:`` `15` `-` `` # Implement function here`` `81` `+` `` self.__cards.append(Joker(color))`` `16` `-` `` pass`` `17` `82` ``if __name__ == '__main__':`` `18` `83` `` game = Game()`` `19` `84` `` suit, value = input().split()`` `20` `85` `` game.add_joker(value) if suit == "Joker" else game.add_card(suit, value)`` `21` `86` `` print(game.card_string(0))`` `22` `87` `` suit, value = input().split()`` `23` `88` `` game.add_joker(value) if suit == "Joker" else game.add_card(suit, value)`` `24` `89` `` print(game.card_string(1))``

## Part Three

This game also involve a concept of a `Hand` and comparing the size of the two hands. For this part, add these following functions to the `Game` class:

• `add_hand(card_indices)`: Create a new `Hand` with cards represented by the list of integer representation of cards `card_indices`. The hand can be represented by `i`, where `i` is the number of hands added before.
• `hand_string(hand)`: Return the string representation of the hand represented by `hand`. It is a list of string representation of cards by their insertion order, separated by `", "`. For example, if `hand` has a 9 of Clubs, K of Hearts, and a Black Joker, the string representation is `"9 of Clubs, K of Hearts, Black Joker"`.
• `beats_hand(hand_a, hand_b)`: Check if the hand represented by `hand_a` beats the hand represented by `hand_b` according to the following rules:
• Starting from the largest card in each hand, compare them. If a card beats another, that hand beats the other hand. Otherwise, compare the next largest card.
• Repeat this process until one hand beats the other, or one hand runs out of cards. If a hand runs out of cards, neither hand beat each other.

### Solution

For this part, we implement the `Hand` class by having it contain a list of cards. When we compare two hands, because we defined a comparison function between two cards, we can sort them using the default sorting algorithm.

Below is the implementation:

 `1` `+` ``from enum import Enum, auto`` `1` `2` ``from typing import List`` `2` `3` `4` `+` ``class Card:`` `5` `+` `` @property`` `6` `+` `` def card_value(self) -> int:`` `7` `+` `` raise NotImplementedError()`` `8` `+` `9` `+` `` def __lt__(self, other):`` `10` `+` `` return self.card_value < other.card_value`` `11` `+` `12` `+` ``class Suit(Enum):`` `13` `+` `` CLUBS = auto()`` `14` `+` `` DIAMONDS = auto()`` `15` `+` `` HEARTS = auto()`` `16` `+` `` SPADES = auto()`` `17` `+` `18` `+` ``class PlayingCard(Card):`` `19` `+` `` SUITS = {`` `20` `+` `` "Clubs": Suit.CLUBS,`` `21` `+` `` "Diamonds": Suit.DIAMONDS,`` `22` `+` `` "Hearts": Suit.HEARTS,`` `23` `+` `` "Spades": Suit.SPADES,`` `24` `+` `` }`` `25` `+` `` SUIT_NAMES = {e: n for n, e in SUITS.items()}`` `26` `+` `` VALUES = {`` `27` `+` `` "A": 1,`` `28` `+` `` **{str(i): i for i in range(2, 11)},`` `29` `+` `` "J": 11,`` `30` `+` `` "Q": 12,`` `31` `+` `` "K": 13,`` `32` `+` `` }`` `33` `+` `` VALUE_NAMES = {e: n for n, e in VALUES.items()}`` `34` `+` `35` `+` `` def __init__(self, suit: str, value: str):`` `36` `+` `` super().__init__()`` `37` `+` `` self.__suit = self.SUITS[suit]`` `38` `+` `` self.__value = self.VALUES[value]`` `39` `+` `40` `+` `` @property`` `41` `+` `` def card_value(self) -> int:`` `42` `+` `` return self.__value`` `43` `+` `44` `+` `` def __str__(self) -> str:`` `45` `+` `` value = self.VALUE_NAMES[self.__value]`` `46` `+` `` suit = self.SUIT_NAMES[self.__suit]`` `47` `+` `` return f'{value} of {suit}'`` `48` `+` `49` `+` ``class JokerColor(Enum):`` `50` `+` `` RED = auto()`` `51` `+` `` BLACK = auto()`` `52` `+` `53` `+` ``class Joker(Card):`` `54` `+` `` COLORS = {`` `55` `+` `` "Red": JokerColor.RED,`` `56` `+` `` "Black": JokerColor.BLACK,`` `57` `+` `` }`` `58` `+` `59` `+` `` COLOR_NAMES = {e: n for n, e in COLORS.items()}`` `60` `+` `61` `+` `` def __init__(self, color: str):`` `62` `+` `` super().__init__()`` `63` `+` `` self.__color = self.COLORS[color]`` `64` `+` `65` `+` `` @property`` `66` `+` `` def card_value(self):`` `67` `+` `` return 14`` `68` `+` `69` `+` `` def __str__(self) -> str:`` `70` `+` `` return f"{self.COLOR_NAMES[self.__color]} Joker"`` `71` `+` `72` `+` ``class Hand:`` `73` `+` `` def __init__(self, cards):`` `74` `+` `` super().__init__()`` `75` `+` `` self.cards = [*cards]`` `76` `+` `77` `+` `` def __str__(self) -> str:`` `78` `+` `` return ", ".join(str(card) for card in self.cards)`` `79` `+` `80` `+` `` def __lt__(self, other):`` `81` `+` `` for card_a, card_b in zip(reversed(sorted(self.cards)), reversed(sorted(other.cards))):`` `82` `+` `` if card_a < card_b:`` `83` `+` `` return True`` `84` `+` `` elif card_b < card_a:`` `85` `+` `` return False`` `86` `+` `` return False`` `87` `+` `3` `88` ``class Game:`` `4` `89` `` def __init__(self):`` `5` `-` `` # Implement initializer here`` `90` `+` `` self.__cards: list[Card] = []`` `6` `-` `` pass`` `91` `+` `` self.__hands: list[Hand] = []`` `7` `92` `8` `93` `` def add_card(self, suit: str, value: str) -> None:`` `9` `-` `` # Implement function here`` `94` `+` `` self.__cards.append(PlayingCard(suit, value))`` `10` `-` `` pass`` `11` `95` `` def card_string(self, card: int) -> str:`` `12` `-` `` # Implement function here`` `96` `+` `` return str(self.__cards[card])`` `13` `-` `` return ""`` `14` `97` `` def card_beats(self, card_a: int, card_b: int) -> bool:`` `15` `-` `` # Implement function here`` `98` `+` `` return self.__cards[card_a] > self.__cards[card_b]`` `16` `-` `` return False`` `17` `99` `` def add_joker(self, color: str) -> None:`` `18` `-` `` # Implement function here`` `100` `+` `` self.__cards.append(Joker(color))`` `19` `-` `` pass`` `20` `101` `` def add_hand(self, card_indices: List[int]) -> None:`` `21` `-` `` # Implement function here`` `102` `+` `` self.__hands.append(Hand([self.__cards[i] for i in card_indices]))`` `22` `-` `` pass`` `23` `103` `` def hand_string(self, hand: int) -> str:`` `24` `-` `` # Implement function here`` `104` `+` `` return str(self.__hands[hand])`` `25` `-` `` return ""`` `26` `105` `` def hand_beats(self, hand_a: int, hand_b: int) -> bool:`` `27` `-` `` # Implement function here`` `106` `+` `` return self.__hands[hand_a] > self.__hands[hand_b]`` `28` `-` `` return False`` `29` `107` ``if __name__ == '__main__':`` `30` `108` `` game = Game()`` `31` `109` `` hand_a_list = []`` `32` `110` `` n_1 = int(input())`` `33` `111` `` for i in range(n_1):`` `34` `112` `` suit, value = input().split()`` `35` `113` `` game.add_joker(value) if suit == "Joker" else game.add_card(suit, value)`` `36` `114` `` hand_a_list.append(i)`` `37` `115` `` game.add_hand(hand_a_list)`` `38` `116` `` print(game.hand_string(0))`` `39` `117` `` hand_b_list = []`` `40` `118` `` n_2 = int(input())`` `Expand 7 lines ...`