-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcard.py
196 lines (160 loc) · 6.28 KB
/
card.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
from functools import partial
from constants import SUITS, VALUES
from typing import TYPE_CHECKING, Callable, Union
if TYPE_CHECKING:
from game import Game
class WrongCardValue(Exception):
def __init__(
self,
value: Union[int, str],
message: str = "Must be a string containing a number from 2 to 10 or one of four letters: J, Q, K, A",
) -> None:
super().__init__(message, value)
class WrongCardSuit(Exception):
def __init__(
self,
suit: str,
message: str = "Must be a string containing one of four possible suits: clubs, spades, diamonds, hearts",
) -> None:
super().__init__(message, suit)
class CardPlayedOnItself(Exception):
def __init__(
self,
card: "Card",
message: str = "Card cannot be played on itself",
) -> None:
super().__init__(message, repr(card))
class Card:
def __init__(self, value: Union[str, int], suit: str) -> None:
"""
Represents a card from a deck of cards.
:param value: card's value, from 2 to ace
:param suit: card's suit: clubs, spades, diamonds, hearts
:raises WrongCardValue: Is raised if the value is not in the VALUES list
:raises WrongCardSuit: Is raised if the suit is not in the SUITS list
"""
value = str(value).lower()
suit = str(suit).lower()
if value not in VALUES:
raise WrongCardValue(value)
if suit not in SUITS:
raise WrongCardSuit(suit)
self._value: str = value
self._suit: str = suit
if value != "king":
self._play_effect: Callable = self.EFFECT_MAP.get(
self.value, partial(self._no_effect)
)
else:
self._play_effect = self.KING_EFFECT_MAP.get(
self.suit, partial(self._block_king)
)
@property
def value(self) -> str:
return self._value
@property
def suit(self) -> str:
return self._suit
@property
def play_effect(self):
return self._play_effect
def get_image_name(self) -> str:
return f"images/{self.suit}_{self.value}.png"
def can_play(self, played_card: "Card", **game_params) -> bool:
"""
Checks if the card selected by the player can be played
:param played_card: The card that is currently played
:key req_suit: Tuple with required suit and number of turns it will stay
:key req_value: Tuple with required value and number of turns it will stay
:key four_played: True if four was played
:key king_played: True if king was played
:key jack_played: True if jack was played
:key ace_played: True if ace was played
:key penalty: Penalty value
:raises CardPlayedOnItself: If two identical cards are compared
:return: True if the selected card can be played, False otherwise
"""
req_suit: tuple[str, int] = game_params.get("suit", None)
req_value: tuple[str, int] = game_params.get("value", None)
four_played: int = game_params.get("skip", None)
king_played: bool = game_params.get("king", None)
jack_played: bool = game_params.get("jack", None)
ace_played: bool = game_params.get("ace", None)
penalty: int = game_params.get("penalty", None)
if self == played_card:
raise CardPlayedOnItself(self)
conditions = [
(four_played, lambda: played_card.value == "4"),
(
req_value,
lambda: req_value[0] == played_card.value
or played_card.value == "jack",
),
(
req_suit,
lambda: req_suit[0] == played_card.suit or played_card.value == "ace",
),
(
jack_played,
lambda: self.check_compatible(played_card)
and played_card.value not in ["2", "3", "4", "ace"]
and not (played_card.value == "king" and played_card.suit in ["spades", "hearts"])
),
(
ace_played,
lambda: self.check_compatible(played_card)
and played_card.value not in ["2", "3", "4", "jack"]
and not (played_card.value == "king" and played_card.suit in ["spades", "hearts"])
),
(
penalty and not king_played,
lambda: self.check_compatible(played_card)
and played_card.value in ["2", "3"],
),
(king_played, lambda: played_card.value == "king"),
(self.value == "queen" or played_card.value == "queen", lambda: True),
]
for condition, result in conditions:
if condition:
return result()
return self.check_compatible(played_card)
def check_compatible(self, played_card: "Card") -> bool:
return self.value == played_card.value or self.suit == played_card.suit
@staticmethod
def _no_effect(game: "Game"):
pass
@staticmethod
def _draw_cards(game: "Game", number: int) -> None:
game._increase_penalty(number)
@staticmethod
def _skip_next_player(game: "Game") -> None:
game._increment_skip()
@staticmethod
def _request_value(game: "Game") -> None:
game._jack_played()
@staticmethod
def _king_draw_cards(game: "Game", previous: bool = False) -> None:
game._king_played(previous=previous)
@staticmethod
def _block_king(game: "Game") -> None:
game._reset_king()
@staticmethod
def _request_suit(game: "Game") -> None:
game._ace_played()
EFFECT_MAP: dict[str, Callable] = {
"2": partial(_draw_cards, number=2),
"3": partial(_draw_cards, number=3),
"4": partial(_skip_next_player),
"jack": partial(_request_value),
"ace": partial(_request_suit),
}
KING_EFFECT_MAP: dict[str, Callable] = {
"spades": partial(_king_draw_cards, previous=True),
"hearts": partial(_king_draw_cards),
}
def __repr__(self) -> str:
return f"Card('{self.value}', '{self.suit}')"
def __eq__(self, card: object) -> bool:
if not isinstance(card, Card):
return NotImplemented
return self.value == card.value and self.suit == card.suit