summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1996-12-30 02:20:29 (GMT)
committerGuido van Rossum <guido@python.org>1996-12-30 02:20:29 (GMT)
commit1b2b53a25d27d96f60926802226dd375767cb3b2 (patch)
tree294cd6997801ddbbf9f92120364f41effb4ed81a
parent8de9f894e1021fc6b5799156e3be89ca35523ce8 (diff)
downloadcpython-1b2b53a25d27d96f60926802226dd375767cb3b2.zip
cpython-1b2b53a25d27d96f60926802226dd375767cb3b2.tar.gz
cpython-1b2b53a25d27d96f60926802226dd375767cb3b2.tar.bz2
Considerably restructured. This fixes the one remaining bug.
-rwxr-xr-xDemo/tkinter/guido/solitaire.py641
1 files changed, 322 insertions, 319 deletions
diff --git a/Demo/tkinter/guido/solitaire.py b/Demo/tkinter/guido/solitaire.py
index 311ae2a..4c1bb78 100755
--- a/Demo/tkinter/guido/solitaire.py
+++ b/Demo/tkinter/guido/solitaire.py
@@ -11,14 +11,6 @@ Limitations:
- No keyboard shortcuts.
- Less fancy animation when you win.
- The determination of which stack you drag to is more relaxed.
-
-Bugs:
-
-- When you double-click a card on a temp stack to move it to the suit
-stack, if the next card is face down, you have to wait until the
-double-click time-out expires before you can click it to turn it.
-I think this has to do with Tk's multiple-click detection, which means
-it's hard to work around.
Apology:
@@ -34,11 +26,12 @@ import math
import random
from Tkinter import *
-from Canvas import Rectangle, CanvasText, Group
+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. This is what should be used:
+# 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):
@@ -48,7 +41,8 @@ class Group(Group):
# 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.
+# 2*MARGIN pixels. OFFSET is the offset used for displaying the
+# face down cards in the row stacks.
CARDWIDTH = 100
CARDHEIGHT = 150
@@ -90,14 +84,15 @@ ALLSUITS = COLOR.keys()
NSUITS = len(ALLSUITS)
-# Card values are 1-13, with symbolic names for the picture cards.
-# ALLVALUES is a list of all card values.
+# 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
@@ -113,59 +108,33 @@ VALNAMES = ["", "A"] + map(str, range(2, 11)) + ["J", "Q", "K"]
NROWS = 7
-# The rest of the program consists of class definitions. Read their
-# doc strings.
-
-class Bottom:
-
- """A "card-like" object to serve as the bottom for some stacks.
-
- Specifically, this is used by the deck and the suit stacks.
-
- """
-
- def __init__(self, stack):
-
- """Constructor, taking the stack as an argument.
-
- We displays ourselves as a gray rectangle the size of a
- playing card, positioned at the stack's x and y location.
-
- We register the stack's bottomhandler to handle clicks.
-
- No other behavior.
-
- """
-
- self.rect = Rectangle(stack.game.canvas,
- stack.x, stack.y,
- stack.x+CARDWIDTH, stack.y+CARDHEIGHT,
- outline='black', fill='gray')
- self.rect.bind('<ButtonRelease-1>', stack.bottomhandler)
+# 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
- turnover() -- turn the card (face up or down) & raise it
- onclick(handler), ondouble(handler), onmove(handler),
- onrelease(handler) -- set various mount event handlers
- reset() -- move the card out of sight, face down, and reset all
- event handlers
- Public instance variables:
+ Public read-only instance variables:
- color, suit, value -- the card's color, suit and value
+ suit, value, color -- the card's suit, value and color
face_shown -- true when the card is shown face up, else false
- Semi-public instance variables (XXX should be made private):
+ 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
@@ -176,403 +145,440 @@ class 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.)
+ is reversed. The card is created face down.)
"""
- def __init__(self, game, suit, value):
+ 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.color = COLOR[suit]
self.value = value
- canvas = game.canvas
+ self.color = COLOR[suit]
+ self.face_shown = 0
+
self.x = self.y = 0
- self.__back = Rectangle(canvas, MARGIN, MARGIN,
- CARDWIDTH-MARGIN, CARDHEIGHT-MARGIN,
- outline='black', fill='blue')
- self.__rect = Rectangle(canvas, 0, 0, CARDWIDTH, CARDHEIGHT,
- outline='black', fill='white')
+ 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 = Group(canvas)
- self.group.addtag_withtag(self.__back)
- self.group.addtag_withtag(self.__rect)
self.group.addtag_withtag(self.__text)
- self.reset()
+
+ 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 "Card(game, %s, %s)" % (`self.suit`, `self.value`)
+ """Return a string for debug print statements."""
+ return "Card(%s, %s)" % (`self.suit`, `self.value`)
def moveto(self, x, y):
- dx = x - self.x
- dy = y - self.y
- self.group.move(dx, dy)
- self.x = x
- self.y = y
+ """Move the card to absolute position (x, y)."""
+ self.moveby(x - self.x, y - self.y)
def moveby(self, dx, dy):
- self.moveto(self.x + dx, self.y + 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
- def turnover(self):
- if self.face_shown:
- self.showback()
- else:
- self.showface()
- def onclick(self, handler):
- self.group.bind('<1>', handler)
+class Stack:
- def ondouble(self, handler):
- self.group.bind('<Double-1>', handler)
+ """A generic stack of cards.
- def onmove(self, handler):
- self.group.bind('<B1-Motion>', handler)
+ This is used as a base class for all other stacks (e.g. the deck,
+ the suit stacks, and the row stacks).
- def onrelease(self, handler):
- self.group.bind('<ButtonRelease-1>', handler)
+ Public methods:
- def reset(self):
- self.moveto(-1000, -1000) # Out of sight
- self.onclick('')
- self.ondouble('')
- self.onmove('')
- self.onrelease('')
- self.showback()
+ 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
-class Deck:
+ Method that subclasses may override:
- def __init__(self, game):
- self.game = game
- self.allcards = []
- for suit in ALLSUITS:
- for value in ALLVALUES:
- self.allcards.append(Card(self.game, suit, value))
- self.reset()
+ position(card) -- move the card to its proper (x, y) position
- def shuffle(self):
- n = len(self.cards)
- newcards = []
- for i in randperm(n):
- newcards.append(self.cards[i])
- self.cards = newcards
+ The default position() method places all cards at the stack's
+ own (x, y) position.
- def deal(self):
- # Raise IndexError when no more cards
- card = self.cards[-1]
- del self.cards[-1]
- return card
+ userclickhandler(), userdoubleclickhandler() -- called to do
+ subclass specific things on single and double clicks
- def accept(self, card):
- if card not in self.cards:
- self.cards.append(card)
+ The default user (single) click handler shows the top card
+ face up. The default user double click handler calls the user
+ single click handler.
- def reset(self):
- self.cards = self.allcards[:]
- for card in self.cards:
- card.reset()
+ usermovehandler(cards) -- called to complete a subpile move
-def randperm(n):
- r = range(n)
- x = []
- while r:
- i = random.choice(r)
- x.append(i)
- r.remove(i)
- return x
+ The default user move handler moves all moved cards back to
+ their original position (by calling the position() method).
-class Stack:
+ Private methods:
- x = MARGIN
- y = MARGIN
+ clickhandler(event), doubleclickhandler(event),
+ motionhandler(event), releasehandler(event) -- event handlers
- def __init__(self, game):
+ 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 "<Stack at (%d, %d)>" % (self.x, self.y)
+ """Return a string for debug print statements."""
+ return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y)
- def reset(self):
- self.cards = []
-
- def acceptable(self, cards):
- return 1
+ # Public methods
- def accept(self, card):
+ def add(self, card):
self.cards.append(card)
- card.onclick(self.clickhandler)
- card.onmove(self.movehandler)
- card.onrelease(self.releasehandler)
- card.ondouble(self.doublehandler)
card.tkraise()
- self.placecard(card)
+ self.position(card)
+ self.group.addtag_withtag(card.group)
- def placecard(self, card):
- card.moveto(self.x, self.y)
+ def delete(self, card):
+ self.cards.remove(card)
+ card.group.dtag(self.group)
def showtop(self):
if self.cards:
self.cards[-1].showface()
- def clickhandler(self, event):
- pass
+ def deal(self):
+ if not self.cards:
+ return None
+ card = self.cards[-1]
+ self.delete(card)
+ return card
- def movehandler(self, event):
- pass
+ # Subclass overridable methods
- def releasehandler(self, event):
- pass
+ def position(self, card):
+ card.moveto(self.x, self.y)
- def doublehandler(self, event):
- pass
+ def userclickhandler(self):
+ self.showtop()
+
+ def userdoubleclickhandler(self):
+ self.userclickhandler()
-class PoolStack(Stack):
+ def usermovehandler(self, cards):
+ for card in cards:
+ self.position(card)
- def __init__(self, game):
- Stack.__init__(self, game)
- self.bottom = Bottom(self)
+ # 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):
- if not self.cards:
- return
- card = self.cards[-1]
- self.game.turned.accept(card)
- del self.cards[-1]
- card.showface()
+ self.keepmoving(event)
+ self.finishmoving()
- def bottomhandler(self, event):
- cards = self.game.turned.cards
- cards.reverse()
- for card in cards:
- card.showback()
- self.accept(card)
- self.game.turned.reset()
+ def doubleclickhandler(self, event):
+ self.finishmoving() # In case we lost an event
+ self.userdoubleclickhandler()
+ self.startmoving(event)
-class MovingStack(Stack):
+ # Move internals
- thecards = None
- theindex = None
+ moving = None
- def clickhandler(self, event):
- self.thecards = self.theindex = None # Just in case
+ def startmoving(self, event):
+ self.moving = None
tags = self.game.canvas.gettags('current')
- if not tags:
- return
- tag = tags[0]
for i in range(len(self.cards)):
card = self.cards[i]
- if tag == str(card.group):
+ if card.group.tag in tags:
break
else:
return
- self.theindex = i
- self.thecards = Group(self.game.canvas)
- for card in self.cards[i:]:
- self.thecards.addtag_withtag(card.group)
- self.thecards.tkraise()
- self.lastx = self.firstx = event.x
- self.lasty = self.firsty = event.y
-
- def movehandler(self, event):
- if not self.thecards:
- return
- card = self.cards[self.theindex]
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.thecards.move(dx, dy)
self.lastx = event.x
self.lasty = event.y
+ if dx or dy:
+ for card in self.moving:
+ card.moveby(dx, dy)
- def releasehandler(self, event):
- cards = self._endmove()
- if not cards:
- return
+ 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 usermovehandler(self, cards):
card = cards[0]
- if not card.face_shown:
- if len(cards) == 1:
- card.showface()
- self.thecards = self.theindex = None
- return
- stack = self.game.closeststack(cards[0])
- if stack and stack is not self and stack.acceptable(cards):
- for card in cards:
- stack.accept(card)
- self.cards.remove(card)
+ 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.placecard(card)
+ self.delete(card)
+ stack.add(card)
+ self.game.wincheck()
- def doublehandler(self, event):
- cards = self._endmove()
- if not cards:
+ def userdoubleclickhandler(self):
+ if not self.cards:
return
- for stack in self.game.suits:
- if stack.acceptable(cards):
- break
- else:
+ card = self.cards[-1]
+ if not card.face_shown:
+ self.userclickhandler()
return
- for card in cards:
- stack.accept(card)
- del self.cards[self.theindex:]
- self.thecards = self.theindex = None
-
- def _endmove(self):
- if not self.thecards:
- return []
- self.thecards.move(self.firstx - self.lastx,
- self.firsty - self.lasty)
- self.thecards.dtag()
- cards = self.cards[self.theindex:]
- if not cards:
- return []
- card = cards[0]
- card.moveby(self.lastx - self.firstx, self.lasty - self.firsty)
- self.lastx = self.firstx
- self.lasty = self.firsty
- return cards
-
-class TurnedStack(MovingStack):
-
- x = XSPACING + MARGIN
- y = MARGIN
+ for s in self.game.suits:
+ if s.acceptable([card]):
+ self.delete(card)
+ s.add(card)
+ self.game.wincheck()
+ break
-class SuitStack(MovingStack):
- y = MARGIN
+class SuitStack(OpenStack):
- def __init__(self, game, i):
- self.index = i
- self.x = MARGIN + XSPACING * (i+3)
- Stack.__init__(self, game)
- self.bottom = Bottom(self)
+ def makebottom(self):
+ bottom = Rectangle(self.game.canvas,
+ self.x, self.y,
+ self.x+CARDWIDTH, self.y+CARDHEIGHT,
+ outline='black', fill='')
- bottomhandler = ""
+ def userclickhandler(self):
+ pass
- def __repr__(self):
- return "SuitStack(game, %d)" % self.index
+ def userdoubleclickhandler(self):
+ pass
def acceptable(self, cards):
if len(cards) != 1:
return 0
card = cards[0]
- if not card.face_shown:
- return 0
if not self.cards:
return card.value == ACE
topcard = self.cards[-1]
- if not topcard.face_shown:
- return 0
return card.suit == topcard.suit and card.value == topcard.value + 1
- def doublehandler(self, event):
- pass
-
- def accept(self, card):
- MovingStack.accept(self, card)
- if card.value == KING:
- # See if we won
- for s in self.game.suits:
- card = s.cards[-1]
- if card.value != KING:
- return
- self.game.win()
- self.game.deal()
-
-class RowStack(MovingStack):
-
- def __init__(self, game, i):
- self.index = i
- self.x = MARGIN + XSPACING * i
- self.y = MARGIN + YSPACING
- Stack.__init__(self, game)
- def __repr__(self):
- return "RowStack(game, %d)" % self.index
-
- def placecard(self, card):
- offset = 0
- for c in self.cards:
- if c is card:
- break
- if c.face_shown:
- offset = offset + 2*MARGIN
- else:
- offset = offset + OFFSET
- card.moveto(self.x, self.y + offset)
+class RowStack(OpenStack):
def acceptable(self, cards):
card = cards[0]
- if not card.face_shown:
- return 0
if not self.cards:
return card.value == KING
topcard = self.cards[-1]
if not topcard.face_shown:
return 0
- if card.value != topcard.value - 1:
- return 0
- if card.color == topcard.color:
- return 0
- return 1
+ 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.buttonframe = Frame(self.master, background=BACKGROUND)
- self.buttonframe.pack(fill=X)
+ 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.buttonframe,
+ self.dealbutton = Button(self.canvas,
text="Deal",
highlightthickness=0,
background=BACKGROUND,
activebackground="green",
command=self.deal)
- self.dealbutton.pack(side=LEFT)
+ Window(self.canvas, MARGIN, 3*YSPACING + 20,
+ window=self.dealbutton, anchor=SW)
- self.canvas = Canvas(self.master,
- background=BACKGROUND,
- highlightthickness=0,
- width=NROWS*XSPACING,
- height=3*YSPACING)
- self.canvas.pack(fill=BOTH, expand=TRUE)
+ x = MARGIN
+ y = MARGIN
- self.deck = Deck(self)
-
- self.pool = PoolStack(self)
- self.turned = TurnedStack(self)
+ 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):
- self.suits.append(SuitStack(self, i))
+ 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(self, i))
+ self.rows.append(RowStack(x, y, self))
+ x = x + XSPACING
+ 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 = self.deck.allcards
+ cards = []
+ for s in self.suits:
+ cards = cards + s.cards
+ if not cards:
+ return
for i in range(1000):
card = random.choice(cards)
dx = random.randint(-50, 50)
@@ -592,27 +598,24 @@ class Solitaire:
cdist = dist
return closest
- def reset(self):
- self.pool.reset()
- self.turned.reset()
- for stack in self.rows + self.suits:
- stack.reset()
- self.deck.reset()
-
def deal(self):
self.reset()
self.deck.shuffle()
for i in range(NROWS):
for r in self.rows[i:]:
card = self.deck.deal()
- r.accept(card)
+ r.add(card)
for r in self.rows:
r.showtop()
- try:
+
+ def reset(self):
+ for stack in [self.opendeck] + self.suits + self.rows:
while 1:
- self.pool.accept(self.deck.deal())
- except IndexError:
- pass
+ card = stack.deal()
+ if not card:
+ break
+ self.deck.add(card)
+ card.showback()
# Main function, run when invoked as a stand-alone Python program.