Python - UnboundLocalError: local variable 'health' referenced before assignment - python

Despite importing the variable 'health' from a different module, the function below provides the error shown in the title. 'Health' is also globalised and I have removed both the globalisation and the importation of the variable and I still receive the same error.
Below is the function that is causing the issue.
def combat():
enemy_health = (random.choice(random_enemy_Health))
enemy_attack = (random.choice(random_enemy_Attack))
print("\nYou are fighting a" ,random.choice(enemies), "with an attack amount of" ,enemy_attack, "and a health amount of" ,enemy_health,".")
while health > 0 and enemy_health > 0:
if turn == 1:
while loop == False:
response=input()
try:
move = response("Do you want to attack or flee? Type '1' to attack and '2' to flee.")
move = int(move)
if move == 1:
enemy_health = enemy_health - attack
print("You attacked!")
loop = True
elif move == 2:
hub_travel()
print("You fled the battle, come back once you are stronger!")
loop = True
else:
print("Invalid number, try again")
continue
except:
print("Invalid number, try again")
continue
turn = 2
if turn == 2:
AImove = randint(1,2)
if AImove == 1:
print ("Enemy attacked!")
health = health - enemy_attack
turn = 1
continue
print ("game over!")
if enemy_health == 0:
print("The enemy has been defeated!")
gold += random.choice(gold_dropped)
The error occurs on this line in particular:
while health > 0 and enemy_health > 0:

If I were you, instead of relying on globals, I would use parameters. This advice may help you to track some errors.
Globals variables is a possibility in programs that have a few lines of code. But, when you application grows, it is a bit hard to track the current value of some variable, because it can be used in several functions or methods (probably, you need a mental mapping to find out the current value). So, this is one of the reasons why you must prefer to use local variables or parameters instead of globals.

This change would allow your function to work the way you want:
def combat(health):
...
Of course, you'd have to find the places where you call the function and pass in the value for health. I don't know if the code at that point has access to that information.
This is probably the simplest fix that could possibly address this issue. It is certainly not the best fix, but this is not a good place for an architecture tutorial.

Related

How do I stop a while loop if something happens inside a function that should stop the itterations?

Hello fellow programmers! I am a beginner to python and a couple months ago, I decided to start my own little project to help my understanding of the whole development process in Python. I briefly know all the basic syntax but I was wondering how I could make something inside a function call the end of the while loop.
I am creating a simple terminal number guessing game, and it works by the player having several tries of guessing a number between 1 and 10 (I currently made it to be just 1 to test some things in the code).
If a player gets the number correct, the level should end and the player will then progress to the next level of the game. I tried to make a variable and make a true false statement but I can't manipulate variables in function inside of a while loop.
I am wondering how I can make it so that the game just ends when the player gets the correct number, I will include my code down here so you guys will have more context:
import random
import numpy
import time
def get_name(time):
name = input("Before we start, what is your name? ")
time.sleep(2)
print("You said your name was: " + name)
# The Variable 'tries' is the indication of how many tries you have left
tries = 1
while tries < 6:
def try_again(get_number, random, time):
# This is to ask the player to try again
answer = (input(" Do you want to try again?"))
time.sleep(2)
if answer == "yes":
print("Alright!, well I am going to guess that you want to play again")
time.sleep(1)
print("You have used up: " + str(tries) + " Of your tries. Remember, when you use 5 tries without getting the correct number, the game ends")
else:
print("Thank you for playing the game, I hope you have better luck next time")
def find_rand_num(get_number, random, time):
num_list = [1,1]
number = random.choice(num_list)
# Asks the player for the number
ques = (input("guess your number, since this is the first level you need to choose a number between 1 and 10 "))
print(ques)
if ques == str(number):
time.sleep(2)
print("Congratulations! You got the number correct!")
try_again(get_number, random, time)
elif input != number:
time.sleep(2)
print("Oops, you got the number wrong")
try_again(get_number, random, time)
def get_number(random, try_again, find_rand_num, time):
# This chooses the number that the player will have to guess
time.sleep(3)
print("The computer is choosing a random number between 1 and 10... beep beep boop")
time.sleep(2)
find_rand_num(get_number, random, time)
if tries < 2:
get_name(time)
tries += 1
get_number(random, try_again, find_rand_num, time)
else:
tries += 1
get_number(random, try_again, find_rand_num, time)
if tries > 5:
break
I apologize for some of the formatting in the code, I tried my best to look as accurate as it is in my IDE. My dad would usually help me with those types of questions but it appears I know more python than my dad at this point since he works with front end web development. So, back to my original question, how do I make so that if this statement:
if ques == str(number):
time.sleep(2)
print("Congratulations! You got the number correct!")
try_again(get_number, random, time)
is true, the while loop ends? Also, how does my code look? I put some time into making it look neat and I am interested from an expert's point of view. I once read that in programming, less is more, so I am trying to accomplish more with less with my code.
Thank you for taking the time to read this, and I would be very grateful if some of you have any solutions to my problem. Have a great day!
There were too many bugs in your code. First of all, you never used the parameters you passed in your functions, so I don't see a reason for them to stay there. Then you need to return something out of your functions to use them for breaking conditions (for example True/False). Lastly, I guess calling functions separately is much more convenient in your case since you need to do some checking before proceeding (Not inside each other). So, this is the code I ended up with:
import random
import time
def get_name():
name = input("Before we start, what is your name? ")
time.sleep(2)
print("You said your name was: " + name)
def try_again():
answer = (input("Do you want to try again? "))
time.sleep(2)
# Added return True/False to check whether user wants to play again or not
if answer == "yes":
print("Alright!, well I am going to guess that you want to play again")
time.sleep(1)
print("You have used up: " + str(tries) + " Of your tries. Remember, when you use 5 tries without getting the correct number, the game ends")
return True
else:
print("Thank you for playing the game, I hope you have better luck next time")
return False
# Joined get_number and find_random_number since get_number was doing nothing than calling find_rand_num
def find_rand_num():
time.sleep(3)
print("The computer is choosing a random number between 1 and 10... beep beep boop")
time.sleep(2)
num_list = [1,1]
number = random.choice(num_list)
ques = (input("guess your number, since this is the first level you need to choose a number between 1 and 10 "))
print(ques)
if ques == str(number):
time.sleep(2)
print("Congratulations! You got the number correct!")
# Added return to check if correct answer is found or not
return "Found"
elif input != number:
time.sleep(2)
print("Oops, you got the number wrong")
tries = 1
while tries < 6:
if tries < 2:
get_name()
res = find_rand_num()
if res == "Found":
break
checker = try_again()
if checker is False:
break
# Removed redundant if/break since while will do it itself
tries += 1

How to update a variable within multiple functions: Powerball game

New to Python, but learning through struggling and trying to make good practices. Hoping someone can point me in the right direction for my python program: the powerball. It's between the computer and user, where each pick 3 unique (non duplicated) numbers between 1-9, and a powerball number between 1-3. I wanted to add a money variable that would be shared between the main() function, (which tells you 'welcome' and 'your current money is: (money)), and also shared between the compare_winnings() function, which compares my two lists, and adds the total to the (money) variable.
I've done some research through google and SO, and found that placing my money variable outside of every function turns it into a 'global variable', which seems useful since it'll be used in 2 functions. I've also learned that using the syntax 'global' is bad practice. However, the code runs fine and within the compare_winnings() function, the money is updated. When the game prompts you if you would like to play again (another function), it then starts over, and the money is back to its original value. (which starts at 20).
def main():
print("Each ticket costs $1. Your winnings will be totaled. Current money is",money, "dollars." )
start = input("\n Are you ready? [y/n]: ")
if start == "y" or start == "yes":
#erased variables for legibility; gathers numbers from computer and user
compare_winnings(actual, chosen,cpb, comp_cpb,money)
else:
play_again()
def compare_winnings(actual, chosen, cpb, comp_cpb,money):
counter = 0
print("You chose: ", chosen) #user-inputted
print("Ticket was actually: ", actual) #random (actual)
same_values = set(actual) & set(chosen)
#print ("These are the values that are the same",same_values)
if len(same_values) > 0:
counter += len(same_values)
print("Numbers correct was : ",counter)
if counter == 1 and cpb != comp_cpb :
print("You won $1")
money += 1
print("total money is:", money)
play_again()
def play_again():
play = input("Do you want to play again? ")
if play == "y" or play == "yes":
main()
else:
print("Program will exit. Thanks for playing.")
I expect for the money variable to be updated (and kept) until they decide to stop playing the game. However, it seems to be restarted when they decide to play again and or at main().

Increasing the count every time a function runs

I just started to learn Python. I am going through a book and making a text based game.
So I have a room. I want to make the player die if he/she comes into the room 3 times but couldn't figure out how to do.
def spawn():
count = 0
count += 1
print(count)
print("You dropped down nearly to the magma.")
print("There are four doors around you.")
print("Which one do you take?")
ch = input("Top, bottom, left or right? > ")
if count = 4:
dead("You wandered around too much and died.")
else:
print()
I tried to track the number with printing but I can't make it increase. What am I doing wrong?
EDIT: When I put count outside the function it gives:
Traceback (most recent call last):
File "ex.py", line 147, in <module>
spawn()
File "ex.py", line 14, in spawn
count += 1
UnboundLocalError: local variable 'count' referenced before assignment
Well in the function you each time set a local variable to 0, so that means that after the function is done, that variable no longer exists.
The trick is to use a variable that remains "active" after the function exists. For example a variable outside the function, or you can add an attribute to the function that you increment. The advantage of the latter is that it is more clear that this is something related to the function, like:
def spawn():
spawn.count += 1
print(spawn.count)
print("You dropped down nearly to the magma.")
print("There are four doors around you.")
print("Which one do you take?")
ch = input("Top, bottom, left or right? > ")
if spawn.count == 4:
dead("You wandered around too much and died.")
else:
print()
spawn.count = 0
Note that you also forgot to use double equal signs (==) for the if statement (equality check versus assignment).
Or you can do it like this:
def spawn():
if not hasattr(spawn, 'count'):
spawn.count = 0
spawn.count += 1
print(spawn.count)
print("You dropped down nearly to the magma.")
print("There are four doors around you.")
print("Which one do you take?")
ch = input("Top, bottom, left or right? > ")
if spawn.count == 4:
dead("You wandered around too much and died.")
else:
print()

My function is too long and I'm told to optimize

So I'm doing a project for an online class and when i got it scored it came back as not passed because my "play game" function was more than 18 lines long and I'm supposed to optimize it. The problem is they didn't make any suggestions as to what i should do and I keep looking at it and feel as if I need everything in it to make the fill in the blank quiz I'm building work. Any suggestions would be great as I am still very new to coding, and maybe what they are asking me to do is easy and I'm over complicating it in my head. This is the part of the code they want optimized...
def play():
'''
This is the main function which allows to play the game/quiz.
It calls the previous functions we have written.
'''
quiz = difficulty_level(user_level) #gives the difficulty
paragraph that the user asks for.
print quiz
print "\nYou will get maximum 3 guesses for each blank. Good luck.\n"
answers_list = relate_answer(user_level) #makes sure that the right
list is called up
blanks_index = 0
answers_index = 0
number_of_guesses = 3
while blanks_index < len(blanks): #This loop keeps going if all the
blanks are not replaced.
user_answer = raw_input("type in your answer for " + blanks[blanks_index] + ": ")
if check_answer(user_answer,answers_list,answers_index) == "Correct":
print "Awesome job! You gave the right answer!\n"
quiz = quiz. replace(blanks[blanks_index],user_answer)
blanks_index += 1
answers_index += 1
number_of_guesses = 3
print quiz
else:
number_of_guesses -= 1
if number_of_guesses == 0:
print "Game over! But try again!"
break
elif number_of_guesses < 0:
print "invalid"
break
else:
print "please try again."
print "You have " + str(number_of_guesses) + " guesses left."
print "Congratulations! You really know your stuff!."
play()
A quick summation of changes:
It looks like answers_index and blanks_index share the same state, no matter what, so we can generalize that to one variable idx. That cuts down on the number of variables you have to keep track of.
As others have suggested, initializing variables as a tuple like var1, var2 = 1, 2 decreases length and makes code more readable (to a point). If you have lots of variables to initialize, you can break the initialization into groups of related variables.
Setting a default value to user_level increases readability, and also gives an easy place to start debugging your function. This occurs in the def statement, so if you don't pass the argument, user_level will be 1, otherwise it will be whatever you give it in the function call. Example play(user_level=40) will overwrite that default value.
Putting the game loss condition at the start of the while loop increases visibility of the checks the code goes through before executing anything unnecessary. This should help you avoid the case where guess would be less than 0, because that code won't execute when guess==0 is True.
Setting a lose boolean with the check after exiting the while loop will also prevent the wrong print statement at the end from executing. That way, if the player loses, you will print the proper message.
Breaking the update of quiz into another function adds a more compact breakpoint into the code. In general, smaller functions that do less are preferable to large functions that do a lot. The smallest number of changes necessary is a good standard for what should be wrapped into a function, though this could be considered a stylistic convention more than a hard and fast rule.
The correct_answer method allows you to update quiz and guess in one line, and if something is wrong with the update itself, you don't have to run the entire script over and over. Instead you can take the quiz that might be causing the problem and plug it into that function until you find what's causing the problem.
I've made some minimal updates to the function you've provided which keep most of your conventions so far. Hopefully this is helpful, and happy coding!
def correct_answer(quiz, blank, answer):
"""
Function to update the quiz and return
both the quiz and reset guess to 3
"""
print("Awesome job! You gave the right answer!\n")
quiz = quiz.replace(blank, answer)
return quiz, 3
def play(user_level=1):
'''
This is the main function which allows to play the game/quiz.
It calls the previous functions we have written.
'''
idx, guess, lose = 0, 3, False
quiz = difficulty_level(user_level) #gives the difficulty paragraph that the user asks for.
print(quiz)
print("\nYou will get maximum 3 guesses for each blank. Good luck.\n")
answers_list = relate_answer(user_level) #makes sure that the right list is called up
while idx < len(blanks): #This loop keeps going if all the blanks are not replaced.
if guess <= 0:
lose = True
break
user_answer = raw_input("type in your answer for %s:" %str(blanks[idx]))
if check_answer(user_answer,answers_list,idx) == "Correct":
quiz, guess = correct_answer(quiz, blanks[idx], user_answer)
print(quiz)
else:
guess -= 1
print("please try again.\nYou have %s guesses left.\n"%str(guess))
idx += 1
if lose:
print("Game over, try again")
else:
print("Congratulations! You really know your stuff!.")
play(user_level = 3)

While loop counter

Using VS2015 Python 3.4
Having some issues with this while counter. It is slowly driving me insane as I'm sure it 'should' work but isn't updating the counter. I've ran stepping debug and can see the counter resetting to 3 before the while condition line. It is annoying me to say the least.
import random
import getpass
print ('Welcome to Rock, Paper or Sissors\nEnter an option.')
user_obj = getpass.getpass('Rock, Paper or Sissors: ').lower()
ai_obj = input('Rock, Paper or Sissors: ').lower()
rps = ('rock', 'paper', 'sissors')
#ai_rps = ['rock', 'paper', 'sissors']
#ai_obj = random.choice(ai_rps)
counter = 3
def rps_game(user_obj, ai_obj):
print('Player selected %s ' % user_obj)
print('Computer selected %s ' % ai_obj)
condition = user_obj in rps and ai_obj in rps
while condition == True and counter >= 0:
if user_obj == ai_obj:
print('Its a draw!')
elif user_obj == 'rock':
if ai_obj == 'paper':
print('You lose!')
break
else:
print('You win!')
elif user_obj == 'paper':
if ai_obj == 'sissors':
print('You lose!')
break
else:
print('You win!')
elif user_obj == 'sissors':
if ai_obj == 'rock':
print('You lose!')
else:
print('You win!')
break
else:
counter += 1
print('Invalid input, please select Rock, Paper or Sissors')
rps_game(user_obj, ai_obj)
rps_game(user_obj, ai_obj)
counter resets to its original value because it is a global variable. Global variables in Python behave differently than in other languages. Try this snippet :
counter = 3
def f():
counter = 2 # Shadows global 'counter' by a newly defined one
f()
print (counter) # Prints 3 !
You could expect the printed value to be 2, but is 3 instead. Global variables are immutable by default, and attempt to modify it in a local scope without global keyword will shadow by local definition instead. See this discussion to use global variable although it would be preferable to rework your program to use local variables.
EDIT:
There is also a mistake, counter is initialized to 3 and only incremented, so the condition >= 0 will always be satisfied (thus creating an infinite loop).
You can try:
[...]
def rps_game(user_obj, ai_obj, counter):
if counter <= 0:
return
print('Player selected %s ' % user_obj)
print('Computer selected %s ' % ai_obj)
while user_obj in rps and ai_obj in rps :
[...]
else:
print('Invalid input, please select Rock, Paper or Sissors')
rps_game(user_obj, ai_obj, counter-1)
rps_game(user_obj, ai_obj, 3)
The else containing the counter update is indented so that it hangs off of the while. That means that it executes only when the while loop terminates because the condition is false (not when you break out). In this case, the condition is false when either the user or ai input is not in the list of valid inputs or the counter is negative. The counter can never be negative because it starts at 3 and is only ever incremented. If the user input is invalid, the error message is printed, the counter is incremented, and then you use recursion to call the function with the same arguments. Since the arguments haven't changed, the function should do the same thing it just did, unless it depends on global data (counter). However, if counter is local (initialized inside the function), each invocation of the function gets a fresh copy. If counter is global (initialized outside the function, but declared global inside the function), it still won't work because you need to decrement it. And even then, because none of the other inputs have changed, it will just print the error message over and over.
Your loop bound appears to expect counter to count down from 3 since it keeps going as long as counter is positive. Incidentally, using >= as the conditional will make the loop execute 4 times (3, 2, 1, and 0). You might want to change the conditional to strictly greater than, and change the counter increment to a counter decrement.
Next, you are using recursion (the function calls itself), but this should be unnecessary since the loop gets you back to where you need to be.
Next, the lose cases break out of the loop, but the win cases do not. If you win, the code will loop, not modifying the counter or any of the other inputs, so it will not terminate.
Finally, the user input is read from outside the function, so it never changes inside the loop. If the user enters an illegal value, the loop will just keep testing the same value. You probably want to read the user input inside the loop.

Categories