I'm stuck in recursive function

Hello, I’m new in Python. I follow a python bootcamp course in Udemy, and one of the project is a blackjack game. I found the code is simple, but feels a bit too simple. Instead showing the card, it just showing a card value. I made some tweaks here and there, using recursion, aand it’s break a bit.

I’ve documented some of the problems I got in comments section in # !— —! pattern. Maybe it’s because I’m a newbie, yet trying to chew things I can’t swallowed.
Please shed some lights to me.

import random

# ---- INITIALIZATION ----
# Cards

the_card = {
    "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,
}

# Initial account

initial = int(input("How many dollars you bring to the table?  $"))

# ---- Functions ----
# ---- Betting function ----

def bet(acc):
    ''' Betting function '''
    while True:
        your_bet = int(input("How many you willing to bet?  $"))
        if your_bet > acc:
            print("Your bet is higher than your account. Please repeat all.")
            continue
        else:            
            return your_bet
# ---- Draw cards ----
def dealing(cards):
    ''' Dealing card function. Return card lists and their value. '''
    print("Game start.")
    your_card = [random.choice(list(cards.keys())) for i in range(0, 2)]
    your_val =  sum(cards[i] for i in your_card)
    dealer_card = [random.choice(list(cards.keys())) for i in range(0, 2)]
    dealer_val = sum(cards[i] for i in dealer_card)
    return your_card, your_val, dealer_card, dealer_val
# ---- Adding card ----
def add_card(hand, cards):
    '''Draw another card '''    
    new_card = random.choice(list(cards.keys()))
    hand.append(new_card)
    new_val = sum(cards[i] for i in hand)
    return hand, new_val
# ---- Compare hands ----
# !--- open_hand() returns None ---!
def open_hand(hand, hand_val, dealer_hand, dealer_val, acc, bet, cards):
    ''' Checking both hands' cards value. Return user money amount.'''
    if dealer_val <= 17:
        # Triggered when the dealer's hand is under 17.
        print("Dealer's hand value is under 17. Dealer will draw another card.")
        new_dealer_hand, new_dealer_val = add_card(dealer_hand, cards)
        open_hand(hand, hand_val, new_dealer_hand, new_dealer_val, acc, bet, cards)
    elif hand_val > 21 and dealer_val > 21:
        # Will be triggered when your hand is more than 21.
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print("Both hands are over 21. It's draw.")
        return acc
    elif hand_val > 21 and dealer_val < 21:
        # Will be triggered if user hand is over 21 and dealer's hand is under 21.
        acc -= bet
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print(f"You lose. Your money now is ${acc}.")
        return acc
    elif hand_val < 21 and dealer_val > 21:
        # Will be triggered if
        acc += bet
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print(f"You win! Your money now is ${acc}.")
        return acc
    elif hand_val < 21 and dealer_val < 21:
        # Triggered when both hands are under 21.
        # Whoever have higher hand, wins.
        if hand_val < dealer_val:
            acc -= bet
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print(f"You lose. Your money now is ${acc}.")
            return acc
        elif hand_val > dealer_val:
            acc += bet
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print(f"You win! Your money now is ${acc}.")
            return acc
        elif hand_val == dealer_val:
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print("Both hands are same. It's draw.")
            return acc
# --- Control if user will stop or continue.
def if_continue(money, cards):
    still_play = input("Do you want to play again? Press y to continue, or any other keys if you want to stop:  > ").lower()
    if still_play == "y":
        game_21(money, cards, status=True)
    else:
        # !--- After end_game() excecuted, the program is not terminated ---!
        # !--- It's back to game_21() ---!
        end_game(money)
# ---- End game ----
def end_game(money):
    return f"Game over, your money is ${money}."
# ---- The body of program ----
# ---- Combining all other functions ----
# ---- Indirect recursion to game_21() from if_continue() ----
def game_21(money, cards, status):
    ''' Core game program.'''
    put_bet = bet(money)
    print("Let's begin the game.")
    hand, hand_val, dealer_hand, dealer_val = dealing(cards)
    print(f"Your hand is {hand} with value {hand_val}.\nDealer's first card is {dealer_hand[0]} with value {cards[dealer_hand[0]]}.")
    # Need one more recursive here.
    # ! --- The loop become endless ---!
    while status:
        if hand_val < 21:
            draw_more = input("Do you want to draw again? Press y to continue, or any other keys if you want to stand:  > ").lower()
            if draw_more =="y":
                hand, hand_val = add_card(hand, cards)
                print(f"Your hand is {hand} with value {hand_val}.\nDealer's first card is {dealer_hand[0]} with value {cards[dealer_hand[0]]}.")
                continue
            else:
                # !--- PROBLEM: open_hand() return None ---!
                money = open_hand(hand, hand_val, dealer_hand, dealer_val, money, put_bet, cards)
                if_continue(money, cards)
        elif hand_val == 21:
            money += put_bet
            print("Blackjack!")
            print(f"Your money now is ${money}.")
            if_continue(money, cards)
        else:
            money -= put_bet
            print(f"Your hand value is over than 21. You lose ${put_bet}. Your money now is ${money}.")
            if_continue(money, cards)
            status = False
          
# ---- PROGRAM STARTS HERE ----

game_21(initial, the_card, status = True)
 
# !--- Error raised ---!
# !--- TypeError: '>' not supported between instances of 'int' and 'NoneType' ---!
# !--- Help T_T ---!

Thank you for your help, folks :smiley:

Sorry if the code looks too cluttered.

Hey @yafethtb , it was a bit difficult to read your code, but simply adding two blank lines between each function (per PEP 8, the standard for Python code style) makes it much easier to read. Also, just FYI, per the docstring standard (PEP 257), docstrings should always use triple double quotes ("""), rather than triple single quotes ('''), and no spaces around them.

With those trivialities out of the way, onward to your issues!

First, in Python, if a function body finishes executing without an explicit return statement, the function returns None. If we look at all of the branches of open_hand, we can see that all of them return acc, which presumably should be a number…all but one of them, that is, the very first branch; its last statement calls open_hand() again which might return acc, but once execution returns to the top-level open_hand function, that function implicitly returns None, since the first branch doesn’t have a return statement. To fix this, just have it return the return value of open_hand, i.e. return open_hand(...). If you prefer, for similarity with the others, you can also do:

acc = open_hand(...)
return acc

This fixes open_hand returning None and the consequent TypeError (since acc in the parent function is set to None, which you of course cannot add numeric values to).

However, we still have a problem, as you noted: if we type y to play another game, everything works, but if we don’t, the original game continues on, with the dealer continuing to draw cards and you continuing to win, regardless of what you type. Again, we trace the code to see what is going on. We can see that after open_hand() is called by game_21, if_continue is then called. The user is asked whether they still want to play; if they enter y, game_21() is called again…okay, that works, I guess. But if they don’t, end_game is called, and here our troubles begin.

The end_game function returns a string that looks like it was meant to be printed instead. Then, back inside of if_continue, that string, the return value of end_game, is neither assigned to anything or returned, so it has no effect. Since there is no return statement in that branch, if_continue returns None. We’re now back to where if_continue was called…but its value itself gets thrown away, and status is not modified. Since status never gets set to False within this branch, the original game_21 loop continues indefinitely, unless you choose to hit (draw) and your hand busts (i.e. its over 21), in which case that codepath has a status = False, which breaks the loop.

I’m guessing that you meant to make that last status = False at the level directly under the while loop, rather than at the level of the else branch, so it runs whenever your hand isn’t under 21 and you don’t choose to hit, and terminates the game. That’s a common mistake, even sometimes among experienced programmers. So, to fix this problem, you’d move status = False up one indentation level, and now everything works! One final tweak: as implied before, you probably want to make end_game print the message rather than return it (i.e. print(f"Game over, your money is ${money}.")), so the user actually sees it.

To note, the recursive design is really not the best for this, as if you play a large number of games, functions keep getting added to the stack and it eventually goes too many levels deep, resulting in a RecursionError. A better design, if you were to implement this for real, would be to use an outer while loop handling the if_continue and calling the game_21 function.

One thing that was helpful in my testing calling random.seed(SEED) first thing. Setting SEED = None produces a random game, while setting SEED = 42 (or any integer) produces deterministic behavior, the same every time…very useful for testing, and you can vary the seed to try out different behavior.

By the way, you might not be at an advanced enough point in your development, but this might be a good idea to use a class to encapsulate the state of the game, instead of passing all the different variables around, which is more complex, harder to follow, more work and a greater chance of bugs.

One final note…technically speaking, only a ten and an ace on the first hand is a blackjack, and true blackjacks pay out at 3:2 instead of 1:1 of the bet. Otherwise, a 21 is just a 21, and is treated the same as any other card value. As a small challenge, how might you handle that in the code?

Cheers, and may the odds ever be in your favor!

By the way, in case you’re stuck or the above is unclear, here’s a copy of your code with just the above tweaks applied. I also took the liberty of one small additional modification: instead of returning acc in every code path of open_hand, I instead just assign the result of open_hand to acc in the first and return acc at the very end. This avoids a lot of duplicated code and the resulting bug potential.

import random

# Set SEED to None for a random game, or to an integer for a deterministic one
SEED = None

random.seed(SEED)

# ---- INITIALIZATION ----
# Cards

the_card = {
    "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,
}

# Initial account

initial = int(input("How many dollars you bring to the table?  $"))

# ---- Functions ----
# ---- Betting function ----

def bet(acc):
    ''' Betting function '''
    while True:
        your_bet = int(input("How many you willing to bet?  $"))
        if your_bet > acc:
            print("Your bet is higher than your account. Please repeat all.")
            continue
        else:
            return your_bet


# ---- Draw cards ----
def dealing(cards):
    ''' Dealing card function. Return card lists and their value. '''
    print("Game start.")
    your_card = [random.choice(list(cards.keys())) for i in range(0, 2)]
    your_val =  sum(cards[i] for i in your_card)
    dealer_card = [random.choice(list(cards.keys())) for i in range(0, 2)]
    dealer_val = sum(cards[i] for i in dealer_card)
    return your_card, your_val, dealer_card, dealer_val


# ---- Adding card ----
def add_card(hand, cards):
    '''Draw another card '''
    new_card = random.choice(list(cards.keys()))
    hand.append(new_card)
    new_val = sum(cards[i] for i in hand)
    return hand, new_val


# ---- Compare hands ----
def open_hand(hand, hand_val, dealer_hand, dealer_val, acc, bet, cards):
    ''' Checking both hands' cards value. Return user money amount.'''
    if dealer_val <= 17:
        # Triggered when the dealer's hand is under 17.
        print("Dealer's hand value is under 17. Dealer will draw another card.")
        new_dealer_hand, new_dealer_val = add_card(dealer_hand, cards)
        acc = open_hand(hand, hand_val, new_dealer_hand, new_dealer_val, acc, bet, cards)
    elif hand_val > 21 and dealer_val > 21:
        # Will be triggered when your hand is more than 21.
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print("Both hands are over 21. It's draw.")
    elif hand_val > 21 and dealer_val < 21:
        # Will be triggered if user hand is over 21 and dealer's hand is under 21.
        acc -= bet
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print(f"You lose. Your money now is ${acc}.")
    elif hand_val < 21 and dealer_val > 21:
        # Will be triggered if
        acc += bet
        print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
        print(f"You win! Your money now is ${acc}.")
    elif hand_val < 21 and dealer_val < 21:
        # Triggered when both hands are under 21.
        # Whoever have higher hand, wins.
        if hand_val < dealer_val:
            acc -= bet
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print(f"You lose. Your money now is ${acc}.")
        elif hand_val > dealer_val:
            acc += bet
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print(f"You win! Your money now is ${acc}.")
        elif hand_val == dealer_val:
            print(f'Your hand is {hand} with value {hand_val}.\nDealer cards are{dealer_hand} with value {dealer_val}.')
            print("Both hands are same. It's draw.")

    return acc


# --- Control if user will stop or continue.
def if_continue(money, cards):
    still_play = input("Do you want to play again? Press y to continue, or any other keys if you want to stop:  > ").lower()
    if still_play == "y":
        game_21(money, cards, status=True)
    else:
        end_game(money)


# ---- End game ----
def end_game(money):
    print(f"Game over, your money is ${money}.")


# ---- The body of program ----
# ---- Combining all other functions ----

# ---- Indirect recursion to game_21() from if_continue() ----
def game_21(money, cards, status):
    ''' Core game program.'''
    put_bet = bet(money)
    print("Let's begin the game.")
    hand, hand_val, dealer_hand, dealer_val = dealing(cards)
    print(f"Your hand is {hand} with value {hand_val}.\nDealer's first card is {dealer_hand[0]} with value {cards[dealer_hand[0]]}.")
    # Need one more recursive here.
    while status:
        if hand_val < 21:
            draw_more = input("Do you want to draw again? Press y to continue, or any other keys if you want to stand:  > ").lower()
            if draw_more =="y":
                hand, hand_val = add_card(hand, cards)
                print(f"Your hand is {hand} with value {hand_val}.\nDealer's first card is {dealer_hand[0]} with value {cards[dealer_hand[0]]}.")
                continue
            else:
                money = open_hand(hand, hand_val, dealer_hand, dealer_val, money, put_bet, cards)
                if_continue(money, cards)
        elif hand_val == 21:
            money += put_bet
            print("Blackjack!")
            print(f"Your money now is ${money}.")
            if_continue(money, cards)
        else:
            money -= put_bet
            print(f"Your hand value is over than 21. You lose ${put_bet}. Your money now is ${money}.")
            if_continue(money, cards)

        status = False

# ---- PROGRAM STARTS HERE ----

game_21(initial, the_card, status = True)