Source Code: Blackjack Game
#! /usr/bin/python2.5
#
# $Id: blackjack.py 13 2008-02-22 11:58:32Z david $
# (c) 2008, David Chaves
#
# DESCRIPTION
#
# This program implements a basic blackjack card game
#
# In brief, each player is dealt 2 face up cards, and
# the dealer is dealt 1 face up and one face down card.
# In sequence, each player decides whether (s)he will take
# another card (hit) or not (stand). If the new card makes
# the total of that player's cards greater than 21,
# that player has busted, and loses his/her bet. Otherwise,
# the player may again choose to hit or stand
#
# Jacks, queens and kings count as 10, and an ace may count
# as either 1 or 11. After all of the players have gone,
# the dealer reveals the down card and hits until the
# dealer's total is greater than or equal to 17
#
# If the dealer busts, all players who did not bust win;
# otherwise, any player with a higher total than the deal win,
# and lower totals lose. If a player has the same total as
# the dealer, it is a push and the player neither wins nor loses
#
# Here is a sample game:
#
# |Number of players?
# |3
# |Player 1: J-D, 4-S
# |Player 2: 7-H, 8-C
# |Player 3: Q-H, 9-D
# |Dealer : A-S, <down>
# |Player 1, you have 14. Hit or stand?
# |hit
# |Player 1: J-D, 4-S, 3-D
# |Player 1, you have 17. Hit or stand?
# |h
# |Player 1: J-D, 4-S, 3-D, 8-H - You busted!
# |Player 2, you have 15. Hit or stand?
# |s
# |Player 3, you have 19. Hit or stand?
# |s
# |Dealer : A-S, 5-S
# |Dealer has 16. Dealer hits.
# |Dealer : A-S, 5-S, 8-D
# |Dealer has 14. Dealer hits.
# |Dealer : A-S, 5-S, 8-D, 3-C
# |Dealer has 17. Dealer stands.
# |Player 1: J-D, 4-S, 3-D, 8-H - You busted!
# |Player 2: 7-H, 8-C - You lose!
# |Player 3: Q-H, 9-D - You win!
# |Dealer : A-S, 5-S, 8-D, 3-C
#
# This program does not implement any of the special
# betting rules of blackjack (splitting, blackjack,
# insurance etc.) but conforms to the basic rules on
# http://en.wikipedia.org/wiki/Blackjack
#
# KNOWN BUGS
#
# We do not consider the case where the Deck becomes empty
# in the middle of the game. This situation should be fixed
# using run-time exceptions thrown in Deck.deal() and
# catched in Game.__init__() and Game.play()
#
# When the deck becomes empty, this program currently
# crashes like this:
#
# Traceback (most recent call last):
# ...
# File "blackjack.py"...
# position_in_deck = random.randint(0, len(self.cards_)-1)
# ...
# File "blackjack.py"...
# raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width)
#
# This program is also weak checking for valid inputs from end-users
#
# Finally, this is our first Python code in our whole life.
# In consecuence, the coding style can certantly be improved
#
# DISCLAIMER
#
# This script was genetically modified from a Python script
# originally written by an unknown author, but available
# at http://www.daniweb.com/code/snippet627.html
#
import random
#--------------------------------------- Constants
# Every card has a rank and a face value
#
# The following Rank maps a rank into the value:
# Ace 11 or 1
# J, Q, K are 10
# the rest of the cards 2 - 10 are face value
Ranks = {
# Rank => Value
'2' : 2,
'3' : 3,
'4' : 4,
'5' : 5,
'6' : 6,
'7' : 7,
'8' : 8,
'9' : 9,
'10' : 10,
'Jack' : 10,
'Queen' : 10,
'King' : 10,
'Ace' : 11
}
#--------------------------------------- class Card
class Card:
'''Represent a single card'''
def __init__(self, rank = 'Ace', suite = 'Heart'):
'''Constructor'''
self.rank_ = rank
self.suite_ = suite
def __int__(self):
'''Return the value for this card'''
return Ranks[self.rank_]
def __repr__(self):
'''Return the string representation for this card'''
return '%s-%s' % (self.rank_, self.suite_)
def is_ace(self):
'''Return True if this card is an Ace'''
return self.rank_ == 'Ace'
#--------------------------------------- class Deck
class Deck:
'''Represent a deck of cards'''
def __init__(self):
'''Constructor'''
# populate an initial list
# containing all possible cards
self.cards_ = []
for suite in [ 'Heart', 'Diamond', 'Spade', 'Club' ]:
for rank in Ranks.keys():
card = Card(rank, suite)
self.cards_.append(card)
# normally we would need to shuffle the deck
# being created, but this is not necessary
# since that we will draw the cards in
# randomly order
def deal(self):
'''Take the next card from the deck'''
# select a random card in the deck
# @see http://docs.python.org/lib/module-random.html
position_in_deck = random.randint(0, len(self.cards_)-1)
# take the card from the deck
card = self.cards_[position_in_deck]
self.cards_.pop(position_in_deck)
return card
#--------------------------------------- class Hand
class Hand:
'''Represent a hand of cards'''
def __init__(self, owner = 'Dealer'):
'''Constructor'''
self.cards_ = []
self.owner_ = owner
def hit(self, deck):
'''One card will be cut from the deck and copied to the hand'''
self.cards_.append(deck.deal())
def tally(self):
'''Return the total the value of the cards in this hand'''
total = aces = 0
for card in self.cards_:
total += int(card) # count the value of the hand
if card.is_ace(): # counting ACE(s) as 11
++aces
# to complicate things a little the ace can be 11 or 1
# this little while loop figures it out for you
while aces > 0 and total > 21:
# you have gone over 21 but there is an ace
# this will switch the ace from 11 to 1
total -= 10
aces -= 1
return total
def is_busted(self):
'''Return true if this hand is busted'''
return self.tally() > 21
def might_want_to_hit(self):
'''Return true if this hand might want to hit another card'''
# This function is used by the class Game to define the
# dealer strategy: s/he generally stands around 17 or 18
#
# However, it is made available to all players becuase
# we might want to hint them in the future... should we?
return self.tally() < 18
def owner(self):
'''Pretty print the owner name'''
return self.owner_
def cards(self):
'''Pretty print the cards in this hand'''
text = ''
for card in self.cards_:
if text:
text += ', '
text += '%s' % card
return text
def tally_and_cards(self):
'''Pretty print the value and the cards in this hand'''
return '%d: %s' % (self.tally(), self.cards())
def tally_and_first_card(self):
'''Pretty print the value and the first card in this hand'''
return '%d: %s, (DOWN)' % (self.tally(), self.cards_[0])
def win_or_lose(self, dealer):
'''Pretty print the win/lose final result'''
# If the dealer busts, all players
# who did not bust win; otherwise,
# any player with a higher total than
# the deal win, and lower totals lose.
# If a player has the same total as the dealer,
# it is a push and the player neither wins nor loses.
if self.is_busted():
return 'you busted'
if dealer.is_busted():
return 'you win'
total_hand = self.tally()
total_dealer = dealer.tally()
if total_dealer > total_hand:
return 'you lose'
elif total_dealer < total_hand:
return 'you win'
else:
return 'this is a push'
#--------------------------------------- class Player
class Player(Hand):
'''Represent a human player (not a dealer)'''
def __init__(self, owner = 'Player'):
'''Constructor'''
Hand.__init__(self, owner)
def header(self):
'''Pretty print the header message'''
return ' %s has %s' % (self.owner(), self.tally_and_cards())
def summary(self):
'''Pretty print the final, summary message'''
return ' %s has %s' % (self.owner(), self.tally_and_cards())
def hit_or_stand(self):
'''Pretty print the message used while this player plays'''
return '%s, you have %s' % (self.owner(), self.tally_and_cards())
#--------------------------------------- class Dealer
class Dealer(Hand):
'''Represent a dealer player (the computer)'''
def __init__(self):
'''Constructor'''
Hand.__init__(self, 'Dealer')
def header(self):
'''Pretty print the header message'''
return ' The Dealer has %s' % self.tally_and_first_card()
def summary(self):
'''Pretty print the final, summary message'''
return ' Dealer has %s' % self.tally_and_cards()
def hit_or_stand(self):
'''Pretty print the message used while the dealer plays'''
return 'Dealer has %s' % self.tally_and_cards()
#--------------------------------------- class Game
class Game:
'''Represent a game in progress'''
def __init__(self, players = range(1, 1)):
'''Constructor'''
self.deck_ = Deck()
self.players_ = {}
for player in players:
hand = Player('Player %s' % player)
# draw 2 cards for the player to start
hand.hit(self.deck_)
hand.hit(self.deck_)
self.players_[player] = hand
self.dealer_ = Dealer()
# draw 2 cards for the dealer to start
self.dealer_.hit(self.deck_)
self.dealer_.hit(self.deck_)
def players(self):
'''Syntatic sugar: return the list of player keys'''
return self.players_.keys()
def print_header(self):
'''Print the messages used at the beginning of the game'''
dealer = self.dealer_
for player in self.players():
hand = self.players_[player]
print hand.header()
print dealer.header()
print
def print_summary(self):
'''Print the messages used at the very end of the game'''
dealer = self.dealer_
for player in self.players():
hand = self.players_[player]
print '%s - %s!' % (hand.summary(), hand.win_or_lose(dealer))
print dealer.summary()
print
def play_player_loop(self, player):
'''This is the complete loop where a player... well, plays'''
hand = self.players_[player]
while True:
if hand.is_busted():
print '%s - You busted!' % hand.hit_or_stand()
break
# @todo -- we might want to wait until the player
# enters a valid answer here, but it is no worth the effort
# for now, an empty answer (just pressing [ENTER], for example)
# will be handled the same as "Hit, please"
question = '%s - Hit or stand? ' % hand.hit_or_stand()
answer = raw_input(question).lower()
if 's' in answer:
break
hand.hit(self.deck_)
print
def play_dealer_loop(self):
'''This is the complete loop where the dealer plays'''
# the dealer reveals the down card and hits until the
# dealer's total is greater than or equal to 17.
dealer = self.dealer_
while True:
if dealer.is_busted():
break
elif dealer.might_want_to_hit():
print '%s - Dealer hits' % dealer.hit_or_stand()
dealer.hit(self.deck_)
else:
break
print '%s - Dealer stands' % dealer.hit_or_stand()
print
def play(self):
'''Play a complete game, from the beginning to the end'''
self.print_header()
for player in self.players():
self.play_player_loop(player)
self.play_dealer_loop()
self.print_summary()
#--------------------------------------- Main Body
if __name__ == '__main__':
# if this file is executed from the command line,
# then we will ask the number of players and
# then we do play a complete run
print
print 'Welcome to BlackJack'
number_of_players = int(raw_input('Number of players? '))
print
players = range(1, number_of_players + 1)
blackjack = Game(players)
blackjack.play()
#--------------------------------------- The End