| #! /usr/bin/env python | 
 |  | 
 | """Solitaire game, much like the one that comes with MS Windows. | 
 |  | 
 | Limitations: | 
 |  | 
 | - No cute graphical images for the playing cards faces or backs. | 
 | - No scoring or timer. | 
 | - No undo. | 
 | - No option to turn 3 cards at a time. | 
 | - No keyboard shortcuts. | 
 | - Less fancy animation when you win. | 
 | - The determination of which stack you drag to is more relaxed. | 
 |    | 
 | Apology: | 
 |  | 
 | I'm not much of a card player, so my terminology in these comments may | 
 | at times be a little unusual.  If you have suggestions, please let me | 
 | know! | 
 |  | 
 | """ | 
 |  | 
 | # Imports | 
 |  | 
 | import math | 
 | import random | 
 |  | 
 | from Tkinter import * | 
 | from Canvas import Rectangle, CanvasText, Group, Window | 
 |  | 
 |  | 
 | # Fix a bug in Canvas.Group as distributed in Python 1.4.  The | 
 | # distributed bind() method is broken.  Rather than asking you to fix | 
 | # the source, we fix it here by deriving a subclass: | 
 |  | 
 | class Group(Group): | 
 |     def bind(self, sequence=None, command=None): | 
 | 	return self.canvas.tag_bind(self.id, sequence, command) | 
 |  | 
 |  | 
 | # Constants determining the size and lay-out of cards and stacks.  We | 
 | # work in a "grid" where each card/stack is surrounded by MARGIN | 
 | # pixels of space on each side, so adjacent stacks are separated by | 
 | # 2*MARGIN pixels.  OFFSET is the offset used for displaying the | 
 | # face down cards in the row stacks. | 
 |  | 
 | CARDWIDTH = 100 | 
 | CARDHEIGHT = 150 | 
 | MARGIN = 10 | 
 | XSPACING = CARDWIDTH + 2*MARGIN | 
 | YSPACING = CARDHEIGHT + 4*MARGIN | 
 | OFFSET = 5 | 
 |  | 
 | # The background color, green to look like a playing table.  The | 
 | # standard green is way too bright, and dark green is way to dark, so | 
 | # we use something in between.  (There are a few more colors that | 
 | # could be customized, but they are less controversial.) | 
 |  | 
 | BACKGROUND = '#070' | 
 |  | 
 |  | 
 | # Suits and colors.  The values of the symbolic suit names are the | 
 | # strings used to display them (you change these and VALNAMES to | 
 | # internationalize the game).  The COLOR dictionary maps suit names to | 
 | # colors (red and black) which must be Tk color names.  The keys() of | 
 | # the COLOR dictionary conveniently provides us with a list of all | 
 | # suits (in arbitrary order). | 
 |  | 
 | HEARTS = 'Heart' | 
 | DIAMONDS = 'Diamond' | 
 | CLUBS = 'Club' | 
 | SPADES = 'Spade' | 
 |  | 
 | RED = 'red' | 
 | BLACK = 'black' | 
 |  | 
 | COLOR = {} | 
 | for s in (HEARTS, DIAMONDS): | 
 |     COLOR[s] = RED | 
 | for s in (CLUBS, SPADES): | 
 |     COLOR[s] = BLACK | 
 |  | 
 | ALLSUITS = COLOR.keys() | 
 | NSUITS = len(ALLSUITS) | 
 |  | 
 |  | 
 | # Card values are 1-13.  We also define symbolic names for the picture | 
 | # cards.  ALLVALUES is a list of all card values. | 
 |  | 
 | ACE = 1 | 
 | JACK = 11 | 
 | QUEEN = 12 | 
 | KING = 13 | 
 | ALLVALUES = range(1, 14) # (one more than the highest value) | 
 | NVALUES = len(ALLVALUES) | 
 |  | 
 |  | 
 | # VALNAMES is a list that maps a card value to string.  It contains a | 
 | # dummy element at index 0 so it can be indexed directly with the card | 
 | # value. | 
 |  | 
 | VALNAMES = ["", "A"] + map(str, range(2, 11)) + ["J", "Q", "K"] | 
 |  | 
 |  | 
 | # Solitaire constants.  The only one I can think of is the number of | 
 | # row stacks. | 
 |  | 
 | NROWS = 7 | 
 |  | 
 |  | 
 | # The rest of the program consists of class definitions.  These are | 
 | # further described in their documentation strings. | 
 |  | 
 |  | 
 | class Card: | 
 |  | 
 |     """A playing card. | 
 |  | 
 |     A card doesn't record to which stack it belongs; only the stack | 
 |     records this (it turns out that we always know this from the | 
 |     context, and this saves a ``double update'' with potential for | 
 |     inconsistencies). | 
 |  | 
 |     Public methods: | 
 |  | 
 |     moveto(x, y) -- move the card to an absolute position | 
 |     moveby(dx, dy) -- move the card by a relative offset | 
 |     tkraise() -- raise the card to the top of its stack | 
 |     showface(), showback() -- turn the card face up or down & raise it | 
 |  | 
 |     Public read-only instance variables: | 
 |  | 
 |     suit, value, color -- the card's suit, value and color | 
 |     face_shown -- true when the card is shown face up, else false | 
 |  | 
 |     Semi-public read-only instance variables (XXX should be made | 
 |     private): | 
 |      | 
 |     group -- the Canvas.Group representing the card | 
 |     x, y -- the position of the card's top left corner | 
 |  | 
 |     Private instance variables: | 
 |  | 
 |     __back, __rect, __text -- the canvas items making up the card | 
 |  | 
 |     (To show the card face up, the text item is placed in front of | 
 |     rect and the back is placed behind it.  To show it face down, this | 
 |     is reversed.  The card is created face down.) | 
 |  | 
 |     """ | 
 |  | 
 |     def __init__(self, suit, value, canvas): | 
 | 	"""Card constructor. | 
 |  | 
 | 	Arguments are the card's suit and value, and the canvas widget. | 
 |  | 
 | 	The card is created at position (0, 0), with its face down | 
 | 	(adding it to a stack will position it according to that | 
 | 	stack's rules). | 
 |  | 
 | 	""" | 
 | 	self.suit = suit | 
 | 	self.value = value | 
 | 	self.color = COLOR[suit] | 
 | 	self.face_shown = 0 | 
 |  | 
 | 	self.x = self.y = 0 | 
 | 	self.group = Group(canvas) | 
 |  | 
 | 	text = "%s  %s" % (VALNAMES[value], suit) | 
 | 	self.__text = CanvasText(canvas, CARDWIDTH/2, 0, | 
 | 			       anchor=N, fill=self.color, text=text) | 
 | 	self.group.addtag_withtag(self.__text) | 
 |  | 
 | 	self.__rect = Rectangle(canvas, 0, 0, CARDWIDTH, CARDHEIGHT, | 
 | 			      outline='black', fill='white') | 
 | 	self.group.addtag_withtag(self.__rect) | 
 |  | 
 | 	self.__back = Rectangle(canvas, MARGIN, MARGIN, | 
 | 			      CARDWIDTH-MARGIN, CARDHEIGHT-MARGIN, | 
 | 			      outline='black', fill='blue') | 
 | 	self.group.addtag_withtag(self.__back) | 
 |  | 
 |     def __repr__(self): | 
 | 	"""Return a string for debug print statements.""" | 
 | 	return "Card(%s, %s)" % (`self.suit`, `self.value`) | 
 |  | 
 |     def moveto(self, x, y): | 
 | 	"""Move the card to absolute position (x, y).""" | 
 | 	self.moveby(x - self.x, y - self.y) | 
 |  | 
 |     def moveby(self, dx, dy): | 
 | 	"""Move the card by (dx, dy).""" | 
 | 	self.x = self.x + dx | 
 | 	self.y = self.y + dy | 
 | 	self.group.move(dx, dy) | 
 |  | 
 |     def tkraise(self): | 
 | 	"""Raise the card above all other objects in its canvas.""" | 
 | 	self.group.tkraise() | 
 |  | 
 |     def showface(self): | 
 | 	"""Turn the card's face up.""" | 
 | 	self.tkraise() | 
 | 	self.__rect.tkraise() | 
 | 	self.__text.tkraise() | 
 | 	self.face_shown = 1 | 
 |  | 
 |     def showback(self): | 
 | 	"""Turn the card's face down.""" | 
 | 	self.tkraise() | 
 | 	self.__rect.tkraise() | 
 | 	self.__back.tkraise() | 
 | 	self.face_shown = 0 | 
 |  | 
 |  | 
 | class Stack: | 
 |  | 
 |     """A generic stack of cards. | 
 |  | 
 |     This is used as a base class for all other stacks (e.g. the deck, | 
 |     the suit stacks, and the row stacks). | 
 |  | 
 |     Public methods: | 
 |  | 
 |     add(card) -- add a card to the stack | 
 |     delete(card) -- delete a card from the stack | 
 |     showtop() -- show the top card (if any) face up | 
 |     deal() -- delete and return the top card, or None if empty | 
 |  | 
 |     Method that subclasses may override: | 
 |  | 
 |     position(card) -- move the card to its proper (x, y) position | 
 |  | 
 |         The default position() method places all cards at the stack's | 
 |         own (x, y) position. | 
 |  | 
 |     userclickhandler(), userdoubleclickhandler() -- called to do | 
 |     subclass specific things on single and double clicks | 
 |  | 
 |         The default user (single) click handler shows the top card | 
 |         face up.  The default user double click handler calls the user | 
 | 	single click handler. | 
 |  | 
 |     usermovehandler(cards) -- called to complete a subpile move | 
 |  | 
 |         The default user move handler moves all moved cards back to | 
 |         their original position (by calling the position() method). | 
 |  | 
 |     Private methods: | 
 |  | 
 |     clickhandler(event), doubleclickhandler(event), | 
 |     motionhandler(event), releasehandler(event) -- event handlers | 
 |  | 
 |         The default event handlers turn the top card of the stack with | 
 |         its face up on a (single or double) click, and also support | 
 |         moving a subpile around. | 
 |      | 
 |     startmoving(event) -- begin a move operation | 
 |     finishmoving() -- finish a move operation | 
 |  | 
 |     """ | 
 |  | 
 |     def __init__(self, x, y, game=None): | 
 | 	"""Stack constructor. | 
 |  | 
 | 	Arguments are the stack's nominal x and y position (the top | 
 | 	left corner of the first card placed in the stack), and the | 
 | 	game object (which is used to get the canvas; subclasses use | 
 | 	the game object to find other stacks). | 
 |  | 
 | 	""" | 
 | 	self.x = x | 
 | 	self.y = y | 
 | 	self.game = game | 
 | 	self.cards = [] | 
 | 	self.group = Group(self.game.canvas) | 
 | 	self.group.bind('<1>', self.clickhandler) | 
 |  	self.group.bind('<Double-1>', self.doubleclickhandler) | 
 | 	self.group.bind('<B1-Motion>', self.motionhandler) | 
 | 	self.group.bind('<ButtonRelease-1>', self.releasehandler) | 
 | 	self.makebottom() | 
 |  | 
 |     def makebottom(self): | 
 | 	pass | 
 |  | 
 |     def __repr__(self): | 
 | 	"""Return a string for debug print statements.""" | 
 | 	return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y) | 
 |  | 
 |     # Public methods | 
 |  | 
 |     def add(self, card): | 
 | 	self.cards.append(card) | 
 | 	card.tkraise() | 
 | 	self.position(card) | 
 | 	self.group.addtag_withtag(card.group) | 
 |  | 
 |     def delete(self, card): | 
 | 	self.cards.remove(card) | 
 | 	card.group.dtag(self.group) | 
 |  | 
 |     def showtop(self): | 
 | 	if self.cards: | 
 | 	    self.cards[-1].showface() | 
 |  | 
 |     def deal(self): | 
 | 	if not self.cards: | 
 | 	    return None | 
 | 	card = self.cards[-1] | 
 | 	self.delete(card) | 
 | 	return card | 
 |  | 
 |     # Subclass overridable methods | 
 |  | 
 |     def position(self, card): | 
 | 	card.moveto(self.x, self.y) | 
 |  | 
 |     def userclickhandler(self): | 
 | 	self.showtop() | 
 |  | 
 |     def userdoubleclickhandler(self): | 
 | 	self.userclickhandler() | 
 |  | 
 |     def usermovehandler(self, cards): | 
 | 	for card in cards: | 
 | 	    self.position(card) | 
 |  | 
 |     # Event handlers | 
 |  | 
 |     def clickhandler(self, event): | 
 | 	self.finishmoving()		# In case we lost an event | 
 | 	self.userclickhandler() | 
 | 	self.startmoving(event) | 
 |  | 
 |     def motionhandler(self, event): | 
 | 	self.keepmoving(event) | 
 |  | 
 |     def releasehandler(self, event): | 
 | 	self.keepmoving(event) | 
 | 	self.finishmoving() | 
 |  | 
 |     def doubleclickhandler(self, event): | 
 | 	self.finishmoving()		# In case we lost an event | 
 | 	self.userdoubleclickhandler() | 
 | 	self.startmoving(event) | 
 |  | 
 |     # Move internals | 
 |  | 
 |     moving = None | 
 |  | 
 |     def startmoving(self, event): | 
 | 	self.moving = None | 
 | 	tags = self.game.canvas.gettags('current') | 
 | 	for i in range(len(self.cards)): | 
 | 	    card = self.cards[i] | 
 | 	    if card.group.tag in tags: | 
 | 		break | 
 | 	else: | 
 | 	    return | 
 | 	if not card.face_shown: | 
 | 	    return | 
 | 	self.moving = self.cards[i:] | 
 | 	self.lastx = event.x | 
 | 	self.lasty = event.y | 
 | 	for card in self.moving: | 
 | 	    card.tkraise() | 
 |  | 
 |     def keepmoving(self, event): | 
 | 	if not self.moving: | 
 | 	    return | 
 | 	dx = event.x - self.lastx | 
 | 	dy = event.y - self.lasty | 
 | 	self.lastx = event.x | 
 | 	self.lasty = event.y | 
 | 	if dx or dy: | 
 | 	    for card in self.moving: | 
 | 		card.moveby(dx, dy) | 
 |  | 
 |     def finishmoving(self): | 
 | 	cards = self.moving | 
 | 	self.moving = None | 
 | 	if cards: | 
 | 	    self.usermovehandler(cards) | 
 |  | 
 |  | 
 | class Deck(Stack): | 
 |  | 
 |     """The deck is a stack with support for shuffling. | 
 |  | 
 |     New methods: | 
 |  | 
 |     fill() -- create the playing cards | 
 |     shuffle() -- shuffle the playing cards | 
 |  | 
 |     A single click moves the top card to the game's open deck and | 
 |     moves it face up; if we're out of cards, it moves the open deck | 
 |     back to the deck. | 
 |  | 
 |     """ | 
 |  | 
 |     def makebottom(self): | 
 | 	bottom = Rectangle(self.game.canvas, | 
 | 			   self.x, self.y, | 
 | 			   self.x+CARDWIDTH, self.y+CARDHEIGHT, | 
 | 			   outline='black', fill=BACKGROUND) | 
 |  	self.group.addtag_withtag(bottom) | 
 |  | 
 |     def fill(self): | 
 | 	for suit in ALLSUITS: | 
 | 	    for value in ALLVALUES: | 
 | 		self.add(Card(suit, value, self.game.canvas)) | 
 |  | 
 |     def shuffle(self): | 
 | 	n = len(self.cards) | 
 | 	newcards = [] | 
 | 	for i in randperm(n): | 
 | 	    newcards.append(self.cards[i]) | 
 | 	self.cards = newcards | 
 |  | 
 |     def userclickhandler(self): | 
 | 	opendeck = self.game.opendeck | 
 | 	card = self.deal() | 
 | 	if not card: | 
 | 	    while 1: | 
 | 		card = opendeck.deal() | 
 | 		if not card: | 
 | 		    break | 
 | 		self.add(card) | 
 | 		card.showback() | 
 | 	else: | 
 | 	    self.game.opendeck.add(card) | 
 | 	    card.showface() | 
 |  | 
 |  | 
 | def randperm(n): | 
 |     """Function returning a random permutation of range(n).""" | 
 |     r = range(n) | 
 |     x = [] | 
 |     while r: | 
 | 	i = random.choice(r) | 
 | 	x.append(i) | 
 | 	r.remove(i) | 
 |     return x | 
 |  | 
 |  | 
 | class OpenStack(Stack): | 
 |  | 
 |     def acceptable(self, cards): | 
 | 	return 0 | 
 |  | 
 |     def usermovehandler(self, cards): | 
 | 	card = cards[0] | 
 | 	stack = self.game.closeststack(card) | 
 | 	if not stack or stack is self or not stack.acceptable(cards): | 
 | 	    Stack.usermovehandler(self, cards) | 
 | 	else: | 
 | 	    for card in cards: | 
 | 		self.delete(card) | 
 | 		stack.add(card) | 
 | 	    self.game.wincheck() | 
 |  | 
 |     def userdoubleclickhandler(self): | 
 | 	if not self.cards: | 
 | 	    return | 
 | 	card = self.cards[-1] | 
 | 	if not card.face_shown: | 
 | 	    self.userclickhandler() | 
 | 	    return | 
 | 	for s in self.game.suits: | 
 | 	    if s.acceptable([card]): | 
 | 		self.delete(card) | 
 | 		s.add(card) | 
 | 		self.game.wincheck() | 
 | 		break | 
 |  | 
 |  | 
 | class SuitStack(OpenStack): | 
 |  | 
 |     def makebottom(self): | 
 | 	bottom = Rectangle(self.game.canvas, | 
 | 			   self.x, self.y, | 
 | 			   self.x+CARDWIDTH, self.y+CARDHEIGHT, | 
 | 			   outline='black', fill='') | 
 |  | 
 |     def userclickhandler(self): | 
 | 	pass | 
 |  | 
 |     def userdoubleclickhandler(self): | 
 | 	pass | 
 |  | 
 |     def acceptable(self, cards): | 
 | 	if len(cards) != 1: | 
 | 	    return 0 | 
 | 	card = cards[0] | 
 | 	if not self.cards: | 
 | 	    return card.value == ACE | 
 | 	topcard = self.cards[-1] | 
 | 	return card.suit == topcard.suit and card.value == topcard.value + 1 | 
 |  | 
 |  | 
 | class RowStack(OpenStack): | 
 |  | 
 |     def acceptable(self, cards): | 
 | 	card = cards[0] | 
 | 	if not self.cards: | 
 | 	    return card.value == KING | 
 | 	topcard = self.cards[-1] | 
 | 	if not topcard.face_shown: | 
 | 	    return 0 | 
 | 	return card.color != topcard.color and card.value == topcard.value - 1 | 
 |  | 
 |     def position(self, card): | 
 | 	y = self.y | 
 | 	for c in self.cards: | 
 | 	    if c == card: | 
 | 		break | 
 | 	    if c.face_shown: | 
 | 		y = y + 2*MARGIN | 
 | 	    else: | 
 | 		y = y + OFFSET | 
 | 	card.moveto(self.x, y) | 
 |  | 
 |  | 
 | class Solitaire: | 
 |  | 
 |     def __init__(self, master): | 
 | 	self.master = master | 
 |  | 
 | 	self.canvas = Canvas(self.master, | 
 | 			     background=BACKGROUND, | 
 | 			     highlightthickness=0, | 
 | 			     width=NROWS*XSPACING, | 
 | 			     height=3*YSPACING + 20 + MARGIN) | 
 | 	self.canvas.pack(fill=BOTH, expand=TRUE) | 
 |  | 
 | 	self.dealbutton = Button(self.canvas, | 
 | 				 text="Deal", | 
 | 				 highlightthickness=0, | 
 | 				 background=BACKGROUND, | 
 | 				 activebackground="green", | 
 | 				 command=self.deal) | 
 | 	Window(self.canvas, MARGIN, 3*YSPACING + 20, | 
 | 	       window=self.dealbutton, anchor=SW) | 
 |  | 
 | 	x = MARGIN | 
 | 	y = MARGIN | 
 |  | 
 | 	self.deck = Deck(x, y, self) | 
 |  | 
 | 	x = x + XSPACING | 
 | 	self.opendeck = OpenStack(x, y, self) | 
 | 	 | 
 | 	x = x + XSPACING | 
 | 	self.suits = [] | 
 | 	for i in range(NSUITS): | 
 | 	    x = x + XSPACING | 
 | 	    self.suits.append(SuitStack(x, y, self)) | 
 |  | 
 | 	x = MARGIN | 
 | 	y = y + YSPACING | 
 |  | 
 | 	self.rows = [] | 
 | 	for i in range(NROWS): | 
 | 	    self.rows.append(RowStack(x, y, self)) | 
 | 	    x = x + XSPACING | 
 |  | 
 | 	self.openstacks = [self.opendeck] + self.suits + self.rows | 
 | 	 | 
 | 	self.deck.fill() | 
 | 	self.deal() | 
 |  | 
 |     def wincheck(self): | 
 | 	for s in self.suits: | 
 | 	    if len(s.cards) != NVALUES: | 
 | 		return | 
 | 	self.win() | 
 | 	self.deal() | 
 |  | 
 |     def win(self): | 
 | 	"""Stupid animation when you win.""" | 
 | 	cards = [] | 
 | 	for s in self.openstacks: | 
 | 	    cards = cards + s.cards | 
 | 	while cards: | 
 | 	    card = random.choice(cards) | 
 | 	    cards.remove(card) | 
 | 	    self.animatedmoveto(card, self.deck) | 
 |  | 
 |     def animatedmoveto(self, card, dest): | 
 | 	for i in range(10, 0, -1): | 
 | 	    dx, dy = (dest.x-card.x)/i, (dest.y-card.y)/i | 
 | 	    card.moveby(dx, dy) | 
 | 	    self.master.update_idletasks() | 
 |  | 
 |     def closeststack(self, card): | 
 | 	closest = None | 
 | 	cdist = 999999999 | 
 | 	# Since we only compare distances, | 
 | 	# we don't bother to take the square root. | 
 | 	for stack in self.openstacks: | 
 | 	    dist = (stack.x - card.x)**2 + (stack.y - card.y)**2 | 
 | 	    if dist < cdist: | 
 | 		closest = stack | 
 | 		cdist = dist | 
 | 	return closest | 
 |  | 
 |     def deal(self): | 
 | 	self.reset() | 
 | 	self.deck.shuffle() | 
 | 	for i in range(NROWS): | 
 | 	    for r in self.rows[i:]: | 
 | 		card = self.deck.deal() | 
 | 		r.add(card) | 
 | 	for r in self.rows: | 
 | 	    r.showtop() | 
 |  | 
 |     def reset(self): | 
 | 	for stack in self.openstacks: | 
 | 	    while 1: | 
 | 		card = stack.deal() | 
 | 		if not card: | 
 | 		    break | 
 | 		self.deck.add(card) | 
 | 		card.showback() | 
 |  | 
 |  | 
 | # Main function, run when invoked as a stand-alone Python program. | 
 |  | 
 | def main(): | 
 |     root = Tk() | 
 |     game = Solitaire(root) | 
 |     root.protocol('WM_DELETE_WINDOW', root.quit) | 
 |     root.mainloop() | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |