6
\$\begingroup\$

It's been a long while since I've done any Python programming so I thought I'd start with a simple Blackjack game. Any comments on how I can make this more readable? Use more methods perhaps?

from sys import exit 
from random import randint 

class Game(object): 

    def __init__(self, start):
        self.quips = [
            " you kinda suck at this.",
            "don't quit your day job.",
            "Such a loser."
        ]
        self.start = start 

    #main method to start playing 
    def play(self):
        next = self.start 
        action = "y"        

        #start playing 
        while True:
            print "\n-------"
            print "Beginning blkjk"
            print "Dealing.. \n"        

            #deal user's cards 
            unum1 = randint(1,10)   
            unum2 = randint(1,10)
            utotal = unum1 + unum2 
            print "Ur cards are: %d & %d totalling %d \n" % (unum1, unum2,utotal)

            #hit until user says stop 
            while action == "y": 
                action = raw_input("Hit? > ")
                if action == "y": 
                    utotal = utotal + self.hit()    
                    print "ur new total is %d" % utotal             

            print "Ur total is %d " % utotal

            #deal dealer's cards 
            dnum1 = randint(1,10)   
            dnum2 = randint(1,10)
            dtotal = dnum1 + dnum2 
            print "Dealer's cards are %d & %d totalling %d \n" % (dnum1, dnum2, dtotal)

            #dealer keeps hitting until close/more than 21
            while dtotal < 21: 
                print"    Dealer hits\n"
                dtotal = dtotal + self.hit()

            if dtotal <= 21:
                print "Dealer's total is %d " % dtotal
                if utotal > dtotal: 
                    print "you win!"
                    exit(1)
                else:
                    print "dealer wins"
                    self.death()
            else:
                print "Dealer busts, u win!" 
                exit(1)                         




    def death(self):
        print self.quips[randint(0, len(self.quips)-1)]
        exit(1)

    def hit(self):
        nextcard = randint(1,9)
        print "ok, next card is %d " % nextcard 
        return nextcard 


a_game = Game("deal")
a_game.play() 
\$\endgroup\$

3 Answers 3

6
\$\begingroup\$

I don't think that having a huge "Game" class that contains the flow of the entire game is the way you want to do it. Why don't you break the game into some manageable chunks? Here's what I came up with after a little thinking:

import random
DEFAULT_CHIPS = 20

class Card(object):
    card_to_name = {1:"Ace", 2:"Two", 3:"Three", 4:"Four", 5:"Five", 6:"Six", 7:"Seven",
                    8:"Eight", 9:"Nine", 10:"Ten", 11:"Jack", 12:"Queen", 13:"King"}

    def __init__(self, value, suit):
        self.name = self.card_to_name[value]
        self.suit = suit
        self.title = "%s of %s" % (self.name, self.suit)
        self.score = min(value, 10)

    def __repr__(self):
        return self.title

class Hand(object):
    def __init__(self, cards):
        self.hand = cards

    def get_scores(self):
        num_aces = sum(card.name == "Ace" for card in self.hand)
        score = sum(card.score for card in self.hand)
        return [score + i*10 for i in range(num_aces+1)]

    def possible_scores(self):
            return [s for s in self.get_scores() if s <= 21]

    def __repr__(self):
        return str(self.hand)

class Deck(object):
    unshuffled_deck = [Card(card, suit) for card in range(1, 14) for suit in ["Clubs", "Diamonds", "Hearts", "Spades"]]
    def __init__(self, num_decks=1):
        self.deck = self.unshuffled_deck * num_decks
        random.shuffle(self.deck)

    def deal_card(self):
        return self.deck.pop()

    def deal_hand(self):
        return Hand([self.deal_card(), self.deal_card()])

class Player(object):
    def __init__(self, name="Player 1", chips=DEFAULT_CHIPS):
        self.name = name
        self.chips = chips
        self.current_bet = 0

    def new_hand(self, hand):
        self.hand = hand

    def hit(self, card):
        self.hand.hand.append(card)

    def is_busted(self):
        return len(self.hand.possible_scores()) == 0

    def scores(self):
            return self.hand.get_scores() if self.is_busted() else self.hand.possible_scores()

    def __repr__(self):
        player_str = self.name + "(BUSTED)" if self.is_busted() else self.name
        return "Player: {}\nChips: {}\nCurrent Bet: {}\nCards: {}\nScore: {}".format(
            player_str, self.chips, self.current_bet, self.hand, self.scores())

if __name__ == "__main__":
    d = Deck()
    print d.deck
    h = d.deal_hand()
    p = Player()
    p.new_hand(h)
    print p
    p.hit(d.deal_card())
    print p

Here is some test output:

[Two of Spades, Two of Diamonds, Three of Clubs, Ten of Hearts, Ace of
Diamonds, Eight of Diamonds, Seven of Diamonds, King of Hearts, Seven
of Hearts, Queen of Diamonds, Six of Clubs, Nine of Diamonds, Seven of
Spades, Four of Spades, Four of Diamonds, Ten of Clubs, Four of Clubs,
Jack of Hearts, Ace of Clubs, Six of Spades, Eight of Spades, Seven of
Clubs, Eight of Hearts, Three of Diamonds, Jack of Clubs, King of
Diamonds, Five of Clubs, King of Clubs, Ace of Spades, King of Spades,
Six of Diamonds, Ten of Diamonds, Queen of Clubs, Two of Hearts, Eight
of Clubs, Nine of Hearts, Ace of Hearts, Five of Spades, Jack of
Diamonds, Nine of Spades, Four of Hearts, Six of Hearts, Two of Clubs,
Three of Spades, Jack of Spades, Five of Diamonds, Three of Hearts, Ten
of Spades, Five of Hearts, Queen of Hearts, Nine of Clubs, Queen of
Spades]
Player: Player 1
Chips: 20
Current Bet: 0
Cards: [Queen of Spades, Nine of Clubs]
Score: [19]
Player: Player 1(BUSTED)
Chips: 20
Current Bet: 0
Cards: [Queen of Spades, Nine of Clubs, Queen of Hearts]
Score: [29]

From there, implementing the game in a clean fashion wouldn't be too tough.

\$\endgroup\$
5
\$\begingroup\$

Nolen Royalty has an excellent example of a well-structured version. Here are some comments on your version.

from sys import exit 
from random import randint 

class Game(object): 

    def __init__(self, start):
        self.quips = [
            " you kinda suck at this.",

Why the space?

            "don't quit your day job.",
            "Such a loser."

Why is only this one capitalized?

        ]

This seems to be a constant list, as such its better as a global constant or a class attribute then a object attribute.

        self.start = start 

    #main method to start playing 
    def play(self):
        next = self.start 
        action = "y"        

assign this closer to where you actually use it

        #start playing 
        while True:
            print "\n-------"

I suggest using an empty print to produce new lines for consistency

            print "Beginning blkjk"
            print "Dealing.. \n"        

            #deal user's cards 
            unum1 = randint(1,10)   
            unum2 = randint(1,10)

Give variables names that hint at their usage, unum1 doesn't really.

            utotal = unum1 + unum2 


            print "Ur cards are: %d & %d totalling %d \n" % (unum1, unum2,utotal)

            #hit until user says stop 
            while action == "y": 
                action = raw_input("Hit? > ")
                if action == "y": 
                    utotal = utotal + self.hit()    
                    print "ur new total is %d" % utotal  

You should put this block after the loop as it'll do the same thing.

            print "Ur total is %d " % utotal

            #deal dealer's cards 
            dnum1 = randint(1,10)   
            dnum2 = randint(1,10)

With all this different instances of getting a card, perhaps you should have a function to do it.

            dtotal = dnum1 + dnum2 
            print "Dealer's cards are %d & %d totalling %d \n" % (dnum1, dnum2, dtotal)

The same logic is being repeated again for the dealer as there was for the player. This suggest a class to represent a Hand of cards might be in order

            #dealer keeps hitting until close/more than 21
            while dtotal < 21: 
                print"    Dealer hits\n"
                dtotal = dtotal + self.hit()

            if dtotal <= 21:
                print "Dealer's total is %d " % dtotal
                if utotal > dtotal: 
                    print "you win!"
                    exit(1)

Don't use exit to close your programs. Just break out of any loops

                else:
                    print "dealer wins"

What if you tied?

                    self.death()
            else:
                print "Dealer busts, u win!" 
                exit(1)                         




    def death(self):
        print self.quips[randint(0, len(self.quips)-1)]

use random.choice to select a random element from a list

        exit(1)

    def hit(self):
        nextcard = randint(1,9)

Why 9 when the rest were 10?

        print "ok, next card is %d " % nextcard 
        return nextcard 


a_game = Game("deal")

You seem to be ignoring the parameter

a_game.play() 
\$\endgroup\$
3
\$\begingroup\$

One problem I see is that your dealing may result in card repetition, which would be illegal. You are going to need to track which card has already been played and not to use them again.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.