symbian-qemu-0.9.1-12/python-2.6.1/Demo/tkinter/guido/solitaire.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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()