-
Notifications
You must be signed in to change notification settings - Fork 0
/
deck.py
174 lines (141 loc) · 5.68 KB
/
deck.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
import itertools
import random
import cards
class Deck(object):
"""
The Deck class is responsible for holding current information about a
player's deck and exposing the relvant methods for adding, removing and
drawing cards from it.
The relevant variables are:
draw_pile - the shuffled deck to draw from
hand - the current cards in hand
discard_pile - the discarded cards which will be shuffled into draw
trash_pile - destroyed cards that are removed from the game
Convention will be that the 0th element of a list is the "top", where
this is a meaningful distinction. These are internally stored as lists,
since we actually care about order at this level.
"""
# Default constants for the game
HAND_SIZE = 5
def __init__(self, deck=cards.STARTING_DECK):
"""Initializes the default deck (3 estate, 7 copper).
Note this does not draw the initial hand.
"""
# Turn level variables for # of buys, # of actions and amount of treasure.
# Note that these all get rewritten and modified by the hand and played
# cards.
self.buys = 0
self.actions = 0
self.treasure = 0
self.draw_pile = []
self.hand = []
self.discard_pile = []
self.trash_pile = []
self.bought_pile = []
self.init_draw_pile(deck)
def init_draw_pile(self, cards):
"""Initialize the draw pile with the passed dict of card counts."""
for card, count in cards.items():
for _ in range(count):
self.draw_pile.append(card)
# And reshuffle for randomness
random.shuffle(self.draw_pile)
def draw(self, num):
"""Draws the num cards from the draw pile into hand and updates vars.
Throws an exception if asked to draw more cards than are available.
"""
if num < 0:
raise DrawingNegativeCardsException
for _ in range(num):
card = self._draw_one()
self.hand.append(card)
self._update_deck_vars()
def _draw_one(self):
"""Handles drawing a single card from the deck, shuffling if needed."""
if len(self.draw_pile) + len(self.discard_pile) == 0:
raise DrawingWithNoCardsException
if len(self.draw_pile) == 0:
self.reshuffle_discard()
return self.draw_pile.pop(0)
def _update_deck_vars(self):
"""Uses the current hand and played cards to recalculate variables."""
self.buys = 1
self.actions = 1
self.treasure = 0
for card in self.hand:
self.treasure += card.plus_treasure
def _can_buy(self, card):
"""Tests whether the current hand has enough treasure and buys to buy a card."""
return (self.treasure >= card.price
and self.buys >= 1)
def buy_card(self, card, game):
"""Buys a card from the bank.
Responsible for the following actions:
- check if the card can be bought (buys and treasure)
- check if the card exists in the bank
if so,
- decrement bank supply
- reduce buys and treasure appropriately
- add card to discard pile
"""
if self._can_buy(card) and game._can_buy(card):
game.buy_card(card)
self.treasure -= card.price
self.buys -= 1
self.discard_pile.append(card)
else:
raise CannotBuyException
def _can_action(self, card):
"""Tests whether the current hand has enough actions and play an action card."""
return self.actions >= 1
def play_action(self, card):
"""Plays an action card from the player's hand.
Responsible for the following actions:
- check if the card can be played (available actions)
- check if the card exists in the hand
if so,
- transfer card from hand to played_pile
- add actions/buys/treasure as appropriate
"""
# Check if we have enough actions and this card is in our hand
if self.can_action(card) and card in self.hand:
# Remove an action for playing this card
self.actions -= 1
# Add any actions, treasure, buys and/or cards
self.buys += card.plus_buys_on_play
self.actions += card.plus_actions_on_play
self.treasure += card.plus_treasure_on_play
self.draw(card.plus_cards_on_play)
# Move card from hand to played_pile
self._play_card_from_hand(card)
else:
raise CannotActionException
return self.actions >= 1
def _play_card_from_hand(self, card):
"""Take card from the hand and transfer to played_pile. DO NOT trigger
any of the cards effects."""
self.hand.remove(card)
self.played_pile.append(card)
def reshuffle_discard(self):
"""Reshuffles the discard pile into the draw pile."""
self.draw_pile = self.discard_pile[:]
self.discard_pile = []
random.shuffle(self.draw_pile)
def calc_total_vp(self):
"""Calculates total VP in all piles (for end game)."""
total_vp = 0
for card in itertools.chain(self.hand, self.discard_pile, self.draw_pile):
total_vp += card.vp()
return total_vp
"""
These are here so we can have meaningful exception names.
No actual functionality aside from the names.
"""
class DrawingNegativeCardsException(Exception):
pass
class DrawingWithNoCardsException(Exception):
pass
class CannotBuyException(Exception):
pass
class CannotActionException(Exception):
pass