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()
1 Answer 1
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.
-
\$\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– retep2016年01月18日 22:26:20 +00:00Commented Jan 18, 2016 at 22:26
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\$classes
until now. \$\endgroup\$