Global variables not carrying across FUNCTIONS - python

(For those who saw this question the last time I asked it, I sincerely apologize, I used the term "module" when I meant "function", but thank you for your very helpful advice nontheless! I'll make sure to keep it in mind when I begin to add other files into the equation.)
I'm trying to make a text based adventure game using python, and as a result it requires a lot of variables, and as backtracking is a must, I need to use global variables for the essential ones. I have run into speed bumps when trying to get these to be read by other functions. This is the line of code used to define the universal variables, and their starting value
def reset():
global gold, exp, etnl, maxHP, curHP, maxmana, curmana, attack, defence, helm, armtop, armbot, boots, gloves, weapons
gold = 0
exp = 0
etnl = 100 #exp to next level
maxHP = 50
curHP = 50
maxmana = 10
curmana = 10
attack = 5
defence = 5
helm = "none"
armtop = "none"
armbot = "none"
boots = "none"
gloves = "none"
weapon = "fists"
And for example, when I try to display one of the global variables, it shows up as the variable being undefined, as shown here:
def gamestart():
clear() #this command is fine, simply to make it look neater when it is run again
print("you wake up in a clearing in the forest, you can't remember what happened.")
print("you feel numb, you realize you're lying flat on your back.")
print
print("HP " + str(curHP) + "/" + str(maxHP))
Can someone help me out with this?
Is there an easier way to do this?
All help is appreciated!
(yes, I make sure to run the reset function before the newgame function)
A much simpler version if this, at least according to me is:
def variable():
global foo
foo = 7
def trigger():
variable():
output():
def output():
print(foo)

You could store those things into a class used as storage-container. If you declare them classvariables and any accessors as #classmethods you do not need an instance.
class GameState:
gold = 0
exp = 0
etnl = 100 #exp to next level
maxHP = 50
curHP = 50
maxmana = 10
curmana = 10
helm = "none"
armtop = "none"
armbot = "none"
boots = "none"
gloves = "none"
weapon = "fists"
weapons = {"fists":(5,5),"sword":(15,12),"mace":(30,3),"cushion":(2,20)}
#classmethod
def reset(cls):
cls.gold = 0
cls.exp = 0
cls.etnl = 100 #exp to next level
cls.maxHP = 50
cls.curHP = 50
cls.maxmana = 10
cls.curmana = 10
cls.helm = "none"
cls.armtop = "none"
cls.armbot = "none"
cls.boots = "none"
cls.gloves = "none"
cls.weapon = "fists"
#classmethod
def attack(cls):
return cls.weapons.get(cls.weapon,(0,0))[0]
#classmethod
def defense(cls):
return cls.weapons.get(cls.weapon,(0,0))[1]
for w in State.weapons:
State.weapon = w
print("{} has attack {} and defense {}.".format(w, State.attack(),State.defense()))
Output:
fists has attack 5 and defense 5.
sword has attack 15 and defense 12.
mace has attack 30 and defense 3.
cushion has attack 2 and defense 20.
You might want to seperate some things out - f.e. an extra class for the weapon/damage/defense related stuff ...
More reading:
What is the difference between #staticmethod and #classmethod?
https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables

Instead of global variables have you considered storing all the stats in a class/struct? Create an instance of the class at the start of the game, with its default values being specified in the constructor.
G = StartClass()
def gamestart():
print("you wake up in a clearing in the forest, you can't remember what happened.")
print("you feel numb, you realize you're lying flat on your back.")
print("HP " + str(G.curHP) + "/" + str(G.maxHP))
Alternatively, declaring G globally and passing it into gamestart(G) and/or re-instantiating in the reset() function might be options.

Here is a simple example of what I think you are trying to accomplish. If you are using global variables, then you need to be sure you are not inadvertently creating local variables with the same names in your functions (when you mean to be modifying the global variable).
You should look at using classes which I think would help you with some of the semantic confusion here.
value = 10
def reset():
global value
value = 10
def start():
print(f'initial value: {value}')
global value
value += 1
print(f'updated value: {value}')
reset()
print(f'reset value: {value}')
start()
# OUTPUT
# initial value: 10
# updated value: 11
# reset value: 10

Related

Use exec to modify global variables in a class

I've recently been refactoring some of my code to use OOP, and I've run into a problem where I can't quite get either global vars, exec(), or a combination of the two to work. The relevant code is below:
# invObject class. Has all properties of an inventory object.
class invObject:
def __init__(self, name, info, output, effect):
self.name = name # Used in menus.
self.info = info # Describes effect.
self.output = output # Printed on use.
self.effect = effect # Executed on use.
def printInfo(self): # Function for name and description.
return "{} - {}".format(self.name, self.info)
def use(self): # Function to use items. It's that easy.
global dodgeChance
global maxHp
global hp
global atk
exec(self.effect)
print(self.output) # Prints output. Also very simple.
print("{} {} {} {}".format(dodgeChance, maxHp, hp, atk)) # debugging
...
inventory[slot].use() # does not update values
Basic rundown: inventory[slot].use() should call the use() function of the object. use() should execute the code stored in inventory[slot].effect.
The output from the debugging line doesn't change anything, even inside the function. I've already tried making it return exec(self.effect) to no avail. print(self.output) does work.
EDIT: Here's a minimal reproducible example that includes everything it needs to run, not just the most important things.
# Assign base stats
dodgeChance = 0
maxHp = int(input("Input maximum HP. > "))
hp = int(input("Input current HP. > "))
# invObject class. Has all properties of an inventory object.
class invObject:
def __init__(self, name, info, output, effect):
self.name = name # Used in menus.
self.info = info # Describes effect.
self.output = output # Printed on use.
self.effect = effect # Executed on use.
def printInfo(self): # Function for name and description.
return "{} - {}".format(self.name, self.info)
def use(self): # Function to use items. It's that easy.
global dodgeChance
global maxHp
global hp
global atk
exec(self.effect)
print(self.output) # Prints output. Also very simple.
print("{} {} {} {}".format(dodgeChance, maxHp, hp, atk)) # debugging
empty = invObject("None", "Vacant slot.", "There's nothing in that slot!", "")
apple = invObject("Apple", "Gives 20 health.", "Ate the apple. Gained 20 health.", "hp = hp + 20\nif hp > maxHp: hp = maxHp")
drink = invObject("Drink", "Some kind of energy drink. Raises dodge chance to 75%.", "Drank the drink. Dodge chance is now 75%!", "dodgeChance = 75")
# Assign base inventory
inventory = [apple, drink, empty]
slot = int(input("Input slot number to use. ")) - 1
inventory[slot].use() # does not update values
# Show final stats
print("New HP value: " + str(hp))
print("Dodge chance: " + str(dodgeChance) + "%")
print()
print("Inventory contents:")
print("Slot 1: " + str(inventory[0].name))
print("Slot 2: " + str(inventory[1].name))
print("Slot 3: " + str(inventory[2].name))
EDIT 2: Another thing: the code works if I don't use exec() (e.g. change it out for hp += 20).
exec() has optional arguments for you to provide the global and local variable contexts.
But you didn't provide them.

Menu for a turn based game

Our teacher has assigned us an assignment for doing a turned based game.
This only included name.strip() but this does not prompt player to input unique name:
def start():
print("\nNew game started...Setting up Player 1's team...\n")
for total_characters in range (1,4):
name = input('Enter a unique name for unit #'+str(total_characters)+'==> ')
if not name.strip():
print('Cant be blank name,please provide a unique name')
return start()
else:
role_selection()
def role_selection():
for total_characters in range (1):
role = input('Select unit #'+str(total_characters)+' type: (W)arrior, (T)anker, or Wi(Z)ard ==> ')
total_characters+=1
if role.upper() == 'W':
pass
elif role.upper() == 'T':
pass
elif role.upper() == 'Z':
pass
else:
print("I don't understand what are you typing.")
return role_selection()
There are things that doesn't make sense :
You have the exact same function twice :
def set_up(team_size)
def set_up(name)
You are doing :
for total_units in range(team_size):
[...]
invalid = True
[...]
while invalid: # Infinite loop
set_up() # What's this function ?
As you can see from the comments in the code above, you never set invalid to False, leading to the infinite loop.
Note: My recommendation is that you should check out some tutorial on python before moving on coding a complex project, because your homework is not that easy.
Edit :
From the new code you've posted, you could do something like this :
def new_game():
names = []
for unit_id in range(1,4):
print(f'Enter a unique name for unit #{unit_id} ->')
empty = True
while empty:
name = input('>')
if name == "":
continue
empty = False
if name in names:
print('Unit name must be unique.')
else:
print('Name looks good!')
names.append(name)
python menu
At first glance, this stood out to me:
if not name.strip():
print('Unit name could not be blank.')
invalid = True
Remember in python the indentation matters. You are setting invalid to True regardless of the if condition. Further down you have a while loop that checks it.
The other if conditions have invalid=True inside the condition. Plus you don't have invalid=False anywhere as far as I see, so you'll get an error if you don't declare it somewhere so it's always on the path before the while.
this doesn't seem like a specific problem, more an ask for general guidance for going about this kind of problem?
One thing to note is that your above script only uses functions (which store behaviour) whilst really for something like the turn based game, you need to store behaviour (attacks etc) and information (how much health is left etc).
I won't write the script for you, but here's an example of how you might define an rpg like entity, capable of attacking, being attacked by another entity etc:
class Entity:
"""Abstract class for any combat creature"""
def __init__(self, name, health):
self.name = name
self.health = health
self.is_alive = True
def attack(self, other):
dmg = random.randint(7, 12)
other.be_attacked(dmg)
def be_attacked(self, dmg):
self.health = self.health - dmg
if self.health <= 0:
self.die()
def die(self):
self.is_alive = False
def check_life(self):
return self.is_alive
You can then initialise a bunch of these to make up the 'team' you where talking about:
one_person = Entity('Lee', 34)
another_person = Entity('Someone Else', 21)
etc. Hope that helps a bit. Good luck with your project and have fun!

Is there a way to fix Name Error due to scope?

I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.

Why are my pickled objects not restoring?

So I'm working on a text-based RPG and am fairly new to coding just started learning python about a month ago so if someone could help they would be a lifesaver.
When I save and load my game it loads my default player stats how do I make it load the stat increases and also my potions and gold reset to default as well.
class Player:
name = "Razor"
atkl = 15
atkh = 20
magic_light_slashl = 20
magic_light_slashh = 25
magic_fireballl = 40
magic_fireballh = 48
magic_lightningl = 55
magic_lightningh = 65
maxhp = 50
hp = 50
maxap = 10
ap = 10
exp = 0
level = 1
gold = 20
potions = 0
great_potions = 0
max_potions = 0
elixers = 0
great_elixers = 0
max_elixers = 0
def save():
player = Player
level_state = Player.level
with open('savefile', 'wb') as f:
pickle.dump([player, level_state], f, protocol=2)
print("Game has been saved.")
start_up()
def load():
if os.path.exists('savefile') == True:
with open('savefile', 'rb') as f:
player, level_state = pickle.load(f)
print("Loaded save state.")
start_up()
else:
print("Could not find save file.")
main()
and here is a bit of how I level up.
def level_up():
if Player.level == 1:
if Player.exp >= 30 and Player.exp < 80:
print("You are level 2")
Player.level = 2
Player.atkl = 17
Player.atkh = 22
Player.magic_light_slashl = 23
Player.magic_light_slashh = 27
Player.maxhp = 53
Player.hp = 53
Player.maxap = 12
Player.ap = 12
If you need more of my code to help me just ask.
You're misunderstanding how classes work. You're using class-level properties, rather than instance-level properties, which is causing them to not pickle correctly. You're essentially treating a class as if it were a dictionary and that's fundamentally not how they work.
When you create a class it acts like a blueprint. A blueprint for a car can be used to create many car "instances", but the blueprint isn't a car itself.
So in order to get an instance out of your Player class, you need to "instantiate" it. You do this by calling the class by name with parenthesis () after it. The parenthesis indicate to Python that you're calling the class' constructor which is defined as __init__() inside your class. Your class has no constructor so should first define one.
class Player:
def __init__(self):
# this is the constructor
# let's add some instance-level properties
self.name = 'Razor'
# you can add the rest of your properties here as long as they being with self
print 'I am a new instance of the player class, my name is ' + self.name
You can then instantiate this and store the instance in a variable like this (note that our message will print during construction):
player = Player()
You can then access the properties on that instance
print player.name
Or you can change them
player.name = 'Blade'
print player.name
# prints 'Blade'
The reason this instantiation is useful and important is that it lets you create as many "players" (or characters, or enemies, etc.) as you want, and they all retain their own properties. self is a clear indicator that you're talking to the instance, and not the class itself.

cannot find attribute in imported file

I am trying to make a button interface for my program which is imported as rlg. rlg has a live graphing system in which two variables can be measured and updated as the generations of the simulation go on. I want to be able to make these two variables selective so i made a dictionary 'graphLines' in the main() method of rlg in which each string selection on the drop down menu acts as a key. However i dont seem to be able to access it and get the error message: AttributeError: 'function' object has no attribute 'graphLines'. Can anyone see what i am doing wrong.
from Tkinter import *
import runlivegraph3 as rlg
def run():
rlg.main()
def setLine1(name):
rlg.main.Line1data = rlg.main.graphLines[name] #graphlines is a dictionary in runlivegraph3 main method
def setLine2(name):
rlg.main.Line2data = rlg.main.graphLines[name]
root = Tk()
var1 = StringVar()
var1.set("select graph line 1 data") #initial variable in drop down menu, each string is a key in the graphLines dictionary
op1 = OptionMenu(root, var1, 'Political attacks in turn',
'Ethnic attacks in turn',
'Total attacks in turn',
'Ethnic attacks as a percentage of total attacks',
'Political attacks as a percentage of total attacks',
'Group 1 ethnic antagonism',
'Group 2 ethnic antagonism',
command = setLine1).pack()
var2 = StringVar()
var2.set("select graph line 2 data") #initial variable in drop down menu
op2 = OptionMenu(root, var2, 'Political attacks in turn',
'Ethnic attacks in turn',
'Total attacks in turn',
'Ethnic attacks as a percentage of total attacks',
'Political attacks as a percentage of total attacks',
'Group 1 ethnic antagonism',
'Group 2 ethnic antagonism',
command = setLine2).pack()
butn = Button(root, text = 'run', command = run)
butn.pack()
root.mainloop()
this is the main() function of the program i am importing in the Tkinter button program
from matplotlib.pylab import *
import sys, random, time, csv
def main():
IDs = {}
boardDims = (20,20)
Line1data = None
Line2data = None
turnLimit = 40
pause = 0.0001
ethnicPred = []
politicalPred = []
totalAttacks = []
generation = []
data1 = []
data2 = []
data3 = []
ethAnt1 = []
ethAnt2 = []
polAnt1 = []
polAnt2 = []
EthnicAttacksInTurn = []
PoliticalAttacksInTurn = []
TotalAttacksInTurn = []
ProportionEth = []
ProportionPol = []
board = make_board(boardDims)
finallyAddAgents(IDs, board, boardDims)
splitAgents(IDs)
setRemainingPolitics(IDs)
setPoliticalAntagonism(IDs)
turn = 0
line1, = plot(turn, 0, 'b') #initialise lines
line2, = plot(turn, 0, 'r')
running = 1
while running:
ion() #sets up graph base and axes
axes()
xlim(0,turnLimit)
ylim(0,30)
if turn == turnLimit: running = 0
print_board3(IDs, board, boardDims)
print 'turn ', str(turn)
polAttackTurn = []
ethAttackTurn = []
AllAgentsPerformActions(IDs, board,turn,ethnicPred, politicalPred,
totalAttacks,polAttackTurn,ethAttackTurn)
totalAttackTurn = sum(ethAttackTurn) + sum(polAttackTurn)
if totalAttackTurn != 0:
propEth = (sum(ethAttackTurn)*100)/totalAttackTurn
propPol = (sum(polAttackTurn)*100)/totalAttackTurn
if totalAttackTurn == 0:
propEth = 0
propPol = 0
TotalAttacksInTurn.append(totalAttackTurn)
EthnicAttacksInTurn.append(sum(ethAttackTurn))
PoliticalAttacksInTurn.append(sum(polAttackTurn))
ProportionEth.append(propEth)
ProportionPol.append(propPol)
k = sum(politicalPred)
j = sum(ethnicPred)
#f = sum(totalAttacks)
#print k, j, f
data1.append(j)
data2.append(k)
#data3.append(f)
generation.append(turn)
for agent in IDs.values():
if agent.group == '1':
ethAnt1.append(agent.antagonism['2'])
break
for agent in IDs.values():
if agent.group == '2':
ethAnt2.append(agent.antagonism['1'])
break
for agent in IDs.values():
if agent.politics == 'A':
polAnt1.append(agent.polAntagonism['B'])
break
for agent in IDs.values():
if agent.politics == 'B':
polAnt2.append(agent.polAntagonism['A'])
break
#this is the dictionary i am trying to access from the Tkinter button program
graphLines = {'Political attacks in turn':sum(polAttackTurn),
'Ethnic attacks in turn':sum(ethAttackTurn),
'Total attacks in turn':totalAttackTurn,
'Ethnic attacks as a percentage of total attacks': propEth,
'Political attacks as a percentage of total attacks': propPol,
'Group 1 ethnic antagonism': ethAnt1[-1],
'Group 2 ethnic antagonism': ethAnt2[-1]}
line1.set_ydata(append(line1.get_ydata(), Line1data))
line1.set_xdata(append(line1.get_xdata(), turn))
line2.set_ydata(append(line2.get_ydata(), Line2data))
line2.set_xdata(append(line2.get_xdata(), turn))
draw()
turn += 1
I figured I'd better turn my comment into an answer, so here I go.
You are getting confused between the difference between variables and attributes, so I'll explain the difference with some examples. Your question is not a matter of importing actually, but more about scope and object oriented programming (OOP).
(e.g. 1) To set a local variable within a function, you can do:
def spam():
eggs = 5
(e.g. 2) To set an attribute on a function object (which is usually not so logical), you can do:
def spam():
pass
spam.eggs = 5
While these may appear to be similar, their effect is very different. In the first example, eggs is a local variable within the function spam. Local variables are created, accessed, and modified only within their defining function.
def spam():
eggs = 5
print spam.eggs
will result in an error, however
def spam():
pass
spam.eggs = 5
print spam.eggs
will not. In the second example, eggs is an attribute of the function (object) spam. It can be created, accessed, and modified both within a method of the object or outside the object, but not within the function itself as a local variable (also because the function does not know of its existence until it is fully defined). Therefore, the following would raise an error:
def spam():
print eggs
spam.eggs = 5
spam()
because eggs is an attribute, not a local variable.
If you are familiar with OOP, here is some expansion:
The first example would be equivalent to:
class Spam(object):
def __init__(self):
eggs = 5
while the second example would be equivalent to:
class Spam(object):
def __init__(self):
self.eggs = 5
In terms of OOP, the difference is simply that the first sets a local variable, while the second sets an instance variable. Trying to do Spam().eggs on the first class would not make sense, while on the second it could.
Finally,
To solve your problem, either define the variables you need outside of a function, or use the global keyword to show that they are global. Example usage:
def spam():
global eggs
eggs = 5
spam()
print eggs # Prints 5

Categories