3
\$\begingroup\$

I just finished making a Blackjack program, and I hate my global variables in the program. Is there any way to change the code to not use globals and still work?

#!/usr/bin/env python3
import skilstak.colors as c
import random
import time
def bet():
    global tokens
    bet.cBet = input("How many tokens would you like to bet?"+c.green+" All "+c.b01+ "to go all in > ").lower()
    if bet.cBet == "all":
        bet.cBet = int(tokens)
        if tokens == 0:
            print("You lost. You're all out of tokens!")
            exit()
    elif bet.cBet == str:
        exit()
    else:
        bet.cBet = int(bet.cBet)
        if bet.cBet > tokens:
            if tokens == 0:
                print("You lost. You're all out of tokens!")
                exit()
            else:
                print("You can't bet what you don't have...")
                print("Your bet was changed to all your credits, or all in.")
                bet.cBet = tokens
                time.sleep(2)
    return bet.cBet, tokens
def computerPlay():
    global mode 
    cscore = sum(chand)
    score = sum(hand)
    if mode == True:
        while cscore < 17:
            chand.append(deck[position])
            del deck[position]
            cscore = sum(chand)
            if cscore > score:
                break
        else:
            if 11 in chand:
                chand.remove(11)
                chand.append(1)
                cscore = sum(chand)
                if cscore > 21:
                    pass
                else:
                    computerPlay()
    else:
        while cscore < 17:
            chand.append(deck[position])
            del deck[position]
            cscore = sum(chand)
        else:
            if 11 in chand:
                chand.remove(11)
                chand.append(1)
                cscore = sum(chand)
                if cscore > 21:
                    pass
                else:
                    computerPlay()
def winOrLose():
    global cwins
    global wins
    global tokens
    cscore = sum(chand)
    score = sum(hand)

    if cscore > 21:
        '''determines if computer orplayer is busted'''
        cbust = "y"
        if score > 21:
            bust = "y"
        else:
            bust = "n"
    else:
        cbust = "n"
        if score > 21:
            bust = "y"
        else:
            bust = "n"
    print(c.cl + "The computer's hand was" + str(chand))
    print("Your hand was" + str(hand))

    if cbust == "y":
        '''determines if computer/player won, adds/subtracts tokens, add/subtract point to score'''
        if bust == "y":
            print(c.yellow + "It's a tie!" + c.base01)
            print("P",wins,": C",cwins)
        else:
            print(c.green + "You won!" + c.b01)
            wins += 1
            print("P",wins,": C",cwins)
            tokens += bet.cBet
            print("You now have",tokens," tokens")
    elif bust == "y":
        if cbust == "y":
            print(c.yellow + "It's a tie!" + c.b01)
            print("P",wins,": C",cwins)
        else:
            print(c.red + "You lost."+ c.b01)
            cwins += 1
            print("P",wins,": C",cwins)
            tokens -= bet.cBet
            print("You now have",tokens," tokens")
    else:
        if cscore > score:
            print(c.red + "You lost."+ c.b01)
            cwins += 1
            print("P",wins,": C",cwins)
            tokens -= bet.cBet
            print("You now have",tokens," tokens")
        elif cscore < score:
            print(c.green + "You won!" + c.b01)
            wins += 1
            print("P",wins,": C",cwins)
            tokens += bet.cBet
            print("You now have",tokens," tokens")
        else:
            print(c.yellow + "It's a tie!" + c.b01)
            print("P",wins,": C",cwins)
def playAgain():
    again = input("Would you like to play again?(y/n) > ")
    if again == "y":
        print(c.cl)
    else:
        exit()
def newcard():
    score = sum(hand)
    while score <= 21:
        userCard = input(c.b01 + "Would you like a new card?(y/n) > ").lower()
        if userCard == "y":
            hand.append(deck[position])
            score = sum(hand)
            del deck[position]
            print(c.cl + "Your hand contains " + str(hand) + " for a total of",score,"points.")

        else:
            break
    else:
        if 11 in hand:
            '''changes 11 in hand to 1'''
            hand.remove(11)
            print(c.cl + "Your ace valued 11 was changed into a 1")
            hand.append(1)
            score = sum(hand)
            print("You now have"+str(hand)+"and",score,"Points")
            if score < 21:
                '''go to  beginning or has computer play depending'''
                newcard()
        else:
            pass
def shuffleAndStart():
    print(c.cl)
    random.shuffle(deck)
    hand.append(deck[position]) 
    del deck[position]
    hand.append(deck[position]) 
    del deck[position]
    score = sum(hand)
    '''shuffles the deck, deals the player 1 card''' 
    print("Your hand contains: "+ str(hand) + " for a total of", score,"points.")
if __name__ == '__main__':
    try:
        print(c.cl + c.b01 + "Welcome to Blackjack V.Alpha.1.2! Created by Peter")
        print("You have 500 tokens.")
        tokens = 500
        hardDifficulty = input("Would you like an" + c.red + " intelligent computer"+c.b01+ "(y/n) > ").lower()
        if hardDifficulty == "y":
            mode = True
        else:
            mode = False
        deck = [2,3,4,5,6,7,8,9,10,10,10,10,11] * 4
        hand = []
        chand = []
        cwins = 0
        wins = 0
        position = 0
        #Which card to pull out of deck
        while True:
            bet()
            shuffleAndStart()
            newcard()
            computerPlay()
            winOrLose()
            playAgain()
            del hand[:]
            del chand[:]
            deck = [2,3,4,5,6,7,8,9,10,10,10,10,11] * 4
    except KeyboardInterrupt:
        print(c.cl + "Thanks for playing!\n" + c.x)
        exit()
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! If you're at all unclear how global works I actually just wrote a post about it here. I'll try get you a review today, but the short answer is that making your code more object oriented will cut down on globals. \$\endgroup\$ – SuperBiasedMan Jan 18 '16 at 10:47
  • \$\begingroup\$ Thanks! I haven't even heard of classes until now. \$\endgroup\$ – retep Jan 18 '16 at 14:49
  • \$\begingroup\$ Actually after reading your code more closely, I think classes aren't strictly necessary. I instead tried to explain more about how functions' scope works. Looking up more on classes would be a good idea though, as they're very helpful for managing scope and organising code. \$\endgroup\$ – SuperBiasedMan Jan 18 '16 at 16:01
2
\$\begingroup\$

The main thing you need to do is refactor and adjust your scoping. You have some quite large functions and reducing them down will make your code less unwieldy. Let's take bet for example. In here, you have:

  • Requesting user input for how much to bet
  • Testing if the user is out of tokens
  • Validating that the user has enough tokens to match their bet
  • Validating that the user entered an integer

This should be rearranged into three functions, where each does one job (though you could simultaneously validate the bet as an int and having enough tokens for it). Here's how I'd rework them:

def get_bet(tokens)
    while True:
        user_bet = input("How many tokens would you like to bet?" + c.green
                         + " All " + c.b01 + "to go all in > ").lower()

        if user_bet == "all":
            return tokens  # betting all tokens
        try:
            bet = int(user_bet)
        except ValueError:
            print("Please enter either 'all' or an integer.")
            continue

        if bet <= tokens:
            return bet
        else:
            print("You don't have that much, you only have: " + str(tokens))

First, using while True means that the user will keep being asked for values until the give an integer that's equal to or below the amount they have. The loop will continue forever until it hits the return bet line, when it exits with the value of their bet.

The try except ValueError lines are to catch if the user enters something like "Banana", which int can't parse and would throw an error. The except part means you prevent the error, instead printing a helpful warning about the user's mistake and then you continue on to the next iteration of the loop, asking for their bet again.

You might have been trying to test this with elif bet.cBet == str, but that doesn't work. If you wanted to test for the bet being a string type, you'd need to use isinstance(bet.cBet, str). If you were testing for an empty string, you can do that with just if bet.cBet:. Empty strings evaluate as False in Python, while strings with characters will be True. Either way, your test wouldn't catch errors from people using letters instead of number characters. In Python, the best way to test is to just try do what you need and catch if an error comes from it.

While we're on this, you used bet.cBet here, but it's totally unnecessary! You're assigning an attribute to a function. This is something you can do in Python but is rarely used like this. You can just use any name, like cBet and it will be scoped locally to the function. That means it will be created in bet, and then destroyed at the end. It does exactly what you need. I know you later used bet.cBet to refer back to this, but this is actually the opposite of what you really need. Instead, you want to use return.

return is what will take a value from the function's scope and return it to where it was called from. You use it, but you haven't quite gotten the point. Instead of using it and then attaching the value to the bet function, you can return it and store it in a name in the global space. Here's how I'd call my function from above:

bet = get_bet(tokens)

tokens is of course the full amount of tokens the user has. It's going the opposite to how return works, it sends the value of tokens into the function so that the bet amount can be validated. Then at the end, the bet amount is returned and now stored in my bet value. No need to worry about connecting values to their functions.

If you try to organise all your code this way, you'll get around the problem of scope and you can stop using global, instead passing and returning values between functions.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Great, thanks! I'm still a little confused about return and the stuff in () of the function. Please look at the top of my code. \$\endgroup\$ – retep Jan 18 '16 at 22:26

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.