Sometimes you want to add just a little more functionality to an existing python object. You can do that by subclassing it and adding on the missing parts. This example is based on a really mindless game of solitaire I used to play as a kid. In this game you start with a shuffled deck. You deal the cards face up one by one. Play doesn't actually start until you have four cards laid out (place them face up, left to right, or fan them out in your hand). Every time you add a card you examine the four most recent cards (rightmost).
If the last card and the fourth last card are the same rank then you remove the last four cards.
If the last card and the fourth last card are the same suit then you remove the two cards between them.
Play continues until you have dealt all the cards. If you have no cards left in your hand at that point then you win.
I knew it was difficult to win a hand (based on never having done so). I wanted to know if it was even possible. Rather than playing and hoping for a win I decided to script it. My first attempt a couple of years back was written in vbScript. This version is written in python. For the record, the vbScript version is 143 lines. The python version is 54 (comments and blank lines not counted).
The basic unit of the game is a card, which consists of a rank (A23..TJQK) and a suit. Both a hand and a deck consist of a list of cards. For the purpose of this game, the only functionality I want to add is the ability to produce a compact string for printing the results.
There are a number of functions that operate the same no matter what objects they are used on. For example, you can get the length of something with the
len(something) function, or convert something to a string with the
str(something) function. Similarly, you can compare things with logical operators like
<=, etc. These are actually implemented on an object by object basis by using the so-called magic methods. I can show you how this works with the card object which (in part) looks like
class Card: def __init__(self, rank, suit): self.rank = rank self.suit = suit
By redefining the magic method
__eq__ as follows:
def __eq__(self, other): return (self.rank == other.rank and self.suit == other.suit)
I can now compare two card objects for equality by
if carda == cardb:
and python will use my overridden
__eq__ method to do the comparison. Likewise, I can redefine the magic method
def __str__(self): return str(self.rank) + self.suit
and when I want to convert card to a string I can just do
I have similarly redefined the
__str__ method in both the Hand and Deck classes. Purists would say that Hand and Deck are so similar that they should not be separate classes but I thought I would just keep it simple here.
""" Name: Solitaire.py Description: This program simulates multiple runs of a pretty mindless game of solitaire that relies only on the luck of the cards. There is no skill involved. In this game, cards are pulled from the deck one at a time and added to your hand. If you have two cards exactly four apart that have the same suit, remove the two cards between these cards. If you have two cards exactly four cards apart of the same rank remove these two cards AND the two cards between them. The idea is to go through the entire deck and end up with as few cards as possible. I wanted to know if it was actually possible to end up with zero cards but I didn't feel like playing for long enough to win so I wrote this instead. It displays the game number, the starting deck, then the hand after every change (card added/cards deleted). It stops when a win is detected (no cards remaining). Audit: 2020-08-27 rj original code """ import random class Card: def __init__(self, rank, suit): self.rank = rank self.suit = suit def __eq__(self, other): return (self.rank == other.rank and self.suit == other.suit) def __str__(self): return str(self.rank) + self.suit class Hand(list): def __init__(self): super().__init__() def __str__(self): s = "" for card in self: s += " " + str(card) return s.strip() class Deck(list): def __init__(self): super().__init__() suits = (chr(9824), chr(9829), chr(9830), chr(9827)) for i in range(52): card = Card("A23456789TJQK"[i % 13], suits[i//13]) self.append(card) def __str__(self): s = "" for card in self: s += " " + str(card) return s.strip() games = 0 random.seed() while True: games += 1 deck = Deck() hand = Hand() random.shuffle(deck) print("\ngame #%d\n" % games) #loop until the deck is empty while len(deck): #draw another card and display the current hand hand.append(deck.pop()) print(str(hand)) #remove cards as long as we are able while True: count = len(hand) #if rank of last) card = rank of 4th last card then remove last 4 if len(hand) > 3 and hand[-1].rank == hand[-4].rank: for i in range(4): del hand[-1] print(str(hand)) #if suit of last card = suit of 4th last card then remove cards between if len(hand) > 3 and hand[-1].suit == hand[-4].suit: for i in range(2): del hand[-2] print(str(hand)) #quit if no cards were removed if count == len(hand): break if len(hand): print("\nLOST - %d cards left" % len(hand)) else: print("WON in %d games" % games) #if no cards left then we won if not len(hand): break