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.
2 Answers 2
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.
-
\$\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\$yren– yren2015年04月04日 06:16:59 +00:00Commented Apr 4, 2015 at 6:16
You forgot to put parentheses on all of the print statements.
-
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\$2022年11月01日 03:02:34 +00:00Commented Nov 1, 2022 at 3:02