blob: d0f5dee7471f2860d8b167c786f98866e4ea5eac [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Guido van Rossum8de9f891996-12-29 20:15:32 +00002
3"""Solitaire game, much like the one that comes with MS Windows.
4
5Limitations:
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.
Tim Peters182b5ac2004-07-18 06:16:08 +000014
Guido van Rossum8de9f891996-12-29 20:15:32 +000015Apology:
16
17I'm not much of a card player, so my terminology in these comments may
18at times be a little unusual. If you have suggestions, please let me
19know!
20
21"""
22
23# Imports
24
Guido van Rossum8de9f891996-12-29 20:15:32 +000025import random
26
Benjamin Petersond6d63f52009-01-04 18:53:28 +000027from tkinter import *
Georg Brandl856023a2010-10-25 17:50:20 +000028from canvasevents import Group
Guido van Rossum8de9f891996-12-29 20:15:32 +000029
30
31# Constants determining the size and lay-out of cards and stacks. We
32# work in a "grid" where each card/stack is surrounded by MARGIN
33# pixels of space on each side, so adjacent stacks are separated by
Guido van Rossum1b2b53a1996-12-30 02:20:29 +000034# 2*MARGIN pixels. OFFSET is the offset used for displaying the
35# face down cards in the row stacks.
Guido van Rossum8de9f891996-12-29 20:15:32 +000036
37CARDWIDTH = 100
38CARDHEIGHT = 150
39MARGIN = 10
40XSPACING = CARDWIDTH + 2*MARGIN
41YSPACING = CARDHEIGHT + 4*MARGIN
42OFFSET = 5
43
44# The background color, green to look like a playing table. The
45# standard green is way too bright, and dark green is way to dark, so
46# we use something in between. (There are a few more colors that
47# could be customized, but they are less controversial.)
48
49BACKGROUND = '#070'
50
51
52# Suits and colors. The values of the symbolic suit names are the
53# strings used to display them (you change these and VALNAMES to
54# internationalize the game). The COLOR dictionary maps suit names to
55# colors (red and black) which must be Tk color names. The keys() of
56# the COLOR dictionary conveniently provides us with a list of all
57# suits (in arbitrary order).
58
59HEARTS = 'Heart'
60DIAMONDS = 'Diamond'
61CLUBS = 'Club'
62SPADES = 'Spade'
63
64RED = 'red'
65BLACK = 'black'
66
67COLOR = {}
68for s in (HEARTS, DIAMONDS):
69 COLOR[s] = RED
70for s in (CLUBS, SPADES):
71 COLOR[s] = BLACK
72
Collin Winter6f2df4d2007-07-17 20:59:35 +000073ALLSUITS = list(COLOR.keys())
Guido van Rossum8de9f891996-12-29 20:15:32 +000074NSUITS = len(ALLSUITS)
75
76
Guido van Rossum1b2b53a1996-12-30 02:20:29 +000077# Card values are 1-13. We also define symbolic names for the picture
78# cards. ALLVALUES is a list of all card values.
Guido van Rossum8de9f891996-12-29 20:15:32 +000079
80ACE = 1
81JACK = 11
82QUEEN = 12
83KING = 13
84ALLVALUES = range(1, 14) # (one more than the highest value)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +000085NVALUES = len(ALLVALUES)
Guido van Rossum8de9f891996-12-29 20:15:32 +000086
87
88# VALNAMES is a list that maps a card value to string. It contains a
89# dummy element at index 0 so it can be indexed directly with the card
90# value.
91
Collin Winter6f2df4d2007-07-17 20:59:35 +000092VALNAMES = ["", "A"] + list(map(str, range(2, 11))) + ["J", "Q", "K"]
Guido van Rossum8de9f891996-12-29 20:15:32 +000093
94
95# Solitaire constants. The only one I can think of is the number of
96# row stacks.
97
98NROWS = 7
99
100
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000101# The rest of the program consists of class definitions. These are
102# further described in their documentation strings.
Guido van Rossum8de9f891996-12-29 20:15:32 +0000103
104
105class Card:
106
107 """A playing card.
108
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000109 A card doesn't record to which stack it belongs; only the stack
110 records this (it turns out that we always know this from the
111 context, and this saves a ``double update'' with potential for
112 inconsistencies).
113
Guido van Rossum8de9f891996-12-29 20:15:32 +0000114 Public methods:
115
116 moveto(x, y) -- move the card to an absolute position
117 moveby(dx, dy) -- move the card by a relative offset
118 tkraise() -- raise the card to the top of its stack
119 showface(), showback() -- turn the card face up or down & raise it
Guido van Rossum8de9f891996-12-29 20:15:32 +0000120
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000121 Public read-only instance variables:
Guido van Rossum8de9f891996-12-29 20:15:32 +0000122
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000123 suit, value, color -- the card's suit, value and color
Guido van Rossum8de9f891996-12-29 20:15:32 +0000124 face_shown -- true when the card is shown face up, else false
125
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000126 Semi-public read-only instance variables (XXX should be made
127 private):
Tim Peters182b5ac2004-07-18 06:16:08 +0000128
Guido van Rossum8de9f891996-12-29 20:15:32 +0000129 group -- the Canvas.Group representing the card
130 x, y -- the position of the card's top left corner
131
132 Private instance variables:
133
134 __back, __rect, __text -- the canvas items making up the card
135
136 (To show the card face up, the text item is placed in front of
137 rect and the back is placed behind it. To show it face down, this
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000138 is reversed. The card is created face down.)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000139
140 """
141
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000142 def __init__(self, suit, value, canvas):
Tim Peters182b5ac2004-07-18 06:16:08 +0000143 """Card constructor.
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000144
Tim Peters182b5ac2004-07-18 06:16:08 +0000145 Arguments are the card's suit and value, and the canvas widget.
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000146
Tim Peters182b5ac2004-07-18 06:16:08 +0000147 The card is created at position (0, 0), with its face down
148 (adding it to a stack will position it according to that
149 stack's rules).
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000150
Tim Peters182b5ac2004-07-18 06:16:08 +0000151 """
152 self.suit = suit
153 self.value = value
154 self.color = COLOR[suit]
155 self.face_shown = 0
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000156
Tim Peters182b5ac2004-07-18 06:16:08 +0000157 self.x = self.y = 0
Georg Brandl856023a2010-10-25 17:50:20 +0000158 self.canvas = canvas
Tim Peters182b5ac2004-07-18 06:16:08 +0000159 self.group = Group(canvas)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000160
Tim Peters182b5ac2004-07-18 06:16:08 +0000161 text = "%s %s" % (VALNAMES[value], suit)
Georg Brandl856023a2010-10-25 17:50:20 +0000162 self.__text = canvas.create_text(CARDWIDTH // 2, 0, anchor=N,
163 fill=self.color, text=text)
Tim Peters182b5ac2004-07-18 06:16:08 +0000164 self.group.addtag_withtag(self.__text)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000165
Georg Brandl856023a2010-10-25 17:50:20 +0000166 self.__rect = canvas.create_rectangle(0, 0, CARDWIDTH, CARDHEIGHT,
167 outline='black', fill='white')
Tim Peters182b5ac2004-07-18 06:16:08 +0000168 self.group.addtag_withtag(self.__rect)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000169
Georg Brandl856023a2010-10-25 17:50:20 +0000170 self.__back = canvas.create_rectangle(MARGIN, MARGIN,
171 CARDWIDTH - MARGIN,
172 CARDHEIGHT - MARGIN,
173 outline='black', fill='blue')
Tim Peters182b5ac2004-07-18 06:16:08 +0000174 self.group.addtag_withtag(self.__back)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000175
176 def __repr__(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000177 """Return a string for debug print statements."""
178 return "Card(%r, %r)" % (self.suit, self.value)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000179
180 def moveto(self, x, y):
Tim Peters182b5ac2004-07-18 06:16:08 +0000181 """Move the card to absolute position (x, y)."""
182 self.moveby(x - self.x, y - self.y)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000183
184 def moveby(self, dx, dy):
Tim Peters182b5ac2004-07-18 06:16:08 +0000185 """Move the card by (dx, dy)."""
186 self.x = self.x + dx
187 self.y = self.y + dy
188 self.group.move(dx, dy)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000189
190 def tkraise(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000191 """Raise the card above all other objects in its canvas."""
192 self.group.tkraise()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000193
194 def showface(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000195 """Turn the card's face up."""
196 self.tkraise()
Georg Brandl856023a2010-10-25 17:50:20 +0000197 self.canvas.tag_raise(self.__rect)
198 self.canvas.tag_raise(self.__text)
Tim Peters182b5ac2004-07-18 06:16:08 +0000199 self.face_shown = 1
Guido van Rossum8de9f891996-12-29 20:15:32 +0000200
201 def showback(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000202 """Turn the card's face down."""
203 self.tkraise()
Georg Brandl856023a2010-10-25 17:50:20 +0000204 self.canvas.tag_raise(self.__rect)
205 self.canvas.tag_raise(self.__back)
Tim Peters182b5ac2004-07-18 06:16:08 +0000206 self.face_shown = 0
Guido van Rossum8de9f891996-12-29 20:15:32 +0000207
Guido van Rossum8de9f891996-12-29 20:15:32 +0000208
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000209class Stack:
Guido van Rossum8de9f891996-12-29 20:15:32 +0000210
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000211 """A generic stack of cards.
Guido van Rossum8de9f891996-12-29 20:15:32 +0000212
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000213 This is used as a base class for all other stacks (e.g. the deck,
214 the suit stacks, and the row stacks).
Guido van Rossum8de9f891996-12-29 20:15:32 +0000215
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000216 Public methods:
Guido van Rossum8de9f891996-12-29 20:15:32 +0000217
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000218 add(card) -- add a card to the stack
219 delete(card) -- delete a card from the stack
220 showtop() -- show the top card (if any) face up
221 deal() -- delete and return the top card, or None if empty
Guido van Rossum8de9f891996-12-29 20:15:32 +0000222
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000223 Method that subclasses may override:
Guido van Rossum8de9f891996-12-29 20:15:32 +0000224
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000225 position(card) -- move the card to its proper (x, y) position
226
227 The default position() method places all cards at the stack's
228 own (x, y) position.
229
230 userclickhandler(), userdoubleclickhandler() -- called to do
231 subclass specific things on single and double clicks
232
233 The default user (single) click handler shows the top card
234 face up. The default user double click handler calls the user
Tim Peters182b5ac2004-07-18 06:16:08 +0000235 single click handler.
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000236
237 usermovehandler(cards) -- called to complete a subpile move
238
239 The default user move handler moves all moved cards back to
240 their original position (by calling the position() method).
241
242 Private methods:
243
244 clickhandler(event), doubleclickhandler(event),
245 motionhandler(event), releasehandler(event) -- event handlers
246
247 The default event handlers turn the top card of the stack with
248 its face up on a (single or double) click, and also support
249 moving a subpile around.
Tim Peters182b5ac2004-07-18 06:16:08 +0000250
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000251 startmoving(event) -- begin a move operation
252 finishmoving() -- finish a move operation
253
254 """
255
256 def __init__(self, x, y, game=None):
Tim Peters182b5ac2004-07-18 06:16:08 +0000257 """Stack constructor.
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000258
Tim Peters182b5ac2004-07-18 06:16:08 +0000259 Arguments are the stack's nominal x and y position (the top
260 left corner of the first card placed in the stack), and the
261 game object (which is used to get the canvas; subclasses use
262 the game object to find other stacks).
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000263
Tim Peters182b5ac2004-07-18 06:16:08 +0000264 """
265 self.x = x
266 self.y = y
267 self.game = game
268 self.cards = []
269 self.group = Group(self.game.canvas)
270 self.group.bind('<1>', self.clickhandler)
271 self.group.bind('<Double-1>', self.doubleclickhandler)
272 self.group.bind('<B1-Motion>', self.motionhandler)
273 self.group.bind('<ButtonRelease-1>', self.releasehandler)
274 self.makebottom()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000275
276 def makebottom(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000277 pass
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000278
279 def __repr__(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000280 """Return a string for debug print statements."""
281 return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000282
283 # Public methods
284
285 def add(self, card):
Tim Peters182b5ac2004-07-18 06:16:08 +0000286 self.cards.append(card)
287 card.tkraise()
288 self.position(card)
289 self.group.addtag_withtag(card.group)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000290
291 def delete(self, card):
Tim Peters182b5ac2004-07-18 06:16:08 +0000292 self.cards.remove(card)
293 card.group.dtag(self.group)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000294
295 def showtop(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000296 if self.cards:
297 self.cards[-1].showface()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000298
299 def deal(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000300 if not self.cards:
301 return None
302 card = self.cards[-1]
303 self.delete(card)
304 return card
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000305
306 # Subclass overridable methods
307
308 def position(self, card):
Tim Peters182b5ac2004-07-18 06:16:08 +0000309 card.moveto(self.x, self.y)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000310
311 def userclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000312 self.showtop()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000313
314 def userdoubleclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000315 self.userclickhandler()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000316
317 def usermovehandler(self, cards):
Tim Peters182b5ac2004-07-18 06:16:08 +0000318 for card in cards:
319 self.position(card)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000320
321 # Event handlers
322
323 def clickhandler(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000324 self.finishmoving() # In case we lost an event
325 self.userclickhandler()
326 self.startmoving(event)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000327
328 def motionhandler(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000329 self.keepmoving(event)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000330
331 def releasehandler(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000332 self.keepmoving(event)
333 self.finishmoving()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000334
335 def doubleclickhandler(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000336 self.finishmoving() # In case we lost an event
337 self.userdoubleclickhandler()
338 self.startmoving(event)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000339
340 # Move internals
341
342 moving = None
343
344 def startmoving(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000345 self.moving = None
346 tags = self.game.canvas.gettags('current')
347 for i in range(len(self.cards)):
348 card = self.cards[i]
349 if card.group.tag in tags:
350 break
351 else:
352 return
353 if not card.face_shown:
354 return
355 self.moving = self.cards[i:]
356 self.lastx = event.x
357 self.lasty = event.y
358 for card in self.moving:
359 card.tkraise()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000360
361 def keepmoving(self, event):
Tim Peters182b5ac2004-07-18 06:16:08 +0000362 if not self.moving:
363 return
364 dx = event.x - self.lastx
365 dy = event.y - self.lasty
366 self.lastx = event.x
367 self.lasty = event.y
368 if dx or dy:
369 for card in self.moving:
370 card.moveby(dx, dy)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000371
372 def finishmoving(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000373 cards = self.moving
374 self.moving = None
375 if cards:
376 self.usermovehandler(cards)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000377
378
379class Deck(Stack):
380
381 """The deck is a stack with support for shuffling.
382
383 New methods:
384
385 fill() -- create the playing cards
386 shuffle() -- shuffle the playing cards
387
388 A single click moves the top card to the game's open deck and
389 moves it face up; if we're out of cards, it moves the open deck
390 back to the deck.
391
392 """
393
394 def makebottom(self):
Georg Brandl856023a2010-10-25 17:50:20 +0000395 bottom = self.game.canvas.create_rectangle(self.x, self.y,
396 self.x + CARDWIDTH, self.y + CARDHEIGHT, outline='black',
397 fill=BACKGROUND)
Tim Peters182b5ac2004-07-18 06:16:08 +0000398 self.group.addtag_withtag(bottom)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000399
400 def fill(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000401 for suit in ALLSUITS:
402 for value in ALLVALUES:
403 self.add(Card(suit, value, self.game.canvas))
Guido van Rossum8de9f891996-12-29 20:15:32 +0000404
405 def shuffle(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000406 n = len(self.cards)
407 newcards = []
408 for i in randperm(n):
409 newcards.append(self.cards[i])
410 self.cards = newcards
Guido van Rossum8de9f891996-12-29 20:15:32 +0000411
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000412 def userclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000413 opendeck = self.game.opendeck
414 card = self.deal()
415 if not card:
416 while 1:
417 card = opendeck.deal()
418 if not card:
419 break
420 self.add(card)
421 card.showback()
422 else:
423 self.game.opendeck.add(card)
424 card.showface()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000425
Guido van Rossum8de9f891996-12-29 20:15:32 +0000426
427def randperm(n):
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000428 """Function returning a random permutation of range(n)."""
Georg Brandl856023a2010-10-25 17:50:20 +0000429 r = list(range(n))
Guido van Rossum8de9f891996-12-29 20:15:32 +0000430 x = []
431 while r:
Tim Peters182b5ac2004-07-18 06:16:08 +0000432 i = random.choice(r)
433 x.append(i)
434 r.remove(i)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000435 return x
436
Guido van Rossum8de9f891996-12-29 20:15:32 +0000437
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000438class OpenStack(Stack):
Guido van Rossum8de9f891996-12-29 20:15:32 +0000439
Guido van Rossuma0dc1c41996-12-30 02:37:07 +0000440 def acceptable(self, cards):
Tim Peters182b5ac2004-07-18 06:16:08 +0000441 return 0
Guido van Rossuma0dc1c41996-12-30 02:37:07 +0000442
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000443 def usermovehandler(self, cards):
Tim Peters182b5ac2004-07-18 06:16:08 +0000444 card = cards[0]
445 stack = self.game.closeststack(card)
446 if not stack or stack is self or not stack.acceptable(cards):
447 Stack.usermovehandler(self, cards)
448 else:
449 for card in cards:
450 self.delete(card)
451 stack.add(card)
452 self.game.wincheck()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000453
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000454 def userdoubleclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000455 if not self.cards:
456 return
457 card = self.cards[-1]
458 if not card.face_shown:
459 self.userclickhandler()
460 return
461 for s in self.game.suits:
462 if s.acceptable([card]):
463 self.delete(card)
464 s.add(card)
465 self.game.wincheck()
466 break
Guido van Rossum8de9f891996-12-29 20:15:32 +0000467
Guido van Rossum8de9f891996-12-29 20:15:32 +0000468
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000469class SuitStack(OpenStack):
Guido van Rossum8de9f891996-12-29 20:15:32 +0000470
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000471 def makebottom(self):
Georg Brandl856023a2010-10-25 17:50:20 +0000472 bottom = self.game.canvas.create_rectangle(self.x, self.y,
473 self.x + CARDWIDTH, self.y + CARDHEIGHT, outline='black', fill='')
Guido van Rossum8de9f891996-12-29 20:15:32 +0000474
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000475 def userclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000476 pass
Guido van Rossum8de9f891996-12-29 20:15:32 +0000477
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000478 def userdoubleclickhandler(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000479 pass
Guido van Rossum8de9f891996-12-29 20:15:32 +0000480
481 def acceptable(self, cards):
Tim Peters182b5ac2004-07-18 06:16:08 +0000482 if len(cards) != 1:
483 return 0
484 card = cards[0]
485 if not self.cards:
486 return card.value == ACE
487 topcard = self.cards[-1]
488 return card.suit == topcard.suit and card.value == topcard.value + 1
Guido van Rossum8de9f891996-12-29 20:15:32 +0000489
Guido van Rossum8de9f891996-12-29 20:15:32 +0000490
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000491class RowStack(OpenStack):
Guido van Rossum8de9f891996-12-29 20:15:32 +0000492
493 def acceptable(self, cards):
Tim Peters182b5ac2004-07-18 06:16:08 +0000494 card = cards[0]
495 if not self.cards:
496 return card.value == KING
497 topcard = self.cards[-1]
498 if not topcard.face_shown:
499 return 0
500 return card.color != topcard.color and card.value == topcard.value - 1
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000501
502 def position(self, card):
Tim Peters182b5ac2004-07-18 06:16:08 +0000503 y = self.y
504 for c in self.cards:
505 if c == card:
506 break
507 if c.face_shown:
508 y = y + 2*MARGIN
509 else:
510 y = y + OFFSET
511 card.moveto(self.x, y)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000512
Guido van Rossum8de9f891996-12-29 20:15:32 +0000513
514class Solitaire:
515
516 def __init__(self, master):
Tim Peters182b5ac2004-07-18 06:16:08 +0000517 self.master = master
Guido van Rossum8de9f891996-12-29 20:15:32 +0000518
Tim Peters182b5ac2004-07-18 06:16:08 +0000519 self.canvas = Canvas(self.master,
520 background=BACKGROUND,
521 highlightthickness=0,
522 width=NROWS*XSPACING,
523 height=3*YSPACING + 20 + MARGIN)
524 self.canvas.pack(fill=BOTH, expand=TRUE)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000525
Tim Peters182b5ac2004-07-18 06:16:08 +0000526 self.dealbutton = Button(self.canvas,
527 text="Deal",
528 highlightthickness=0,
529 background=BACKGROUND,
530 activebackground="green",
531 command=self.deal)
Georg Brandl856023a2010-10-25 17:50:20 +0000532 self.canvas.create_window(MARGIN, 3 * YSPACING + 20,
533 window=self.dealbutton, anchor=SW)
Guido van Rossum8de9f891996-12-29 20:15:32 +0000534
Tim Peters182b5ac2004-07-18 06:16:08 +0000535 x = MARGIN
536 y = MARGIN
Guido van Rossum8de9f891996-12-29 20:15:32 +0000537
Tim Peters182b5ac2004-07-18 06:16:08 +0000538 self.deck = Deck(x, y, self)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000539
Tim Peters182b5ac2004-07-18 06:16:08 +0000540 x = x + XSPACING
541 self.opendeck = OpenStack(x, y, self)
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000542
Tim Peters182b5ac2004-07-18 06:16:08 +0000543 x = x + XSPACING
544 self.suits = []
545 for i in range(NSUITS):
546 x = x + XSPACING
547 self.suits.append(SuitStack(x, y, self))
Guido van Rossum8de9f891996-12-29 20:15:32 +0000548
Tim Peters182b5ac2004-07-18 06:16:08 +0000549 x = MARGIN
550 y = y + YSPACING
Guido van Rossuma0dc1c41996-12-30 02:37:07 +0000551
Tim Peters182b5ac2004-07-18 06:16:08 +0000552 self.rows = []
553 for i in range(NROWS):
554 self.rows.append(RowStack(x, y, self))
555 x = x + XSPACING
556
557 self.openstacks = [self.opendeck] + self.suits + self.rows
558
559 self.deck.fill()
560 self.deal()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000561
562 def wincheck(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000563 for s in self.suits:
564 if len(s.cards) != NVALUES:
565 return
566 self.win()
567 self.deal()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000568
569 def win(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000570 """Stupid animation when you win."""
571 cards = []
572 for s in self.openstacks:
573 cards = cards + s.cards
574 while cards:
575 card = random.choice(cards)
576 cards.remove(card)
577 self.animatedmoveto(card, self.deck)
Guido van Rossumb5846d71996-12-30 16:45:14 +0000578
579 def animatedmoveto(self, card, dest):
Tim Peters182b5ac2004-07-18 06:16:08 +0000580 for i in range(10, 0, -1):
Benjamin Petersond7b03282008-09-13 15:58:53 +0000581 dx, dy = (dest.x-card.x)//i, (dest.y-card.y)//i
Tim Peters182b5ac2004-07-18 06:16:08 +0000582 card.moveby(dx, dy)
583 self.master.update_idletasks()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000584
585 def closeststack(self, card):
Tim Peters182b5ac2004-07-18 06:16:08 +0000586 closest = None
587 cdist = 999999999
588 # Since we only compare distances,
589 # we don't bother to take the square root.
590 for stack in self.openstacks:
591 dist = (stack.x - card.x)**2 + (stack.y - card.y)**2
592 if dist < cdist:
593 closest = stack
594 cdist = dist
595 return closest
Guido van Rossum8de9f891996-12-29 20:15:32 +0000596
Guido van Rossum8de9f891996-12-29 20:15:32 +0000597 def deal(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000598 self.reset()
599 self.deck.shuffle()
600 for i in range(NROWS):
601 for r in self.rows[i:]:
602 card = self.deck.deal()
603 r.add(card)
604 for r in self.rows:
605 r.showtop()
Guido van Rossum1b2b53a1996-12-30 02:20:29 +0000606
607 def reset(self):
Tim Peters182b5ac2004-07-18 06:16:08 +0000608 for stack in self.openstacks:
609 while 1:
610 card = stack.deal()
611 if not card:
612 break
613 self.deck.add(card)
614 card.showback()
Guido van Rossum8de9f891996-12-29 20:15:32 +0000615
616
617# Main function, run when invoked as a stand-alone Python program.
618
619def main():
620 root = Tk()
621 game = Solitaire(root)
622 root.protocol('WM_DELETE_WINDOW', root.quit)
623 root.mainloop()
624
625if __name__ == '__main__':
626 main()