6
\$\begingroup\$

I feel the best way to learn is practice. I have written a simple Blackjack game in Python which supports multiple-players and functionality like Hit, Stand, Surrender, Split and Double_Down. I need some help to review my code, in order to remove any redundant code, simplify the implementation, and improve the code quality over all.

Please let me know your thoughts, any feedback is welcome.

import random
SUITS = ['Clubs', 'Spades', 'Hearts', 'Diamonds']
RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
MAX_PLAYERS = 8
MAX_BALANCE = 1000
chip_balance = 0
class Card(object):
 def __init__(self, suit, rank):
 self.suit = suit
 self.rank = rank
 if rank == 'A':
 self.point = 11
 elif rank in ['K', 'Q', 'J']:
 self.point = 10
 else:
 self.point = int(rank)
 self.hidden = False
 def __str__(self):
 if self.hidden:
 return '[X]'
 else:
 return '[' + self.suit + ' ' + self.rank + ']'
 def hide_card(self):
 self.hidden = True
 def reveal_card(self):
 self.hidden = False
 def is_ace(self):
 return self.rank == 'A'
class Deck(object):
 def __init__(self):
 self.cards = [Card(suit, rank) for suit in SUITS for rank in RANKS]
 self.shuffle()
 def __str__(self):
 cards_in_deck = ''
 for card in self.cards:
 cards_in_deck = cards_in_deck + str(card) + ' '
 return cards_in_deck
 def shuffle(self):
 random.shuffle(self.cards)
 def deal_card(self):
 card = self.cards.pop(0)
 return card
class Hand(object):
 def __init__(self):
 self.hand = []
 def add_card(self, card):
 self.hand.append(card)
 return self.hand
 def get_value(self):
 aces = 0
 value = 0
 for card in self.hand:
 if card.is_ace():
 aces += 1
 value += card.point
 while (value > 21) and aces:
 value -= 10
 aces -= 1
 return value
class Dealer(Hand):
 def __init__(self, name, deck):
 Hand.__init__(self)
 self.name = name
 self.deck = deck
 self.isBust = False
 def show_hand(self):
 for card in self.hand:
 print card,
 print
 def hit(self):
 print "Hitting..."
 self.add_card(self.deck.deal_card())
 return self.hand
 def stand(self):
 print "%s gets %d. Done." % (self.name, self.get_value())
 def check_bust(self):
 if self.get_value() > 21:
 self.isBust = True
 print "%s gets bust!" % self.name
 else:
 self.stand()
class Player(Dealer):
 def __init__(self, name, deck, bet):
 Dealer.__init__(self, name, deck)
 self.bet = bet
 self.isBust = False
 self.isSurrender = False
 self.isSplit = False
 self.split = []
def play(player, deck):
 print player.name + ':',
 player.show_hand()
 if player.name == 'Dealer':
 while player.get_value() < 17:
 player.hit()
 player.show_hand()
 player.check_bust()
 else:
 global chip_balance
 if chip_balance > player.bet and not player.isSplit:
 if player.hand[0].point == player.hand[1].point:
 choice = input_func("Hit, Stand, DoubleDown, Split or Surrender? (h/s/d/p/u) ", str.lower,
 range_=('h', 's', 'd', 'p', 'u'))
 else:
 choice = input_func("Hit, Stand, DoubleDown or Surrender? (h/s/d/u) ", str.lower,
 range_=('h', 's', 'd', 'u'))
 else:
 choice = input_func("Hit, Stand or Surrender? (h/s/u) ", str.lower, range_=('h', 's', 'u'))
 while choice == 'h':
 player.hit()
 player.show_hand()
 if player.get_value() > 21:
 player.isBust = True
 print "%s gets bust!" % player.name
 break
 choice = input_func("Hit or Stand? (h/s) ", str.lower, range_=('h', 's'))
 if choice == 's':
 player.stand()
 if choice == 'd':
 chip_balance -= player.bet
 player.bet *= 2
 print "New balance = %d" % chip_balance
 player.hit()
 player.show_hand()
 player.check_bust()
 if choice == 'u':
 player.isSurrender = True
 chip_balance += (player.bet - player.bet / 2)
 print "New balance = %d" % chip_balance
 if choice == 'p':
 chip_balance -= player.bet
 print "New balance = %d" % chip_balance
 player.split.append(Player(' Split_1', deck, player.bet))
 player.split.append(Player(' Split_2', deck, player.bet))
 for p in player.split:
 p.add_card(player.hand.pop(0))
 p.add_card(deck.deal_card())
 p.isSplit = True
 play(p, deck)
def input_func(prompt, type_=None, min_=None, max_=None, range_=None):
 value = ''
 while True:
 value = raw_input(prompt)
 if type_ is not None:
 try:
 value = type_(value)
 except ValueError:
 print "Sorry I don't understand."
 continue
 if min_ is not None and value < min_:
 print "Sorry your input can not be less than %d!" % min_
 elif max_ is not None and value > max_:
 print "Sorry your input can not be more than %d!" % max_
 elif range_ is not None and value not in range_:
 print "You must select from", range_
 else:
 break
 return value
def report(player, dealer):
 global chip_balance
 if player.isSurrender:
 tag = 'surrender'
 elif player.isBust:
 tag = 'lose'
 elif len(player.hand) == 2 and player.get_value() == 21 and not player.isSplit:
 tag = 'blackjack'
 chip_balance += player.bet * 3
 elif dealer.isBust or (player.get_value() > dealer.get_value()):
 tag = 'win'
 chip_balance += player.bet * 2
 elif player.get_value() == dealer.get_value():
 tag = 'push'
 chip_balance += player.bet
 else:
 tag = 'lose'
 print "%s: %-*s Balance = %d" % (player.name, 10, tag, chip_balance)
def game():
 players = []
 global chip_balance
 deck = Deck()
 player_num = input_func("\nPlease enter the number of players: (1-8) ", int, 1, MAX_PLAYERS)
 print "\nLet's get started...\n"
 for i in range(player_num):
 if chip_balance > 0:
 player_name = 'Player_' + str(i + 1)
 print "%s:" % player_name
 player_bet = input_func("Please bet. The minimal bet is 1 chip. ", int, 1, chip_balance)
 chip_balance -= player_bet
 print "Balance updated. New balance is %d." % chip_balance
 player = Player(player_name, deck, player_bet)
 players.append(player)
 else:
 print "\nThe actual number of player is %d. There's no balance to support more players." % (len(players))
 break
 dealer = Dealer('Dealer', deck)
 for i in range(2):
 for player in (players + [dealer]):
 player.add_card(deck.deal_card())
 dealer.hand[1].hide_card()
 print "\nDealer:"
 dealer.show_hand()
 print
 dealer.hand[1].reveal_card()
 for player in (players + [dealer]):
 play(player, deck)
 print
 print "...Final result...\n"
 for player in players:
 if not player.split:
 report(player, dealer)
 else:
 print "%s: split" % player.name
 for p in player.split:
 report(p, dealer)
 print "\nFinal chip balance is %d.\n" % chip_balance
if __name__ == '__main__':
 chip_balance = input_func("\nWelcome to BlackJack! Please enter the chip balance: (1-1000) ", int, 1, MAX_BALANCE)
 while True:
 game()
 if chip_balance < 1:
 print "You don't have enough balance to proceed. Game over."
 break
 proceed = input_func("Do you want to continue? (y/n) ", str.lower, range_=('y', 'n'))
 if proceed == 'n':
 print "\nThank you for playing! See you next time."
 break

Below is the result:

Welcome to BlackJack! Please enter the chip balance: (1-1000) 100
Please enter the number of players: (1-8) 4
Let's get started...
Player_1:
Please bet. The minimal bet is 1 chip. 2
Balance updated. New balance is 98.
Player_2:
Please bet. The minimal bet is 1 chip. 2
Balance updated. New balance is 96.
Player_3:
Please bet. The minimal bet is 1 chip. 2
Balance updated. New balance is 94.
Player_4:
Please bet. The minimal bet is 1 chip. 2
Balance updated. New balance is 92.
Dealer:
[Clubs A] [X]
Player_1: [Diamonds J] [Clubs 2]
Hit, Stand, DoubleDown or Surrender? (h/s/d/u) h
Hitting...
[Diamonds J] [Clubs 2] [Hearts A]
Hit or Stand? (h/s) h
Hitting...
[Diamonds J] [Clubs 2] [Hearts A] [Diamonds 10]
Player_1 gets bust!
Player_2: [Clubs 7] [Hearts 5]
Hit, Stand, DoubleDown or Surrender? (h/s/d/u) d
New balance = 90
Hitting...
[Clubs 7] [Hearts 5] [Hearts 4]
Player_2 gets 16. Done.
Player_3: [Spades 3] [Spades 9]
Hit, Stand, DoubleDown or Surrender? (h/s/d/u) d
New balance = 88
Hitting...
[Spades 3] [Spades 9] [Diamonds 2]
Player_3 gets 14. Done.
Player_4: [Spades K] [Spades 5]
Hit, Stand, DoubleDown or Surrender? (h/s/d/u) h
Hitting...
[Spades K] [Spades 5] [Hearts K]
Player_4 gets bust!
Dealer: [Clubs A] [Spades 8]
Dealer gets 19. Done.
...Final result...
Player_1: lose Balance = 88
Player_2: lose Balance = 88
Player_3: lose Balance = 88
Player_4: lose Balance = 88
Final chip balance is 88.
Do you want to continue? (y/n) n
Thank you for playing! See you next time.
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Apr 3, 2015 at 2:26
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

The code generally looks good.


The inheritance hierarchy is weird: Dealer is a Hand? I would rather say that a dealer has a hand. Player is a Dealer? That sounds backwards — rather, a dealer is a player.

The difference in naming convention between properties (e.g. isBust) and method calls (e.g. get_value()) is annoying. You should use the is_bust convention uniformly. I would expect card.is_ace to be a property, rather than card.is_ace().


For Deck.__str__(), you could write

def __str__(self):
 return ' '.join(str(card) for card in self.cards)

... which also gets rid of the extra space at the end of any non-empty deck.

Deck.deal_card() should just be return self.cards.pop(). Dealing from the start of the array requires every card to be shifted over — that's a lot of unnecessary copying.


For Hand.get_value(), you should probably use sum():

def get_value(self):
 value = sum(card.point for card in self.cards)
 aces = sum(card.is_ace for card in self.cards)
 while (value > 21) and aces:
 value -= 10
 aces -= 1
 return value

The function name report() suggests that it merely prints the outcome. It's a bit surprising that it does more: it also awards the winnings. Therefore, award_winnings() would be a better name.

Winning double your bet for a Blackjack is unusually generous. Customarily, you only earn ×ばつ your bet.

answered Apr 3, 2015 at 8:53
\$\endgroup\$
1
  • \$\begingroup\$ Thanks!. The reason I let player inherit dealer is dealer is a more general type, it has some functions such as hit/stand that can be re-used by player, but player has more functions that dealer doesn't require, like surrender/split and attributes like isSplit etc. I don't want to initialize a object (dealer) that includes too many unused attributes and methods. Usually blackjack wins 1.5 x bet, but i wanted to avoid the floats, same reason for surrender: usually we get half bet back but in my program we will get (rounded) half, for example, 2 of 3 chips will be back once we surrender. \$\endgroup\$ Commented Apr 4, 2015 at 6:16
0
\$\begingroup\$

You forgot to put parentheses on all of the print statements.

answered Nov 1, 2022 at 1:26
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! The OP did not mention which version of Python they were using, nor was version 2.x or 3.x tagged, though given the age it may well be the case that it was intended for version 2. In Python 2.x print is actually a special statement and not a function*.. Might there be any other observations or recommendations about the code to mention that hasn’t been brought up in the other answer? \$\endgroup\$ Commented Nov 1, 2022 at 3:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.