diff -r ffa851df0825 -r 2fb8b9db1c86 symbian-qemu-0.9.1-12/python-2.6.1/Demo/tkinter/guido/solitaire.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/symbian-qemu-0.9.1-12/python-2.6.1/Demo/tkinter/guido/solitaire.py Fri Jul 31 15:01:17 2009 +0100 @@ -0,0 +1,637 @@ +#! /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(%r, %r)" % (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('', self.doubleclickhandler) + self.group.bind('', self.motionhandler) + self.group.bind('', 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()