Spring Semester 2007


Nine: Object-Oriented Design: The Blackjack Game.
Consider the following code:
class one(object):
    def __init__(self, name):
        self.name = name
    def hurt(self):
        print self.name + ": ouch!..."
    def shoot(self, other):
        print self.name + ": I am shooting " + other.name
        other.hurt()

def test():
    a = one("Arthur")
    b = one("Buster")
    a.shoot(b)
    b.shoot(a)
    b.shoot(a)
It produces the following output:
>>> test()
Arthur: I am shooting Buster
Buster: ouch!...
Buster: I am shooting Arthur
Arthur: ouch!...
Buster: I am shooting Arthur
Arthur: ouch!...
>>> 

This is just like the Alien Blaster program. The difference is that we use one and the same type of objects.

Now would be a good time to set the goal for this chapter. Let's play the game, see how it goes.

Copying the blackjack.py file and running it won't be enough. Why? What's missing?

It needs two modules: games.py, and cards.py So, copy these first, then run the program.

This is important. Yes, but when you finally run it, how does it go?

Take a look. Hey, that's cool!
		Welcome to Blackjack!

How many players? (1 - 7): 2
Enter player name: Larry
Enter player name: Rick

Larry:	8d	4d	(12)
Rick:	9d	Qh	(19)
Dealer:	XX	Qs	

Larry, do you want a hit? (Y/N): y
Larry:	8d	4d	6c	(18)

Larry, do you want a hit? (Y/N): n

Rick, do you want a hit? (Y/N): n
Dealer:	Ac	Qs	(21)
Larry loses.
Rick loses.

Do you want to play again?: y
Larry:	3h	2s	(5)
Rick:	Jd	Ad	(21)
Dealer:	XX	7c	

Larry, do you want a hit? (Y/N): y
Larry:	3h	2s	Ah	(16)

Larry, do you want a hit? (Y/N): y
Larry:	3h	2s	Ah	10c	(16)

Larry, do you want a hit? (Y/N): y
Larry:	3h	2s	Ah	10c	Kh	(26)
Larry busts.
Larry loses.

Rick, do you want a hit? (Y/N): n
Dealer:	2c	7c	(9)
Dealer:	2c	7c	Jh	(19)
Rick wins.

Do you want to play again?: n

Press the enter key to exit.

Here are pictures of Larry and Rick. No wonder, then.

Wouldn't it be better if we could play with real cards? Oh, I like those. Of course it would...

So that's what we will do when we do web programming. Great. Let's get back to our current chapter.

We have already covered the alien blaster. We need to cover games and cards before we talk about blackjack.

Good. That's the big picture. On a smaller scale our next step is the playing cards program.

What does it do? Here's how it runs:
Printing a Card object:
Ac

Printing the rest of the objects individually:
2c
3c
4c
5c

Printing my hand before I add any cards:


Printing my hand after adding 5 cards:
Ac  2c  3c  4c  5c  

Gave the first two cards from my hand to your hand.
Your hand:
Ac  2c  
My hand:
3c  4c  5c  

My hand after clearing it:



Press the enter key to exit.

It's a test program, basically. That's right. Here's the main part of the program.
# main
card1 = Card(rank = "A", suit = "c")
print "Printing a Card object:"
print card1

card2 = Card(rank = "2", suit = "c")
card3 = Card(rank = "3", suit = "c")
card4 = Card(rank = "4", suit = "c")
card5 = Card(rank = "5", suit = "c")
print "\nPrinting the rest of the objects individually:"
print card2
print card3
print card4
print card5

my_hand = Hand()
print "\nPrinting my hand before I add any cards:"
print my_hand

my_hand.add(card1)
my_hand.add(card2)
my_hand.add(card3)
my_hand.add(card4)
my_hand.add(card5)
print "\nPrinting my hand after adding 5 cards:"
print my_hand

your_hand = Hand()
my_hand.give(card1, your_hand)
my_hand.give(card2, your_hand)
print "\nGave the first two cards from my hand to your hand."
print "Your hand:"
print your_hand
print "My hand:"
print my_hand

my_hand.clear()
print "\nMy hand after clearing it:"
print my_hand

raw_input("\n\nPress the enter key to exit.")
Can you go over it, briefly? I sure can. It starts by creating a card object.

It then creates a few more cards, prints them, assembles them in a hand. I suppose you mean Card, Hand instead.

As a matter of fact: yes. Whhat do objects of type Card look like?

Extremely simple structure. I'd say they look like 2D points. They're just pairs.
class Card(object):
    """ A playing card. """
    RANKS = ["A", "2", "3", "4", "5", "6", "7",
             "8", "9", "10", "J", "Q", "K"]
    SUITS = ["c", "d", "h", "s"]
    
    def __init__(self, rank, suit):
        self.rank = rank 
        self.suit = suit

    def __str__(self):
        rep = self.rank + self.suit
        return rep

What is a Hand? Nice little abstraction; in reality, a glorified array.
class Hand(object):
    """ A hand of playing cards. """
    def __init__(self):
        self.cards = []

    def __str__(self):
        if self.cards:
           rep = ""
           for card in self.cards:
               rep += str(card) + "  "
        else:
            rep = ""
        return rep

    def clear(self):
        self.cards = []

    def add(self, card):
        self.cards.append(card)

    def give(self, card, other_hand):
        self.cards.remove(card)
        other_hand.add(card)

Oh, boy! Why are you saying that?

I just realized there are two more versions of this program. Well, let's see them.

OK, here's what the second one does, first. Looks interesting...
Created a new deck.
Deck:


Populated the deck.
Deck:
Ac	2c	3c	4c	5c	6c	7c	8c	9c	10c	Jc	Qc	Kc	Ad	2d	3d	4d	5d	6d	
7d	8d	9d	10d	Jd	Qd	Kd	Ah	2h	3h	4h	5h	6h	7h	8h	9h	10h	Jh	Qh	
Kh	As	2s	3s	4s	5s	6s	7s	8s	9s	10s	Js	Qs	Ks	

Shuffled the deck.
Deck:
9h	Kd	Qc	9d	As	Js	4c	10d	5s	8h	6d	Jc	3s	2s	Qd	Qs	10s	5c	6h	
5d	7c	Jd	10c	Qh	4s	4d	2d	7h	Ah	6s	3d	2h	10h	Ad	Ks	Kc	7s	3c	
4h	9c	2c	8d	8s	Ac	7d	8c	Kh	9s	Jh	6c	3h	5h	

Dealt 5 cards to my hand and your hand.
My hand:
9h	Qc	As	4c	5s	
Your hand:
Kd	9d	Js	10d	8h	
Deck:
6d	Jc	3s	2s	Qd	Qs	10s	5c	6h	5d	7c	Jd	10c	Qh	4s	4d	2d	7h	Ah	
6s	3d	2h	10h	Ad	Ks	Kc	7s	3c	4h	9c	2c	8d	8s	Ac	7d	8c	Kh	9s	
Jh	6c	3h	5h	

Cleared the deck.
Deck: 


Press the enter key to exit.

Here's the program that produces that. Do the Card and Hand classes stay the same?
# main
deck1 = Deck()
print "Created a new deck."
print "Deck:"
print deck1

deck1.populate()
print "\nPopulated the deck."
print "Deck:"
print deck1

deck1.shuffle()
print "\nShuffled the deck."
print "Deck:"
print deck1

my_hand = Hand()
your_hand = Hand()
hands = [my_hand, your_hand]
deck1.deal(hands, per_hand = 5)
print "\nDealt 5 cards to my hand and your hand."
print "My hand:"
print my_hand
print "Your hand:"
print your_hand
print "Deck:"
print deck1

deck1.clear()
print "\nCleared the deck."
print "Deck:", deck1

raw_input("\n\nPress the enter key to exit.")

Yes, but as you can see, there's a new class. Deck. I like that.
class Deck(Hand):
    """ A deck of playing cards. """
    def populate(self):
        for suit in Card.SUITS:
            for rank in Card.RANKS: 
                self.add(Card(rank, suit))

    def shuffle(self):
        import random
        random.shuffle(self.cards)

    def deal(self, hands, per_hand = 1):
        for rounds in range(per_hand):
            for hand in hands:
                if self.cards:
                    top_card = self.cards[0]
                    self.give(top_card, hand)
                else:
                    print "Can't continue deal. Out of cards!"

Notice how a Deck is just a type of Hand. That's what I was saying.

Everything else is the same. Nice. What's the third version doing?

The out put is a bit cryptic. Well, then let's look at the code.
Printing a Card object:
Ac

Printing an Unprintable_Card object:


Printing a Positionable_Card object:
Ah
Flipping the Positionable_Card object.
Printing the Positionable_Card object:
XX


Press the enter key to exit.

Good idea. Here's the main part of it. Well, it looks like there's more to it than just that.
#main
card1 = Card("A", "c")
card2 = Unprintable_Card("A", "d")
card3 = Positionable_Card("A", "h")

print "Printing a Card object:"
print card1

print "\nPrinting an Unprintable_Card object:"
print card2

print "\nPrinting a Positionable_Card object:"
print card3
print "Flipping the Positionable_Card object."
card3.flip()
print "Printing the Positionable_Card object:"
print card3

raw_input("\n\nPress the enter key to exit.")

That's true. I see new types of objects there.

Correct. Which one do you want to see first? How do you define an Unprintable_Card?
class Unprintable_Card(Card):
    """ A Card that won't reveal its rank or suit when printed. """
    def __str__(self):
        return ""

We've already been through a lot of code so far. So what, I want to know what a Positionable_Card looks like.
class Positionable_Card(Card):
    """ A Card that can be face up or face down. """
    def __init__(self, rank, suit, face_up = True):
        super(Positionable_Card, self).__init__(rank, suit)
        self.is_face_up = face_up

    def __str__(self):
        if self.is_face_up:
            rep = super(Positionable_Card, self).__str__()
        else:
            rep = "XX"
        return rep

    def flip(self):
        self.is_face_up = not self.is_face_up

It's a Card that can be flipped. There you go!

So printing it and flipping it and printing it again shows. The back of the card is marked "XX".

Very clear now. We're almost there, step by step. What comes next?

Oh, man---I hope you realize this is just a brief description of things to come. Yes, but let them come. So what's next in our step by step development?

Here's a simple game being played between some adults. Oh. Who is Ben?
Welcome to the world's simplest game!

How many players? (2 - 5): 3
Player name: Rick
Player name: Larry
Player name: Reggie

Here are the game results:
Rick:	26
Larry:	71
Reggie:	53

Do you want to play again? (y/n): y
How many players? (2 - 5): 4
Player name: Ben
Player name: Reggie
Player name: Rick
Player name: Larry

Here are the game results:
Ben:	73
Reggie:	70
Rick:	19
Larry:	73

Do you want to play again? (y/n): n


Press the enter key to exit.

You don't know him. A friend of mine, he moved to Chicago this past summer. I never met him. He looks intimidating.

He plays good defense. Is he a lawyer?

More like an ambassador. Good for him. Let's see the source code now.
import games, random

print "Welcome to the world's simplest game!\n"

again = None
while again != "n":
    players = []
    num = games.ask_number(question = "How many players? (2 - 5): ",
                          low = 2, high = 5)
    for i in range(num):
        name = raw_input("Player name: ")
        score = random.randrange(100) + 1    
        player = games.Player(name, score)
        players.append(player)

    print "\nHere are the game results:"
    for player in players:
        print player
    
    again = games.ask_yes_no("\nDo you want to play again? (y/n): ")

raw_input("\n\nPress the enter key to exit.")

It's imperative now that we present the games module. I think so.
class Player(object):
    """ A player for a game. """
    def __init__(self, name, score = 0):
        self.name = name
        self.score = score

    def __str__(self):
        rep = self.name + ":\t" + str(self.score)
        return rep


def ask_yes_no(question):
    """Ask a yes or no question."""
    response = None
    while response not in ("y", "n"):
        response = raw_input(question).lower()
    return response


def ask_number(question, low, high):
    """Ask for a number within a range."""
    response = None
    while response not in range(low, high):
        response = int(raw_input(question))
    return response

  
if __name__ == "__main__":
    print "You ran this module directly (and did not 'import' it)."
    raw_input("\n\nPress the enter key to exit.")

Ok. That would be enough for one lecture. I think so too.

Then let's think of it all and resume it on Thursday. Sounds like a great idea to me.

Updated by Adrian German for A202/A598