4
\$\begingroup\$

I am brand new to coding, and am just working my way through the basics. This is the Day 11 project for the 100 days of code on Angela Yu's course on Udemy. I am not familiar with classes yet, so none were used. I definitely feel like I overused if elif statements, like a lot, and probably could have utilized functions better, but had trouble getting them to work when I tried them in various places. I wanted to do this one on my own, so I skipped all of the hints and tutorials and just did it. I know it's a mess, but any feedback would be very much appreciated. Thanks for looking!

Main

from day_11_library import *
from os import system
from time import sleep


player_wallet = 0

def blackjack(player_wallet):

    # deposits money if player hits $0
    if player_wallet <= 0:
        player_wallet += 100

    #creates empty list for dealer hand, sets flag for wile loop and sets dealer total to 0
    dealer_hand = []
    dealer_total = 0
    dealer_flag = True

    #draws cards for dealer and returns result
    while dealer_flag == True:
        if dealer_total == 0:
            dealer_card_1 = draw()
            dealer_card_2 = draw()
            dealer_total += dealer_card_1[1] + dealer_card_2[1]
            dealer_hand.append(dealer_card_1[0])
            dealer_hand.append(dealer_card_2[0])
        elif dealer_total == 21 and len(dealer_hand) == 2:
            dealer_result = "blackjack"
            dealer_flag = False
        elif dealer_total == 21:
            dealer_result = "21"
            dealer_flag = False
        elif dealer_total < 17:
            dealer_draw = draw()
            dealer_total += dealer_draw[1]
            dealer_hand.append(dealer_draw[0])
        elif 17 <= dealer_total < 21:
            dealer_result = 'stand'
            dealer_flag = False
        elif dealer_total > 21:
            for card in dealer_hand:
                if card == 'A':
                    dealer_hand[dealer_hand.index(card)] = 'x'
                    dealer_total -= 10
                    break
            if dealer_total > 21:
                dealer_result = "bust"
                dealer_flag = False

    #returns dealers remvoed aces
    for ace in dealer_hand:
        if ace == 'x':
            dealer_hand[dealer_hand.index(ace)] = 'A'

    print(f'\nThe dealer is showing:\n'
          f'{dealer_hand[0]}')

    # creates empty list for player hand, sets flag for wile loop and sets player total to 0
    player_hand = []
    player_split_1 = []
    player_split_2 = []
    player_total = 0
    split_total_1 = 0
    split_total_2 = 0
    split_flag_1 = False
    split_flag_2 = False
    player_flag = True
    player_wallet -= 5

    while player_flag == True:
        # Initial player draw
        if player_total == 0:
            player_card_1 = draw()
            player_card_2 = draw()
            player_hand.append(player_card_1[0])
            player_hand.append(player_card_2[0])
            player_total += player_card_1[1] + player_card_2[1]
            print('\nYour hand is:')
            hand = player_hand
            print_cards(hand)
            print(f'\n\nYour balance is:\n'
                  f'${player_wallet}\n')


            # split 1 21 or blackjack
        if split_total_1 == 21 and len(player_split_1) == 2:
            split_1_result = 'blackjack'
        elif split_total_1 == 21:
            split_1_result = '21'

            # split 2 21 or blackjack
        if split_total_2 == 21 and len(player_split_2) == 2:
            split_2_result = 'blackjack'
            player_flag = False
            sleep(.25)
            system('clear')
        elif split_total_2 == 21:
            split_2_result = '21'
            player_flag = False
            sleep(.25)
            system('clear')

            # player 21 or blackjack
        if player_total == 21 and len(player_hand) == 2:
            player_result = 'blackjack'
            player_flag = False
            sleep(.25)
            system('clear')
        elif player_total == 21:
            player_result = '21'
            player_flag = False
            sleep(.25)
            system('clear')

        # player first round action
        if player_total < 21 and len(player_hand) == 2:
            player_choice = input(f'Would you like to Hit, Stand, Split, or Double Down?: ').lower()
            sleep(.25)
            system('clear')
            if player_choice not in ['split', 'double', 'stand', 'hit']:
                print('Please type one of the accepted words.')
            #player split first round
            elif player_choice == 'split':
                player_wallet -= 5
                split_flag_1 = True
                split_flag_2 = True
                draw_1 = draw()
                draw_2 = draw()
                player_split_1.append(player_hand[0])
                player_split_2.append(player_hand[1])
                player_split_1.append(draw_1[0])
                player_split_2.append(draw_2[0])
                split_total_1 += player_card_1[1] + draw_1[1]
                split_total_2 += player_card_2[1] + draw_2[1]
                del player_hand[0]
                del player_hand[0]
                sleep(.25)
                system('clear')
                print(f'\nThe dealer is showing:\n'
                      f'{dealer_hand[0]}')
                hand = player_split_1
                print('\nSplit 1 hand:')
                hand = player_split_1
                print_cards(hand)
                print('\n')
                hand = player_split_2
                print('Split 2 hand:')
                print_cards(hand)
                print(f'\n\nYour current balance is:\n'
                      f'${player_wallet}\n')
            # double down first round
            elif player_choice == 'double':
                sleep(.25)
                system('clear')
                player_wallet -= 5
                card_draw = draw()
                player_hand.append(card_draw[0])
                player_total += card_draw[1]
                if player_total > 21:
                    player_result = 'bust'
                else:
                    player_result = 'double'
                player_flag = False
                sleep(.25)
                system('clear')
            # player stand first round
            elif player_choice == 'stand':
                player_result = 'stnd'
                player_flag = False
                sleep(.25)
                system('clear')
            #player hit first round
            elif player_choice == 'hit':
                card_draw = draw()
                player_hand.append(card_draw[0])
                player_total += card_draw[1]
                print(f'\nThe dealer is showing:\n'
                      f'{dealer_hand[0]}')
                print('\n')
                print('Your hand:')
                hand = player_hand
                print_cards(hand)
                print('\n\n')
        # split 1 action prompt
        elif split_total_1 < 21 and split_flag_1 == True:
            split_1_choice = input(f'\n\nWould you like to Hit, or Stand on split 1?: ').lower()
            sleep(.25)
            system('clear')

            if split_1_choice not in ['hit', 'stand']:
                print('Please type one of the accepted words.')
            # split 1 stand
            elif split_1_choice == 'stand':
                split_1_result = 'stand'
                split_flag_1 = False
                sleep(.25)
                system('clear')
                print(f'\nThe dealer is showing:\n'
                      f'{dealer_hand[0]}')
                hand = player_split_1
                print('\nSplit 1 hand:')
                hand = player_split_1
                print_cards(hand)
                print('\n')
                hand = player_split_2
                print('Split 2 hand:')
                print_cards(hand)
            # split 1 hit
            elif split_1_choice == 'hit':
                sleep(.25)
                system('clear')
                card_draw = draw()
                player_split_1.append(card_draw[0])
                split_total_1 += card_draw[1]
                print(f'\nThe dealer is showing:\n'
                      f'{dealer_hand[0]}')
                hand = player_split_1
                print('\nSplit 1 hand:')
                hand = player_split_1
                print_cards(hand)
                print('\n')
                hand = player_split_2
                print('Split 2 hand:')
                print_cards(hand)
        # split 2 action prompt
        elif split_total_2 < 21 and split_flag_2 == True:
            split_2_choice = input(f'\n\nWould you like to Hit, or Stand on split 2?: ').lower()
            sleep(.25)
            system('clear')

            if split_2_choice not in ['hit', 'stand']:
                print('Please type one of the accepted words.')
            # split 2 stand
            elif split_2_choice == 'stand':
                split_2_result = 'stand'
                split_flag_2 = False
                player_flag = False
                sleep(.25)
                system('clear')
            # split 2 hit
            elif split_2_choice == 'hit':
                sleep(.25)
                system('clear')
                card_draw = draw()
                player_split_2.append(card_draw[0])
                split_total_2 += card_draw[1]
                print(f'\nThe dealer is showing:\n'
                      f'{dealer_hand[0]}')
                hand = player_split_1
                print('\nSplit 1 hand:')
                hand = player_split_1
                print_cards(hand)
                print('\n')
                hand = player_split_2
                print('Split 2 hand:')
                print_cards(hand)
        # player action prompt after first round
        elif player_total < 21 and len(player_hand) >= 3:
            player_choice = input(f'Would you like to Hit, or Stand?: ').lower()
            sleep(.25)
            system('clear')

            if player_choice not in ['hit', 'stand']:
                print('Please type one of the accepted words.')
            # player stand
            elif player_choice == 'stand':
                player_result = 'stand'
                player_flag = False
                sleep(.25)
                system('clear')
            # player hit
            elif player_choice == 'hit':
                sleep(.25)
                system('clear')
                card_draw = draw()
                player_hand.append(card_draw[0])
                player_total += card_draw[1]
                hand = player_hand
                print('\nYour hand:\n')
                print_cards(hand)
                print('\n\n')

        # if split 1 is over 21, check for aces, if found reduce total by 10 so ace equals 1
        elif split_total_1 > 21 and split_flag_1 == True:
            for card in player_split_1:
                if card == 'A':
                    # changes list item A to x so the same ace doesn't get flagged more than once
                    player_split_1[player_split_1.index(card)] = 'x'
                    split_total_1 -= 10
                    break
            if split_total_1 > 21:
                split_1_result = "bust"
                split_flag_1 = False
                sleep(.25)
                system('clear')



        # # if split 2 is over 21, check for aces, if found reduce total by 10 so ace equals 1
        elif split_total_2 > 21 and split_flag_2 == True:
            for card in player_split_2:
                if card == 'A':
                    # # changes list item A to x so the same ace doesn't get flagged more than once
                    player_split_2[player_split_2.index(card)] = 'x'
                    split_total_2 -= 10
                    break
            if split_total_2 > 21:
                split_2_result = "bust"
                split_flag_2 = False
                player_flag = False
                sleep(.25)
                system('clear')

        # # if player hand is over 21, check for aces, if found reduce total by 10 so ace equals 1
        elif player_total > 21 and split_flag_1 == False:
            for card in player_hand:
                if card == 'A':
                    # # changes list item A to x so the same ace doesn't get flagged more than once
                    player_hand[player_hand.index(card)] = 'x'
                    player_total -= 10
                    break
            if player_total > 21:
                player_result = "bust"
                player_flag = False
                sleep(.25)
                system('clear')

    # returns removed aces to hands
    for ace in player_hand:
        if ace == 'x':
            player_hand[player_hand.index(ace)] = 'A'
    for ace in dealer_hand:
        if ace == 'x':
            dealer_hand[dealer_hand.index(ace)] = 'A'
    for ace in player_split_1:
        if ace == 'x':
            player_split_1[player_split_1.index(ace)] = 'A'
    for ace in player_split_2:
        if ace == 'x':
            player_split_2[player_split_2.index(ace)] = 'A'

    # prints dealer hand
    print('Dealer hand:')
    hand = dealer_hand
    print_cards(hand)

    # compares cards if there was a split
    if split_total_1 > 0:
        print('\nSplit 1 hand:')
        hand = player_split_1
        print_cards(hand)
        if split_1_result == 'blackjack':
            player_wallet += 15
            print('\n\nBlackJack!')
        elif split_1_result == 'bust':
            print('\nYou busted, better luck next time.\n')
        elif dealer_result == 'bust':
            print('\nThe dealer busted.\n\n'
                  'Congratulations! You won $5!')
            player_wallet += 5
        elif split_total_1 == dealer_total:
            player_wallet += 5
            print('Draw. You initial bet back.')
        elif split_total_1 > dealer_total:
            player_wallet += 10
            print(f'Congratulations! You won $5!')
        else:
            print('\nYou lost, better luck netxt time.\n')

        print('\nSplit 2 hand:')
        hand = player_split_2
        print_cards(hand)

        if split_2_result == 'blackjack':
            player_wallet += 15
            print('\n\nBlackJack!')
        elif split_2_result == 'bust':
            print('\nYou busted, better luck next time.\n')
        elif dealer_result == 'bust':
            print(dealer_total)
            print('\nThe dealer busted.\n\n'
                  'Congratulations! You won $5!')
            player_wallet += 10
        elif split_total_1 == dealer_total:
            player_wallet += 5
            print('Draw. You initial bet back.')
        elif split_total_2 > dealer_total:
            player_wallet += 10
            print(f'Congratulations! You won $5!')
        else:
            print('\nYou lost, better luck netxt time.\n')

    # compares player hand if no split
    else:
        print('\nYour hand:')
        hand = player_hand
        print_cards(hand)
        if player_result == 'blackjack':
            player_wallet += 15
            print('\n\nBlackJack!')
        elif player_result == 'bust':
            print('\n\nYou busted, better luck next time.\n')
        elif dealer_result == 'bust':
            if player_result == 'double':
                print('\nThe dealer busted.\n\n'
                      'Congratulations! You won $10!')
                player_wallet += 20
            else:
                print('\nThe dealer busted.\n\n'
                      'Congratulations! You won $5!')
                player_wallet += 10
        elif player_result == 'double' and player_total > dealer_total:
            print('\n\nCongratulations! You won $10\n')
            player_wallet += 20
        elif split_total_1 == dealer_total:
            if player_result == 'double':
                player_wallet += 10
                print('Draw. You initial bet back.')
            else:
                player_wallet += 5
                print('Draw. You initial bet back.')
        elif player_total > dealer_total:
            player_wallet += 10
            print(f'\n\nCongratulations! You won $5!')
        else:
            print('\n\nYou lost, better luck netxt time.\n')

    # print balance
    print(f'Your balance is:\n'
          f'${player_wallet}')

    # prompt player to play again
    play_again = input(f'\nWould you like to keep playing? Type yes or no: ').lower()
    while True:
        if play_again not in ['yes', 'no']:
            print('Please type yes or no.')
            break
        elif play_again == 'yes':
            sleep(.25)
            system('clear')
            blackjack(player_wallet)


blackjack(player_wallet)

library

import random

cards = {
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9,
    '10': 10,
    'J': 10,
    'Q': 10,
    'K': 10,
    'A': 11
}

def draw():
    draw_card = random.choice(list(cards.items()))
    return draw_card

def print_cards(hand):
    for card in range(len(hand)):
        print(hand[card], end=" ")
\$\endgroup\$

1 Answer 1

12
\$\begingroup\$

PEP-8

PEP-8 is the standard style recommendation for Python. In it, it includes things like how many blank lines, how much indentation, recommendations for variable naming and casing.

There are linters (Flake8, Pylint and others) which check your code against PEP-8 and issue useful warnings. In your case there are several simple and obvious things.

This flags things like:

tmp.py:251:50: E712 comparison to True should be 'if cond is True:' or 'if cond:'
tmp.py:340:49: E712 comparison to False should be 'if cond is False:' or 'if not cond:'
tmp.py:391:19: F541 f-string is missing placeholders
tmp.py:31:0: C0103: Constant name "player_wallet" doesn't conform to UPPER_CASE naming style (invalid-name)

Small things, but things which make a big difference to someone approaching your code for the first time, as well as how to fix them.

Style

In Python (and most other languages), we don't tend to use

if split_flag_2 == True
#or
if split_flag_2 == False

We can simply say

if split_flag_2
#or
if not split_flag_2

If we want to be explicit about the type

if split_flag_2 is True

but it's not terribly common that we need to do that.

Library

Let's just take a quick look at your "library" first before we get started since it's smaller.

I won't go fully into classes since you've said you're not using them yet, but you might want to take a quick look at dataclass for storing your cards, it would look like:

from dataclasses import dataclass

@dataclass
class Card:
    face: str
    val: int

cards = (Card('2', 2),
         Card('3', 3),
         ...
         Card('K', 10),
         Card('A', 11))

ACE = cards[-1]
ACE.face # => 'A'
ACE.val # => 11

It's just a neat way of encapsulating these data like a tuple with names (there is also collections.namedtuple), this also means your draw would just become:

def draw():
    return random.choice(cards)

In Python it's usual to just loop over the elements of a list rather than iterating through the range and then the elements.

def print_cards(hand):
    for card in hand:
        print(card, end=" ")

if we need the index, we can do

def print_cards(hand):
    for ind, card in enumerate(hand):
        print(f"{ind}: {card}", end=" ")

Structure

Your problem isn't so much your use of if-elif, but more the fact that you have one huge, monolithic function. As we tackle this, we're going to break this down into smaller functions.

Look at the warnings given by our linter

tmp.py:33:0: R0912: Too many branches (89/12) (too-many-branches)
tmp.py:33:0: R0915: Too many statements (346/50) (too-many-statements)

Phew! That's a lot. Let's take a look at the function and see what it does:

Blackjack:

  • Give free money if bankrupt
  • Dealer AI
  • Dealer's removed aces
  • Display dealer cards
  • Player drawing
    • Get Player input (multiple times)
    • Split
    • Double
    • Stand
    • Hit
    • Check hand bust/validity
  • Player and dealer removed aces
  • Reveal hands
  • Compares hands (winners)
  • Update balance
  • Ask to play again (recurse)
  • Clear screen

That's a lot of responsibilities for one little function. Each of these could (and mostly should) be their own functions.

Main

Let's first of all look at what sorts of things we might want in the core loop. The core loop to me goes like this:

  • Ask to play
  • Take bets (free money?)
  • Play round
  • Update balance
  • Clear screen

That's simple enough to do, we extract those bits from the blackjack function into a new main.

def main():
    player_wallet = 1000

    while True:
        # print balance
        system('clear')
        print(f'Your balance is:\n ${player_wallet}')
        blackjack(player_wallet)

        # prompt player to play again
        while True:
            play_again = input('\nWould you like to keep playing? Type yes or no: ').lower()

            if play_again == 'no':
                return
            if play_again == 'yes':
                break
            print('Please type yes or no.')

        # deposits money if player hits $0
        if player_wallet <= 0:
            print("Looks like you're a little short, have $100")
            player_wallet += 100

Now, we could make this take arguments to initialise the wallet and things, but we'll just leave it here for now.

Another advantage to this is that we can import this code into something else and use it elsewhere. To this end we should also use a "main guard":

if __name__ == "__main__":
    ...

which only runs if the code is called directly as python blackjack.py

Dealer AI

Let's now go back to the top and look at our dealer AI. (After breaking it off into its own function).

dealer_flag seems to be just whether the dealer is over 17. So why not make it that?

The dealer drawing their card is always a sequence of 3 things:

get card
increment total
add card to hand

We can make that a function.

def draw_to_hand(hand, total):
    face, value = draw()
    out_hand = hand + [face]
    out_total = total + value
    return out_hand, out_total

now this doesn't matter if this is our dealer's hand or our player's hand. Convenient! (N.B. when you learn classes, this is probably a method on a Hand). Realistically, I probably wouldn't do it this way, and I would make computing the total a function on hands.

What's the difference between a "21" result and a "stand" result with total 21? I think we can remove the distinction.

If we want to draw 2 cards on turn 0, we can move that outside the loop, dealer_hand will never be 0 outside of this. Likewise, blackjack will never occur outside of round 1, we can move that outside too.

Since we always draw to 17, we can just loop over that and if we go over, but have aces, we go back down under 17 and then draw back to 17.

When we're searching for aces we can check if there is an ace, then use index to find it.

if 'A' in dealer_hand:
    dealer_hand[dealer_hand.index('A')] = 'x'
    dealer_total -= 10

To return the aces, we could use a list comprehension:

dealer_hand = [card if card != 'x' else 'A' for card in dealer_hand]

or

dealer_hand = [card.sub('x','A') for card in dealer_hand]

Throughout all of this, I'm thinking that you might just want hand to be a string (which you can think of as a list of chars if that helps) which has convenient methods for doing a lot of the things you're wanting to do. (find, sub) still has in checking and is iterable.

But there we are, we've refactored our dealer:

def dealer_turn():
    # creates empty list for dealer hand, sets flag for while loop and sets dealer total to 0
    dealer_hand = []
    dealer_total = 0

    # draws cards for dealer and returns result
    dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)
    dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)

    if dealer_total == 21:
        dealer_result = "blackjack"
        return dealer_hand, dealer_total, dealer_result

    while dealer_total < 17:
        dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)

        if dealer_total > 21 and 'A' in dealer_hand:
                dealer_hand[dealer_hand.index('A')] = 'x'
                dealer_total -= 10

    # Could do
    # dealer_result = 'stand' if dealer_total <= 21 else 'bust'

    if dealer_total <= 21:
        dealer_result = 'stand'
    elif dealer_total > 21:
        dealer_result = "bust"

    # returns dealers removed aces
    dealer_hand = [card if card != 'x' else 'A' for card in dealer_hand]

    return dealer_hand, dealer_total, dealer_result

Victory checking

Before I take a look at handling the player's turn, I'm going to zoop down to the bottom and look at comparing our victories. You have special cases for each kind of victory, so we're going to split it off into a function, which compares hands, results and totals.

def check_victory(player, dealer):
    # Remember how earlier we returned a tuple so we can unpack (again, nicer as a namedtuple or dataclass)
    player_hand, player_total, player_result = player
    dealer_hand, dealer_total, dealer_result = dealer
    ...

So this means, that we can pass either both the split hands, or the player's hand to the same function and work out who has won. This also avoids typos (like you have in your victory check)

print('\nSplit 2 hand:')
hand = player_split_2
print_cards(hand)

if split_2_result == 'blackjack':
...
elif split_total_1 == dealer_total: # <---- TOTAL 1

So what are the possible results?

  • Blackjack -> wallet += 2*bet
  • Win w/ Double -> wallet += 2*bet
  • Win -> wallet += bet
  • Push -> wallet += 0
  • Lose -> wallet -= bet
  • Lose w/ Double -> wallet -= 2*bet

N.B. This is the only place where the wallet really changes, we don't have to keep track of it beyond here we can just add or subtract here.

The check_victory function might return the multiplier on your bet (-2, -1, 0, 1, 2) and increment the wallet based on that.


EDIT: Couple of notes inspired by ggorlen in the comments

Enum aside

Here, I suggested using: (-2, -1, 0, 1, 2) as scaling factors. Strictly this is not the best coding practice and would be called "magic numbers". Realistically, what we'd want here is an "enumeration" which is a consistent way of grouping constant values to names, but again these are classes and I'm trying to avoid those. The same goes for <x>_result and anywhere where you have an anonymous string or int whose value is one of a set and needs to be checked elsewhere.

The advantage of enums is that they check for typos (asking for an undefined property is an error, checking against a misspelled string is fine), can be renamed/renumbered (and updated everywhere across the code) and they are self-documenting as coherent groups and support full docstrings.

Side effects

For better code in general we want to avoid things like printing or using global (external to the function) variables unless we have to, we call these things "side-effect"s, and a function which avoids side-effects "pure". Side effects include:

  • Modifying files, or printing to screen
  • Affecting or relying on variables from outside your function
    • Using global
    • Calling things like system time
    • Using random numbers (sequences of random numbers rely on a consistent state to generate the next one)

The reasons for wanting pure functions are manifold:

  • The function clutters the screen and you can't disable the print without modifying the internals.
  • I have my own variable with the same name as the global one which alters how the function behaves unpredictably
  • They only depend on the arguments which go in, so they can be run wherever whenever and give the same answer for the same inputs which makes testing/debugging easier.
  • (Advanced) They can be vectorised/parallelised trivially

In the example below, I have left the prints in the check_victory function, but they would probably be better handled by a separate print_victory function. See what you can do!


So we can use your existing victory checks but rather than copy-pasting, we can call the function.

This means that your entire check_victory block becomes:

    for rnd, res in enumerate(player):
        print(f'\nSplit {rnd} hand:')
        print_cards(res[0])
        check_victory(res, dealer)

I advise you to make the player's hand/total/result a list/tuple so you can iterate over them and avoid repeating yourself.

Player Turn

I'm going to leave the refactoring of player_turn as a task for you using what I've shown here. Use functions, break things down, avoid repeating yourself.

A structure might look like:

def player_turn(init_cards=None):
    if init_cards is None:
        player_hand, player_total = draw_to_hand(...)
        player_hand, player_total = draw_to_hand(...)
    ...
    if 'split':
        return (player_turn(player_hand[0]), player_turn(player_hand[1]))

    ...
     return ((player_hand_a, player_total_a, player_result_a),)

Summary

Overall, your problem isn't using if-elif but not breaking down your functions to do ONE job well (this is called the Single Responsibility Principle or SRP) and don't repeat yourself (or DRY).

from os import system

def draw_to_hand(hand, total):
    face, value = draw()
    out_hand = hand + [face]
    out_total = total + value
    return out_hand, out_total


def dealer_turn():
    # creates empty list for dealer hand, sets flag for while loop and sets dealer total to 0
    dealer_hand = []
    dealer_total = 0

    # draws cards for dealer and returns result
    dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)
    dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)

    if dealer_total == 21:
        dealer_result = "blackjack"
        return

    while dealer_total < 17:
        dealer_hand, dealer_total = draw_to_hand(dealer_hand, dealer_total)

        if dealer_total > 21 and 'A' in dealer_hand:
                dealer_hand[dealer_hand.index('A')] = 'x'
                dealer_total -= 10

    if 17 <= dealer_total <= 21:
        dealer_result = 'stand'
    elif dealer_total > 21:
        dealer_result = "bust"

    # returns dealers removed aces
    dealer_hand = [card if card != 'x' else 'A' for card in dealer_hand]

    return dealer_hand, dealer_total, dealer_result


def check_victory(player, dealer):
    player_hand, player_total, player_result = player
    dealer_hand, dealer_total, dealer_result = dealer

    if player_result == 'blackjack':
        print('\n\nBlackJack!')
        return 2
    elif player_result == 'bust':
        print('\nYou busted, better luck next time.\n')
        return -1
    elif dealer_result == 'bust':
        print(dealer_total)
        print('\nThe dealer busted.\n\n'
              'Congratulations! You won $5!')
        return 1 if player_result != 'double' else 2
    elif player_total == dealer_total:
        print('Draw. You initial bet back.')
        return 0
    elif player_total > dealer_total:
        print('Congratulations! You won $5!')
        return 1 if player_result != 'double' else 2
    else:
        print('\nYou lost, better luck netxt time.\n')
        return -1


def player_turn():
    ...
    if 'split':
        ...
        return ((player_hand_a, player_total_a, player_result_a),
                (player_hand_b, player_total_b, player_result_b))
    else:
        ...
        return ((player_hand_a, player_total_a, player_result_a),)

def blackjack(player_wallet):

    dealer = dealer_turn()

    # Would be nicer as a namedtuple, dataclass or class
    print(f'\nThe dealer is showing:\n'
          f'{dealer[0][0]}')

    player = player_turn()

    # prints dealer hand
    print('Dealer hand:')
    print_cards(dealer[0])

    for rnd, res in enumerate(player):
        print(f'\nSplit {rnd} hand:')
        print_cards(res[0])
        check_victory(res, dealer)


def main():
    player_wallet = 1000

    while True:
        # print balance
        print(f'Your balance is:\n ${player_wallet}')
        blackjack(player_wallet)

        # prompt player to play again
        while True:
            play_again = input('\nWould you like to keep playing? Type yes or no: ').lower()

            if play_again == 'no':
                return
            if play_again == 'yes':
                break
            print('Please type yes or no.')

        system('clear')
        # deposits money if player hits $0
        if player_wallet <= 0:
            print("Looks like you're a little short, have $100")
            player_wallet += 100

if __name__ == "__main__":
    main()

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Good review, but you may consider separating presentation and logic a bit more. check_win not only checks a win, it also produces print() side effects. If the BJ game engine could easily be plugged into a PyQT UI rather than a command line UI, the system would be loosely coupled. Having players representated as a 3-tuple that's passed around and packed/unpacked seems a bit strange. Maybe a class would be better to better encapsulate this data? Final nitpick: using strings like "blackjack" rather than an enumeration of possible states. \$\endgroup\$
    – ggorlen
    Apr 16, 2023 at 0:12
  • \$\begingroup\$ Agree with you on the enumeration, but I'm avoiding using classes per their request. Named constants would be better, but I was just following what they did and not wanting to rewrite the whole thing more, and with no classes it works out to be much the same. I agree and will move the prints out of check_victory, again, copy-paste of their logic just to demonstrate breaking it down. \$\endgroup\$ Apr 16, 2023 at 12:37
  • 3
    \$\begingroup\$ There's a bit about the possibilities of dataclass, namedtuple mentioned in passing (and now enum), so hopefully that's enough to trigger some deeper study if they have questions. \$\endgroup\$ Apr 16, 2023 at 16:50
  • \$\begingroup\$ Missed that too, great. \$\endgroup\$
    – ggorlen
    Apr 16, 2023 at 18:23

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.