7
\$\begingroup\$

This is a simple blackjack game I finished making using Python. I hope you like it and I'm open to any suggestions or critiques you would give me.

from random import shuffle
import sys


def deal(deck, player, dealer):
    shuffle(deck)

    for _ in range(2):
        player.append(deck.pop())
        dealer.append(deck.pop())


def score(hand):
    non_aces = [c for c in hand if c != 'A']
    aces = [c for c in hand if c == 'A']

    total = 0

    for card in non_aces:
        if card in 'JQK':
            total += 10
        else:
            total += int(card)

    for card in aces:
        if total <= 10:
            total += 11
        else:
            total += 1

    return total


def display_info(player, dealer, stand):
    print("Your cards: [{}] ({})".format(']['.join(player), score(player)))
    if stand:
        print("Dealer cards: [{}] ({})".format(']['.join(dealer), score(dealer)))
    else:
        print("Dealer cards: [{}] [?]".format(dealer[0]))


def results(player, dealer, hand, stand):
    if score(player) == 21 and hand:
        print("Blackjack! You won!")
        sys.exit()
    elif score(player) > 21:
        print("Busted. You lost!")
        sys.exit()
    if stand:
        if score(dealer) > 21:
            print("Dealer busted. You won!")
        elif score(player) > score(dealer):
            print("You beat the dealer! You won!")
        elif score(player) < score(dealer):
            print("You lost!")
        else:
            print("Push. Nobody wins or losses.")
        sys.exit()


def hit_stand(deck, player, dealer, hand, stand):
    print("What would you like to do")
    print("[1] - Hit\n[2] - Stand")
    choice = input("> ")
    hand = False
    if choice == '1':
        player.append(deck.pop())
    elif choice == '2':
        stand = True
        while score(dealer) <= 16:
            dealer.append(deck.pop())
        display_info(player, dealer, stand)
        results(player, dealer, hand, stand)


if __name__ == '__main__':
    deck = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']*4
    player = []
    dealer = []
    standing = False
    first_hand = True
    deal(deck, player, dealer)
    while True:
        display_info(player, dealer, standing)
        results(player, dealer, first_hand, standing)
        hit_stand(deck, player, dealer, first_hand, standing)

The follow-up is "Blackjack game - follow-up".

\$\endgroup\$
4
\$\begingroup\$

Welcome to CodeReview, and greetings! Your code looks nice: it is well indented, mostly PEP8-conformant, and has clear names. I suspect you may not have spent enough time in Atlantic City or Las Vegas, however. ;-)

Encapsulation

You correctly guard your "main routine" code with if __name__ == '__main__' but you didn't bother to put all that code into a function. Please do so. Call it main if you like, but take the chance to wrap it all up into something that can be invoked from outside, for testing.

def main():
    ''' Play one hand of blackjack. '''
    deck = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']*4
    player = []
    dealer = []
    standing = False
    first_hand = True
    deal(deck, player, dealer)
    while True:
        display_info(player, dealer, standing)
        results(player, dealer, first_hand, standing)
        hit_stand(deck, player, dealer, first_hand, standing)

if __name__ == '__main__':
    main()

Similarly, have a look at this:

if choice == '1':
    player.append(deck.pop())

And this:

while score(dealer) <= 16:
    dealer.append(deck.pop())

What are you doing there? That seems like another opportunity to use a name to describe what's happening:

if choice == '1':
    deal_card(shoe, player)

# ... and ...

while score(dealer) <= 16:
    deal_card(shoe, dealer)

Also, what's 16? Is that a magic number? Should it have a name? (Hint: yes!)

while score(dealer) <= DEALER_MINIMUM_SCORE:
    deal_card(shoe, dealer)

While we're looking at the hit_stand function, consider that it does two things:

def hit_stand(deck, player, dealer, hand, stand):
    print("What would you like to do")
    print("[1] - Hit\n[2] - Stand")
    choice = input("> ")

    ########## End of part 1, start of part 2 ##########

    hand = False
    if choice == '1':
        player.append(deck.pop())
    elif choice == '2':
        stand = True
        while score(dealer) <= 16:
            dealer.append(deck.pop())
        display_info(player, dealer, stand)
        results(player, dealer, hand, stand)

First, it asks for user input. Then it tries to update the game state based on that input. That's one thing too many!

Create separate functions for separate things. I know it may seem pointless when some of those functions are one line long. But it represents a chance for you to "write" an explanation of what you are doing, and it also provides a place to make changes to the behavior.

Also, consider that when you "grow" this blackjack game you will really want places to add behavior. There are many more possibilities than "hit or stand" at the table!

And always remember, user input is unreliable! You have to validate it. What if I type "hit" or "stand" instead of 1 or 2?

Control Flow

You don't actually have an exit condition for your while loop. Instead, you are relying on calls to sys.exit() from lower in your code. That's not going to work for very long as your code complexity grows. Instead, why not create a boolean variable still_playing = True and use while still_playing: as your loop condition. Then you can change that value when it's appropriate.

And your program structure doesn't really resemble the flow of a blackjack game. At a blackjack table, the dealer deals starting cards to all the players, then each player "plays" their entire game, then the dealer "plays" according to his rules, then the results are evaluated. You jammed your "dealer plays" element into the hit_stand function. Pry it loose! Make your flow look more like the real flow of the table, and it will be easier to understand and easier to extend.

Data Flow

In Pascal, a programming language we used in that last millennium by training dinosaurs to interpret the statements for us, there were two kinds of subroutines, procedures and functions. The difference was that a function could return a value, while a procedure just executed statements with no returned result. That discrimination doesn't exist in Python: you can do either thing, but it all starts with def. Regardless, you seem to write only subroutines that do not return values. I think you should change that.

Creating and shuffling the deck is a place where a function could be put to good use, especially if you grow your game. Just write:

shoe = new_shoe()

or later:

shuffle_shoe(shoe)

(If you can't tell, this is heading towards a Shoe class. That will come in a much later review. Also: a shoe is what the dealer takes the cards from.)

Speaking of data, have a look at this:

def hit_stand(deck, player, dealer, hand, stand):
    print("What would you like to do")
    print("[1] - Hit\n[2] - Stand")
    choice = input("> ")
    hand = False

Notice that you take a hand parameter, do nothing with it, and then set it to False? What's up with that? The only thing you use it for is to pass it down to results, but you could just as well write results(..., False, ...) and not bother with the hand parameter.

Also, have a look at the results subroutine. You call it in the middle of the loop, right after displaying the hands. But you've divided showing the hands and evaluating them, when that's kind of the same thing. And you're ignoring the distinction between "showing the current situation" and "wrapping up the end of the game." I'd suggest that your checking for blackjack and for busting should go near where you deal the cards, and should update the loop control variable.

Game Flow

There are a few aspects of blackjack that you are missing, even at this simple level. First, check for and announce blackjack, for both the dealer and the player! If a player gets blackjack, there's no Hit/Stand question, there's only the question of whether the dealer pushes. And if the dealer gets blackjack, the game is over before it began. So check that first.

The obvious extras, like assigning and displaying card suits as well as ranks, multiple players, support for splits, doubling down, insurance, surrendering, etc., would all take you away from a "simple" blackjack game. But your code should be organized in such a way that it is simple to add those features!

Update:

To answer your question from the comments,

Also, how do I handle the choice variable in the second function if I split hit_stand() as you suggested? And how results() should look like after I make still_playing Boolean?

Let's say hit_or_stand becomes a separate function. You would write something like:

def hit_or_stand():
    while True:
        ans = input("Hit or stand? ").lower()
        if ans in ('hit', 'stand'):
            return ans

# In your main loop:

player_stands = False
while not player_stands:
    # stuff like before
    if hit_or_stand() == 'stand':
        player_stands = True

    if not player_stands:
        deal_one_card(shoe, player)
        if score(player) > 21:
            break

# After loop: dealer takes cards, etc.
results()

The results can stay mostly the same. But some of your passed in variables are not needed, since you can compute them from the other data. For example, if the len(player) is 2, that's the first_hand flag. What's key is that results is outside the loop, not inside. You print the results after breaking/exiting from the loop.

\$\endgroup\$
  • \$\begingroup\$ Hey! Thank you for the answer. And yeah, I am from Europe and I don't really know all the rules of blackjack (apart from hitting, standing and basic stuff). \$\endgroup\$ – Maria Laura Mar 3 at 19:10
  • 1
    \$\begingroup\$ Also, how do I handle the choice variable in the second function if I split hit_stand() as you suggested? And how results() should look like after I make still_playing Boolean? \$\endgroup\$ – Maria Laura Mar 3 at 20:34
  • \$\begingroup\$ Hey! So I tried rewriting the script and it will keep asking for input after I bust. pastebin.com/7x5LGRzY Thank you. \$\endgroup\$ – Maria Laura Mar 6 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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