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.

Try it yourself

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.

Try it yourself

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.

Try it yourself

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 ...