mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 02:15:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			637 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			637 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
| #! /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 = list(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"] + list(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('<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()
 | 
