1

I'm making an attack system in python with def, but i got an error saying that the variable got referenced before assignment, eventhough i wrote it before the def. Does anyone know how?

import random
import time

monster = 20

def fight():
  print('Monster Spawned! Attack by 
typing A')
  A = str(input())

  while (A == 'A'):
    damage = range(1, 21)
    damage_done=(random.choice(damage))
    monster = monster - damage_done
    print('Slish Slash!')
    print(monster)
    print(damage_done)

fight()
if (monster < 0):
    print('Good Job')

EDIT 1: Error message is

Traceback (most recent call last):
File "main.py", line 17, in <module>
  fight()
File "main.py", line 12, in fight
  monster = monster - damage_done
UnboundLocalError: local variable 
'monster' referenced before assignment
Kuro
  • 23
  • 3
  • Your while loop never terminates if the input is `A` and it never gets run if the input is not `A`, I don't think your while condition should be `A == 'A'` and you should probably change the variable name `'A'` to something more meaninful like `user_input`, see [Reference](https://www.earthdatascience.org/courses/intro-to-earth-data-science/write-efficient-python-code/intro-to-clean-code/expressive-variable-names-make-code-easier-to-read/) – Liam Apr 09 '22 at 07:14

3 Answers3

0

Add global monster one line before monster=monster-damage_done If you don't add that then python recognizes monster as a local variable, thus the local variable is used before assignment.

itsme11
  • 13
  • 3
0
global monster
monster = monster - damage_done

you are in a function and it can't access a variable outside of it, so say to the function that there is a global variable with this name, outside and that it's a global variable... if not, it will be considered as a local variable and it will search for the monster variable inside the function

I think your code needs a rewrite:

import random
monster = 20
def fight():
  print('Monster Spawned! Attack by typing A')
  A = str(input())
  global monster
  if A == 'A':
      while monster > 0:
          damage = range(1, 21)
          damage_done=(random.choice(damage))
          monster = monster - damage_done
          print('Slish Slash!')
          print(monster)
          print(damage_done)
      else:
          print('Good Job')
fight()

you want a while to run unless monster < 0 and if monster < 0, then you want to print good job.

Liam
  • 461
  • 4
  • 27
MoRe
  • 2,296
  • 2
  • 3
  • 23
  • 1
    Why *str(input())*? Have you heard of randint? Why does monster have to be global? It can be local - just initialise it before testing the user input – DarkKnight Apr 09 '22 at 06:23
  • @LancelotduLac did you read question code? he used **str** and **global variable**. I know they are incorrect, and at least can be better, but we bombing he with information? In my code, there are some point that he must consider and check, please dont add more information and disappoint him at start :) – MoRe Apr 09 '22 at 13:38
  • 2
    one slight nitpick is that you could describe why a global variable can't be accessed without explicitly declaring it as such. it is specifically because `monster` is assigned (anywhere) within the function, which makes it local to the function. If monster were not assigned within the function, but only referenced, this code would work without the global keyword. – Michael Delgado Apr 09 '22 at 16:49
  • @MichaelDelgado thanks a lot and good point... I attempted to say that by "search for the monster" and simple way, but I think your comnent is better – MoRe Apr 09 '22 at 17:11
-1

TLDR:

  • You are modifying the monster variable within the function which makes it local to the fight() function, hence causing the error.

  • You are also checking the value of monster outside of the fight() function which will not give you the desired functionality.

A simple quick fix would be to move the declaration of monster to the start of the fight() function and moving the if monster < 0 statement inside the fight() function at the end of the assignment of monster like so:

import random

def fight():
  print('Monster Spawned! Attack by typing A')
  monster = 20
  A = str(input())

  while (A == 'A'):
    damage = range(1, 21)
    damage_done=(random.choice(damage))
    monster = monster - damage_done
    if (monster < 0):
        print('Good Job')
        break
    print('Slish Slash!')
    print(monster)
    print(damage_done)

fight()

However I suggest you read the following:

Problem 1:

You are getting this error:

UnboundLocalError: local variable 
'monster' referenced before assignment

because you are declaring monster outside of fight() hence why fight() creates an error from not being to see monster. (As @MoRe correctly pointed out).


Problem 2:

Depending on whether or not you want monster to be accessible outside the fight() function this could be a problem. You are not returning the monster variable from your fight() function so if you want to use the updated value of the monster variable later then you will need to return it from the fight() function.


Problem 3:

Your while loop will only be run once if the input is A and will never terminate once you enter A once. If the input is not A then your while loop will never run. This isn't the best approach regarding user input. I would suggest validating the user input, you can read more about it here: Asking the user for input until they give a valid response


Problem 4:

As @Lancelot du Lac mentioned, I don't think you need to convert the input() to a string, you can simply read the input and check if it matches the A character.

The input() function always converts the user input into a string and then returns it to the calling program.

Reference

Suggestion:

I have removed the import time import as you are not using it in the code provided (be sure to add it back if you're using it later on in other parts of the code)


Solution 1 (least amount of change):

If you can afford to assign the monster variable within the fight() function then this is the solution with the least amount of changes. I also suggest breaking out of the while loop once the condition is True if not you run into an infinite loop.

import random

def fight():
  print('Monster Spawned! Attack by typing A')
  monster = 20
  A = str(input())

  while (A == 'A'):
    damage = range(1, 21)
    damage_done=(random.choice(damage))
    monster = monster - damage_done
    print('Slish Slash!')
    print(monster)
    print(damage_done)
    if (monster < 0):
        print('Good Job')
        break

fight()

Solution 2 (Making monster global):

As @More's answer suggest you could declare the monster variable as a global variable:

global monster

It's important to explain why monster cannot be accessed as a global variable unless you declare it as such. This is because you are making an assignment to monster from within the fight() function, if you only reference it or declare it from within the function (like solution 1 suggests) then you shouldn't have that error anymore.

While this does solve your error, I don't think it's the best solution. In general it would be better to pass parameters to functions instead of declaring them as global variables.

See: (Python) Should I use parameters or make it global?


Solution 3 (Passing monster as a parameter)

You don't have to declare monster as global or move the assignment of monster to within the fight() function, you can simply pass it as a parameter like so:

import random

monster = 20
def fight(monster):
  print('Monster Spawned! Attack by typing A')
  A = input()
  if A == 'A':
      while monster > 0:
          damage = range(1, 21)
          damage_done=(random.choice(damage))
          monster = monster - damage_done
          print('Slish Slash!')
          print(monster)
          print(damage_done)
      else:
          print('Good Job')
fight(monster)

Solution 4 (More flexible)

Taking all the problems into account here is another option to write your program to allow for more flexibility.

import random

def fight(monster, user_input):
    if user_input == 'A':
        damage = range(1, 21)
        damage_done=(random.choice(damage))
        monster = monster - damage_done
        print('Slish Slash!')
        print(monster)
        print(damage_done)
    else:
        print('You have not typed A')
    return monster

monster = 20
user_input = ''
while True:
    user_input = input('Monster Spawned! Attack by typing A: ')
    monster = fight(monster, user_input)
    if (monster < 0):
        print('Good Job')
        break
  • The program initializes the monster variable to 20 (as per your example) and then passes that variable to the function while updating it on the return of each function call.

  • The program asks the user repeatedly for user input and displays an error message if they don't enter A, this approach is more robust as it will allow you to add other user inputs options later on if you decide so. (If you only have need one option then you may not need this).

  • It's also better to have a flexible function that can be called several times with different inputs (hence why we use parameters to allow for this flexibility). If you absolutely need to have the while loop be inside the fight() function then you can move it inside, but I don't see a good reason why you would want a while loop that never terminates inside the fight() function.

Thanks to @MoRe, @Lancelot du Lac and @Michael Delgado for their helpful answer, comments and contribution that allowed me write this answer.

Liam
  • 461
  • 4
  • 27
  • 1
    Using strings like that for loop control is not best practice. *while True* with an appropriately place *break* is a more common paradigm – DarkKnight Apr 09 '22 at 07:21
  • Thank you I've updated the answer, sorry for the mistake. – Liam Apr 09 '22 at 07:24
  • 1
    and thank you for your comprehensive answer, but i think time can be removed because we dont use it and you can optimize it more... like print(...,sep="\n") and random.randint... – MoRe Apr 09 '22 at 17:22
  • 1
    Thank you for your helpful comment @MoRe, I did think about removing `time` but I assumed that the OP had imported it and just hadn't included the section of code that used it but I could of course be wrong, I made an assumption and aired on the side of caution. There are of course more optimizations to make but I didn't want to lose OP from being able to understand the changes and the problem. – Liam Apr 09 '22 at 19:06