Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /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('<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()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement