mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			637 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			637 lines
		
	
	
	
		
			15 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 = 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()
 | 
