1 | #! /usr/bin/env python
|
---|
2 |
|
---|
3 | """Solitaire game, much like the one that comes with MS Windows.
|
---|
4 |
|
---|
5 | Limitations:
|
---|
6 |
|
---|
7 | - No cute graphical images for the playing cards faces or backs.
|
---|
8 | - No scoring or timer.
|
---|
9 | - No undo.
|
---|
10 | - No option to turn 3 cards at a time.
|
---|
11 | - No keyboard shortcuts.
|
---|
12 | - Less fancy animation when you win.
|
---|
13 | - The determination of which stack you drag to is more relaxed.
|
---|
14 |
|
---|
15 | Apology:
|
---|
16 |
|
---|
17 | I'm not much of a card player, so my terminology in these comments may
|
---|
18 | at times be a little unusual. If you have suggestions, please let me
|
---|
19 | know!
|
---|
20 |
|
---|
21 | """
|
---|
22 |
|
---|
23 | # Imports
|
---|
24 |
|
---|
25 | import math
|
---|
26 | import random
|
---|
27 |
|
---|
28 | from Tkinter import *
|
---|
29 | from Canvas import Rectangle, CanvasText, Group, Window
|
---|
30 |
|
---|
31 |
|
---|
32 | # Fix a bug in Canvas.Group as distributed in Python 1.4. The
|
---|
33 | # distributed bind() method is broken. Rather than asking you to fix
|
---|
34 | # the source, we fix it here by deriving a subclass:
|
---|
35 |
|
---|
36 | class Group(Group):
|
---|
37 | def bind(self, sequence=None, command=None):
|
---|
38 | return self.canvas.tag_bind(self.id, sequence, command)
|
---|
39 |
|
---|
40 |
|
---|
41 | # Constants determining the size and lay-out of cards and stacks. We
|
---|
42 | # work in a "grid" where each card/stack is surrounded by MARGIN
|
---|
43 | # pixels of space on each side, so adjacent stacks are separated by
|
---|
44 | # 2*MARGIN pixels. OFFSET is the offset used for displaying the
|
---|
45 | # face down cards in the row stacks.
|
---|
46 |
|
---|
47 | CARDWIDTH = 100
|
---|
48 | CARDHEIGHT = 150
|
---|
49 | MARGIN = 10
|
---|
50 | XSPACING = CARDWIDTH + 2*MARGIN
|
---|
51 | YSPACING = CARDHEIGHT + 4*MARGIN
|
---|
52 | OFFSET = 5
|
---|
53 |
|
---|
54 | # The background color, green to look like a playing table. The
|
---|
55 | # standard green is way too bright, and dark green is way to dark, so
|
---|
56 | # we use something in between. (There are a few more colors that
|
---|
57 | # could be customized, but they are less controversial.)
|
---|
58 |
|
---|
59 | BACKGROUND = '#070'
|
---|
60 |
|
---|
61 |
|
---|
62 | # Suits and colors. The values of the symbolic suit names are the
|
---|
63 | # strings used to display them (you change these and VALNAMES to
|
---|
64 | # internationalize the game). The COLOR dictionary maps suit names to
|
---|
65 | # colors (red and black) which must be Tk color names. The keys() of
|
---|
66 | # the COLOR dictionary conveniently provides us with a list of all
|
---|
67 | # suits (in arbitrary order).
|
---|
68 |
|
---|
69 | HEARTS = 'Heart'
|
---|
70 | DIAMONDS = 'Diamond'
|
---|
71 | CLUBS = 'Club'
|
---|
72 | SPADES = 'Spade'
|
---|
73 |
|
---|
74 | RED = 'red'
|
---|
75 | BLACK = 'black'
|
---|
76 |
|
---|
77 | COLOR = {}
|
---|
78 | for s in (HEARTS, DIAMONDS):
|
---|
79 | COLOR[s] = RED
|
---|
80 | for s in (CLUBS, SPADES):
|
---|
81 | COLOR[s] = BLACK
|
---|
82 |
|
---|
83 | ALLSUITS = COLOR.keys()
|
---|
84 | NSUITS = len(ALLSUITS)
|
---|
85 |
|
---|
86 |
|
---|
87 | # Card values are 1-13. We also define symbolic names for the picture
|
---|
88 | # cards. ALLVALUES is a list of all card values.
|
---|
89 |
|
---|
90 | ACE = 1
|
---|
91 | JACK = 11
|
---|
92 | QUEEN = 12
|
---|
93 | KING = 13
|
---|
94 | ALLVALUES = range(1, 14) # (one more than the highest value)
|
---|
95 | NVALUES = len(ALLVALUES)
|
---|
96 |
|
---|
97 |
|
---|
98 | # VALNAMES is a list that maps a card value to string. It contains a
|
---|
99 | # dummy element at index 0 so it can be indexed directly with the card
|
---|
100 | # value.
|
---|
101 |
|
---|
102 | VALNAMES = ["", "A"] + map(str, range(2, 11)) + ["J", "Q", "K"]
|
---|
103 |
|
---|
104 |
|
---|
105 | # Solitaire constants. The only one I can think of is the number of
|
---|
106 | # row stacks.
|
---|
107 |
|
---|
108 | NROWS = 7
|
---|
109 |
|
---|
110 |
|
---|
111 | # The rest of the program consists of class definitions. These are
|
---|
112 | # further described in their documentation strings.
|
---|
113 |
|
---|
114 |
|
---|
115 | class Card:
|
---|
116 |
|
---|
117 | """A playing card.
|
---|
118 |
|
---|
119 | A card doesn't record to which stack it belongs; only the stack
|
---|
120 | records this (it turns out that we always know this from the
|
---|
121 | context, and this saves a ``double update'' with potential for
|
---|
122 | inconsistencies).
|
---|
123 |
|
---|
124 | Public methods:
|
---|
125 |
|
---|
126 | moveto(x, y) -- move the card to an absolute position
|
---|
127 | moveby(dx, dy) -- move the card by a relative offset
|
---|
128 | tkraise() -- raise the card to the top of its stack
|
---|
129 | showface(), showback() -- turn the card face up or down & raise it
|
---|
130 |
|
---|
131 | Public read-only instance variables:
|
---|
132 |
|
---|
133 | suit, value, color -- the card's suit, value and color
|
---|
134 | face_shown -- true when the card is shown face up, else false
|
---|
135 |
|
---|
136 | Semi-public read-only instance variables (XXX should be made
|
---|
137 | private):
|
---|
138 |
|
---|
139 | group -- the Canvas.Group representing the card
|
---|
140 | x, y -- the position of the card's top left corner
|
---|
141 |
|
---|
142 | Private instance variables:
|
---|
143 |
|
---|
144 | __back, __rect, __text -- the canvas items making up the card
|
---|
145 |
|
---|
146 | (To show the card face up, the text item is placed in front of
|
---|
147 | rect and the back is placed behind it. To show it face down, this
|
---|
148 | is reversed. The card is created face down.)
|
---|
149 |
|
---|
150 | """
|
---|
151 |
|
---|
152 | def __init__(self, suit, value, canvas):
|
---|
153 | """Card constructor.
|
---|
154 |
|
---|
155 | Arguments are the card's suit and value, and the canvas widget.
|
---|
156 |
|
---|
157 | The card is created at position (0, 0), with its face down
|
---|
158 | (adding it to a stack will position it according to that
|
---|
159 | stack's rules).
|
---|
160 |
|
---|
161 | """
|
---|
162 | self.suit = suit
|
---|
163 | self.value = value
|
---|
164 | self.color = COLOR[suit]
|
---|
165 | self.face_shown = 0
|
---|
166 |
|
---|
167 | self.x = self.y = 0
|
---|
168 | self.group = Group(canvas)
|
---|
169 |
|
---|
170 | text = "%s %s" % (VALNAMES[value], suit)
|
---|
171 | self.__text = CanvasText(canvas, CARDWIDTH//2, 0,
|
---|
172 | anchor=N, fill=self.color, text=text)
|
---|
173 | self.group.addtag_withtag(self.__text)
|
---|
174 |
|
---|
175 | self.__rect = Rectangle(canvas, 0, 0, CARDWIDTH, CARDHEIGHT,
|
---|
176 | outline='black', fill='white')
|
---|
177 | self.group.addtag_withtag(self.__rect)
|
---|
178 |
|
---|
179 | self.__back = Rectangle(canvas, MARGIN, MARGIN,
|
---|
180 | CARDWIDTH-MARGIN, CARDHEIGHT-MARGIN,
|
---|
181 | outline='black', fill='blue')
|
---|
182 | self.group.addtag_withtag(self.__back)
|
---|
183 |
|
---|
184 | def __repr__(self):
|
---|
185 | """Return a string for debug print statements."""
|
---|
186 | return "Card(%r, %r)" % (self.suit, self.value)
|
---|
187 |
|
---|
188 | def moveto(self, x, y):
|
---|
189 | """Move the card to absolute position (x, y)."""
|
---|
190 | self.moveby(x - self.x, y - self.y)
|
---|
191 |
|
---|
192 | def moveby(self, dx, dy):
|
---|
193 | """Move the card by (dx, dy)."""
|
---|
194 | self.x = self.x + dx
|
---|
195 | self.y = self.y + dy
|
---|
196 | self.group.move(dx, dy)
|
---|
197 |
|
---|
198 | def tkraise(self):
|
---|
199 | """Raise the card above all other objects in its canvas."""
|
---|
200 | self.group.tkraise()
|
---|
201 |
|
---|
202 | def showface(self):
|
---|
203 | """Turn the card's face up."""
|
---|
204 | self.tkraise()
|
---|
205 | self.__rect.tkraise()
|
---|
206 | self.__text.tkraise()
|
---|
207 | self.face_shown = 1
|
---|
208 |
|
---|
209 | def showback(self):
|
---|
210 | """Turn the card's face down."""
|
---|
211 | self.tkraise()
|
---|
212 | self.__rect.tkraise()
|
---|
213 | self.__back.tkraise()
|
---|
214 | self.face_shown = 0
|
---|
215 |
|
---|
216 |
|
---|
217 | class Stack:
|
---|
218 |
|
---|
219 | """A generic stack of cards.
|
---|
220 |
|
---|
221 | This is used as a base class for all other stacks (e.g. the deck,
|
---|
222 | the suit stacks, and the row stacks).
|
---|
223 |
|
---|
224 | Public methods:
|
---|
225 |
|
---|
226 | add(card) -- add a card to the stack
|
---|
227 | delete(card) -- delete a card from the stack
|
---|
228 | showtop() -- show the top card (if any) face up
|
---|
229 | deal() -- delete and return the top card, or None if empty
|
---|
230 |
|
---|
231 | Method that subclasses may override:
|
---|
232 |
|
---|
233 | position(card) -- move the card to its proper (x, y) position
|
---|
234 |
|
---|
235 | The default position() method places all cards at the stack's
|
---|
236 | own (x, y) position.
|
---|
237 |
|
---|
238 | userclickhandler(), userdoubleclickhandler() -- called to do
|
---|
239 | subclass specific things on single and double clicks
|
---|
240 |
|
---|
241 | The default user (single) click handler shows the top card
|
---|
242 | face up. The default user double click handler calls the user
|
---|
243 | single click handler.
|
---|
244 |
|
---|
245 | usermovehandler(cards) -- called to complete a subpile move
|
---|
246 |
|
---|
247 | The default user move handler moves all moved cards back to
|
---|
248 | their original position (by calling the position() method).
|
---|
249 |
|
---|
250 | Private methods:
|
---|
251 |
|
---|
252 | clickhandler(event), doubleclickhandler(event),
|
---|
253 | motionhandler(event), releasehandler(event) -- event handlers
|
---|
254 |
|
---|
255 | The default event handlers turn the top card of the stack with
|
---|
256 | its face up on a (single or double) click, and also support
|
---|
257 | moving a subpile around.
|
---|
258 |
|
---|
259 | startmoving(event) -- begin a move operation
|
---|
260 | finishmoving() -- finish a move operation
|
---|
261 |
|
---|
262 | """
|
---|
263 |
|
---|
264 | def __init__(self, x, y, game=None):
|
---|
265 | """Stack constructor.
|
---|
266 |
|
---|
267 | Arguments are the stack's nominal x and y position (the top
|
---|
268 | left corner of the first card placed in the stack), and the
|
---|
269 | game object (which is used to get the canvas; subclasses use
|
---|
270 | the game object to find other stacks).
|
---|
271 |
|
---|
272 | """
|
---|
273 | self.x = x
|
---|
274 | self.y = y
|
---|
275 | self.game = game
|
---|
276 | self.cards = []
|
---|
277 | self.group = Group(self.game.canvas)
|
---|
278 | self.group.bind('<1>', self.clickhandler)
|
---|
279 | self.group.bind('<Double-1>', self.doubleclickhandler)
|
---|
280 | self.group.bind('<B1-Motion>', self.motionhandler)
|
---|
281 | self.group.bind('<ButtonRelease-1>', self.releasehandler)
|
---|
282 | self.makebottom()
|
---|
283 |
|
---|
284 | def makebottom(self):
|
---|
285 | pass
|
---|
286 |
|
---|
287 | def __repr__(self):
|
---|
288 | """Return a string for debug print statements."""
|
---|
289 | return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y)
|
---|
290 |
|
---|
291 | # Public methods
|
---|
292 |
|
---|
293 | def add(self, card):
|
---|
294 | self.cards.append(card)
|
---|
295 | card.tkraise()
|
---|
296 | self.position(card)
|
---|
297 | self.group.addtag_withtag(card.group)
|
---|
298 |
|
---|
299 | def delete(self, card):
|
---|
300 | self.cards.remove(card)
|
---|
301 | card.group.dtag(self.group)
|
---|
302 |
|
---|
303 | def showtop(self):
|
---|
304 | if self.cards:
|
---|
305 | self.cards[-1].showface()
|
---|
306 |
|
---|
307 | def deal(self):
|
---|
308 | if not self.cards:
|
---|
309 | return None
|
---|
310 | card = self.cards[-1]
|
---|
311 | self.delete(card)
|
---|
312 | return card
|
---|
313 |
|
---|
314 | # Subclass overridable methods
|
---|
315 |
|
---|
316 | def position(self, card):
|
---|
317 | card.moveto(self.x, self.y)
|
---|
318 |
|
---|
319 | def userclickhandler(self):
|
---|
320 | self.showtop()
|
---|
321 |
|
---|
322 | def userdoubleclickhandler(self):
|
---|
323 | self.userclickhandler()
|
---|
324 |
|
---|
325 | def usermovehandler(self, cards):
|
---|
326 | for card in cards:
|
---|
327 | self.position(card)
|
---|
328 |
|
---|
329 | # Event handlers
|
---|
330 |
|
---|
331 | def clickhandler(self, event):
|
---|
332 | self.finishmoving() # In case we lost an event
|
---|
333 | self.userclickhandler()
|
---|
334 | self.startmoving(event)
|
---|
335 |
|
---|
336 | def motionhandler(self, event):
|
---|
337 | self.keepmoving(event)
|
---|
338 |
|
---|
339 | def releasehandler(self, event):
|
---|
340 | self.keepmoving(event)
|
---|
341 | self.finishmoving()
|
---|
342 |
|
---|
343 | def doubleclickhandler(self, event):
|
---|
344 | self.finishmoving() # In case we lost an event
|
---|
345 | self.userdoubleclickhandler()
|
---|
346 | self.startmoving(event)
|
---|
347 |
|
---|
348 | # Move internals
|
---|
349 |
|
---|
350 | moving = None
|
---|
351 |
|
---|
352 | def startmoving(self, event):
|
---|
353 | self.moving = None
|
---|
354 | tags = self.game.canvas.gettags('current')
|
---|
355 | for i in range(len(self.cards)):
|
---|
356 | card = self.cards[i]
|
---|
357 | if card.group.tag in tags:
|
---|
358 | break
|
---|
359 | else:
|
---|
360 | return
|
---|
361 | if not card.face_shown:
|
---|
362 | return
|
---|
363 | self.moving = self.cards[i:]
|
---|
364 | self.lastx = event.x
|
---|
365 | self.lasty = event.y
|
---|
366 | for card in self.moving:
|
---|
367 | card.tkraise()
|
---|
368 |
|
---|
369 | def keepmoving(self, event):
|
---|
370 | if not self.moving:
|
---|
371 | return
|
---|
372 | dx = event.x - self.lastx
|
---|
373 | dy = event.y - self.lasty
|
---|
374 | self.lastx = event.x
|
---|
375 | self.lasty = event.y
|
---|
376 | if dx or dy:
|
---|
377 | for card in self.moving:
|
---|
378 | card.moveby(dx, dy)
|
---|
379 |
|
---|
380 | def finishmoving(self):
|
---|
381 | cards = self.moving
|
---|
382 | self.moving = None
|
---|
383 | if cards:
|
---|
384 | self.usermovehandler(cards)
|
---|
385 |
|
---|
386 |
|
---|
387 | class Deck(Stack):
|
---|
388 |
|
---|
389 | """The deck is a stack with support for shuffling.
|
---|
390 |
|
---|
391 | New methods:
|
---|
392 |
|
---|
393 | fill() -- create the playing cards
|
---|
394 | shuffle() -- shuffle the playing cards
|
---|
395 |
|
---|
396 | A single click moves the top card to the game's open deck and
|
---|
397 | moves it face up; if we're out of cards, it moves the open deck
|
---|
398 | back to the deck.
|
---|
399 |
|
---|
400 | """
|
---|
401 |
|
---|
402 | def makebottom(self):
|
---|
403 | bottom = Rectangle(self.game.canvas,
|
---|
404 | self.x, self.y,
|
---|
405 | self.x+CARDWIDTH, self.y+CARDHEIGHT,
|
---|
406 | outline='black', fill=BACKGROUND)
|
---|
407 | self.group.addtag_withtag(bottom)
|
---|
408 |
|
---|
409 | def fill(self):
|
---|
410 | for suit in ALLSUITS:
|
---|
411 | for value in ALLVALUES:
|
---|
412 | self.add(Card(suit, value, self.game.canvas))
|
---|
413 |
|
---|
414 | def shuffle(self):
|
---|
415 | n = len(self.cards)
|
---|
416 | newcards = []
|
---|
417 | for i in randperm(n):
|
---|
418 | newcards.append(self.cards[i])
|
---|
419 | self.cards = newcards
|
---|
420 |
|
---|
421 | def userclickhandler(self):
|
---|
422 | opendeck = self.game.opendeck
|
---|
423 | card = self.deal()
|
---|
424 | if not card:
|
---|
425 | while 1:
|
---|
426 | card = opendeck.deal()
|
---|
427 | if not card:
|
---|
428 | break
|
---|
429 | self.add(card)
|
---|
430 | card.showback()
|
---|
431 | else:
|
---|
432 | self.game.opendeck.add(card)
|
---|
433 | card.showface()
|
---|
434 |
|
---|
435 |
|
---|
436 | def randperm(n):
|
---|
437 | """Function returning a random permutation of range(n)."""
|
---|
438 | r = range(n)
|
---|
439 | x = []
|
---|
440 | while r:
|
---|
441 | i = random.choice(r)
|
---|
442 | x.append(i)
|
---|
443 | r.remove(i)
|
---|
444 | return x
|
---|
445 |
|
---|
446 |
|
---|
447 | class OpenStack(Stack):
|
---|
448 |
|
---|
449 | def acceptable(self, cards):
|
---|
450 | return 0
|
---|
451 |
|
---|
452 | def usermovehandler(self, cards):
|
---|
453 | card = cards[0]
|
---|
454 | stack = self.game.closeststack(card)
|
---|
455 | if not stack or stack is self or not stack.acceptable(cards):
|
---|
456 | Stack.usermovehandler(self, cards)
|
---|
457 | else:
|
---|
458 | for card in cards:
|
---|
459 | self.delete(card)
|
---|
460 | stack.add(card)
|
---|
461 | self.game.wincheck()
|
---|
462 |
|
---|
463 | def userdoubleclickhandler(self):
|
---|
464 | if not self.cards:
|
---|
465 | return
|
---|
466 | card = self.cards[-1]
|
---|
467 | if not card.face_shown:
|
---|
468 | self.userclickhandler()
|
---|
469 | return
|
---|
470 | for s in self.game.suits:
|
---|
471 | if s.acceptable([card]):
|
---|
472 | self.delete(card)
|
---|
473 | s.add(card)
|
---|
474 | self.game.wincheck()
|
---|
475 | break
|
---|
476 |
|
---|
477 |
|
---|
478 | class SuitStack(OpenStack):
|
---|
479 |
|
---|
480 | def makebottom(self):
|
---|
481 | bottom = Rectangle(self.game.canvas,
|
---|
482 | self.x, self.y,
|
---|
483 | self.x+CARDWIDTH, self.y+CARDHEIGHT,
|
---|
484 | outline='black', fill='')
|
---|
485 |
|
---|
486 | def userclickhandler(self):
|
---|
487 | pass
|
---|
488 |
|
---|
489 | def userdoubleclickhandler(self):
|
---|
490 | pass
|
---|
491 |
|
---|
492 | def acceptable(self, cards):
|
---|
493 | if len(cards) != 1:
|
---|
494 | return 0
|
---|
495 | card = cards[0]
|
---|
496 | if not self.cards:
|
---|
497 | return card.value == ACE
|
---|
498 | topcard = self.cards[-1]
|
---|
499 | return card.suit == topcard.suit and card.value == topcard.value + 1
|
---|
500 |
|
---|
501 |
|
---|
502 | class RowStack(OpenStack):
|
---|
503 |
|
---|
504 | def acceptable(self, cards):
|
---|
505 | card = cards[0]
|
---|
506 | if not self.cards:
|
---|
507 | return card.value == KING
|
---|
508 | topcard = self.cards[-1]
|
---|
509 | if not topcard.face_shown:
|
---|
510 | return 0
|
---|
511 | return card.color != topcard.color and card.value == topcard.value - 1
|
---|
512 |
|
---|
513 | def position(self, card):
|
---|
514 | y = self.y
|
---|
515 | for c in self.cards:
|
---|
516 | if c == card:
|
---|
517 | break
|
---|
518 | if c.face_shown:
|
---|
519 | y = y + 2*MARGIN
|
---|
520 | else:
|
---|
521 | y = y + OFFSET
|
---|
522 | card.moveto(self.x, y)
|
---|
523 |
|
---|
524 |
|
---|
525 | class Solitaire:
|
---|
526 |
|
---|
527 | def __init__(self, master):
|
---|
528 | self.master = master
|
---|
529 |
|
---|
530 | self.canvas = Canvas(self.master,
|
---|
531 | background=BACKGROUND,
|
---|
532 | highlightthickness=0,
|
---|
533 | width=NROWS*XSPACING,
|
---|
534 | height=3*YSPACING + 20 + MARGIN)
|
---|
535 | self.canvas.pack(fill=BOTH, expand=TRUE)
|
---|
536 |
|
---|
537 | self.dealbutton = Button(self.canvas,
|
---|
538 | text="Deal",
|
---|
539 | highlightthickness=0,
|
---|
540 | background=BACKGROUND,
|
---|
541 | activebackground="green",
|
---|
542 | command=self.deal)
|
---|
543 | Window(self.canvas, MARGIN, 3*YSPACING + 20,
|
---|
544 | window=self.dealbutton, anchor=SW)
|
---|
545 |
|
---|
546 | x = MARGIN
|
---|
547 | y = MARGIN
|
---|
548 |
|
---|
549 | self.deck = Deck(x, y, self)
|
---|
550 |
|
---|
551 | x = x + XSPACING
|
---|
552 | self.opendeck = OpenStack(x, y, self)
|
---|
553 |
|
---|
554 | x = x + XSPACING
|
---|
555 | self.suits = []
|
---|
556 | for i in range(NSUITS):
|
---|
557 | x = x + XSPACING
|
---|
558 | self.suits.append(SuitStack(x, y, self))
|
---|
559 |
|
---|
560 | x = MARGIN
|
---|
561 | y = y + YSPACING
|
---|
562 |
|
---|
563 | self.rows = []
|
---|
564 | for i in range(NROWS):
|
---|
565 | self.rows.append(RowStack(x, y, self))
|
---|
566 | x = x + XSPACING
|
---|
567 |
|
---|
568 | self.openstacks = [self.opendeck] + self.suits + self.rows
|
---|
569 |
|
---|
570 | self.deck.fill()
|
---|
571 | self.deal()
|
---|
572 |
|
---|
573 | def wincheck(self):
|
---|
574 | for s in self.suits:
|
---|
575 | if len(s.cards) != NVALUES:
|
---|
576 | return
|
---|
577 | self.win()
|
---|
578 | self.deal()
|
---|
579 |
|
---|
580 | def win(self):
|
---|
581 | """Stupid animation when you win."""
|
---|
582 | cards = []
|
---|
583 | for s in self.openstacks:
|
---|
584 | cards = cards + s.cards
|
---|
585 | while cards:
|
---|
586 | card = random.choice(cards)
|
---|
587 | cards.remove(card)
|
---|
588 | self.animatedmoveto(card, self.deck)
|
---|
589 |
|
---|
590 | def animatedmoveto(self, card, dest):
|
---|
591 | for i in range(10, 0, -1):
|
---|
592 | dx, dy = (dest.x-card.x)//i, (dest.y-card.y)//i
|
---|
593 | card.moveby(dx, dy)
|
---|
594 | self.master.update_idletasks()
|
---|
595 |
|
---|
596 | def closeststack(self, card):
|
---|
597 | closest = None
|
---|
598 | cdist = 999999999
|
---|
599 | # Since we only compare distances,
|
---|
600 | # we don't bother to take the square root.
|
---|
601 | for stack in self.openstacks:
|
---|
602 | dist = (stack.x - card.x)**2 + (stack.y - card.y)**2
|
---|
603 | if dist < cdist:
|
---|
604 | closest = stack
|
---|
605 | cdist = dist
|
---|
606 | return closest
|
---|
607 |
|
---|
608 | def deal(self):
|
---|
609 | self.reset()
|
---|
610 | self.deck.shuffle()
|
---|
611 | for i in range(NROWS):
|
---|
612 | for r in self.rows[i:]:
|
---|
613 | card = self.deck.deal()
|
---|
614 | r.add(card)
|
---|
615 | for r in self.rows:
|
---|
616 | r.showtop()
|
---|
617 |
|
---|
618 | def reset(self):
|
---|
619 | for stack in self.openstacks:
|
---|
620 | while 1:
|
---|
621 | card = stack.deal()
|
---|
622 | if not card:
|
---|
623 | break
|
---|
624 | self.deck.add(card)
|
---|
625 | card.showback()
|
---|
626 |
|
---|
627 |
|
---|
628 | # Main function, run when invoked as a stand-alone Python program.
|
---|
629 |
|
---|
630 | def main():
|
---|
631 | root = Tk()
|
---|
632 | game = Solitaire(root)
|
---|
633 | root.protocol('WM_DELETE_WINDOW', root.quit)
|
---|
634 | root.mainloop()
|
---|
635 |
|
---|
636 | if __name__ == '__main__':
|
---|
637 | main()
|
---|