Why are my pickled objects not restoring? - python

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.

Related

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.

Global variables not carrying across FUNCTIONS

(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

Python - Unexpected behaviour of class attributes

Edited in simple words
code:
class temp:
attr1 = 0
attr2 = []
t1 = temp()
t2 = temp()
t1.attr1 = 50
t1.attr2.append(50)
print(t1.attr1)
print(t1.attr2)
print(t2.attr1)
print(t2.attr2)
output:
50
[50]
0
[50]
I have called append only on attr2 object t1 but the append changes attr2 of both objects. if attr2 is shared (class attributes) then why does attr1 values are different for t1 and t2. What might have caused this unexpected behaviour ?
old question
I am writing a python code for blackjack. The code I have written is as follows.
from random import randint
from IPython.display import clear_output
deck = ["S","D","C","H"]
class Player:
cards = []
total = 0
amount = 0
def __init__(self,money=0):
self.amount = money
def busted(self):
return self.total > 21
def showCards(self):
for i in self.cards:
print("| {}{} |".format(i%13,deck[i//13]),end = " ")
print()
def hit(self):
no = randint(1,53)
self.cards.append(no)
if no % 13 == 1:
if self.total + 11 > 21:
self.total+=1
else:
self.total+=11
else:
self.total += (no%13 if no%13 <= 10 else 10)
dealer = Player(10000)
p1 = Player(0)
print("Welcome to BlackJack ....")
while True:
try:
p1.amount = int(input("Enter the amount you currrently have for the game"))
except:
print("invalid Value")
continue
else:
break
Game = True
while Game:
print(dealer.cards)
print(p1.cards)
dealer.hit()
print(dealer.cards)
print(p1.cards)
print(dealer.total)
print(p1.total)
Game = False
output of this code is as follows
Welcome to BlackJack ....
Enter the amount you currrently have for the game55
[]
[]
[45]
[45]
6
0
as you can see I had called hit() only once on dealer object but it is appending it to cards attribute of both dealer as well as p1 object. However total attribute is different. Can anyone explain what might have caused this unexpected behaviour ?
When you do t1.attr1 = 50, you're rebinding attr1 to a new value in the t1 object's attribute namespace. It previously let you access the value bound in the class namespace, but when you bind a new value, you hide the one from the class (for that instance only).
In contrast, when you do t1.attr2.append(50), you're mutating the existing list (which is bound in the class namespace, but is visible though all instances) in place, with no rebinding of variables happening at all. This is why you see the change in t2. The variables t1.attr2 and t2.attr2 are both references to the same object (which you can verify using the is operator: t1.attr2 is t2.attr2).
In general, it's usually not a good idea to use lists or other mutable values for class variables if you don't want them to be shared by all instances. It's not forbidden though, because sometimes you do specifically do want the shared behavior.
I got what you are asking. You need to differentiate all cards with player cards. So, instead of naming everything as cards, I would suggest doing this:
class Player:
all_cards = []
total = 0
amount = 0
and update __init__ as :
def __init__(self, money=0):
self.amount = money
self.player_cards = []
while doing append operation, append it to all_cards and to the player_cards. Anyway, you are printing only player cards, you can see different list of cards.
Here is full code :
from random import randint
from IPython.display import clear_output
deck = ["S","D","C","H"]
class Player:
all_cards = []
total = 0
amount = 0
def __init__(self,money=0):
self.player_cards = []
self.amount = money
def busted(self):
return self.total > 21
def showCards(self):
for i in self.player_cards:
print("| {}{} |".format(i%13,deck[i//13]),end = " ")
print()
def hit(self):
no = randint(1,53)
self.player_cards.append(no)
self.all_cards.append(no)
if no % 13 == 1:
if self.total + 11 > 21:
self.total+=1
else:
self.total+=11
else:
self.total += (no%13 if no%13 <= 10 else 10)
dealer = Player(10000)
p1 = Player(0)
print("Welcome to BlackJack ....")
while True:
try:
p1.amount = int(input("Enter the amount you currrently have for the game"))
except:
print("invalid Value")
continue
else:
break
Game = True
while Game:
print(dealer.player_cards)
print(p1.player_cards)
dealer.hit()
print(dealer.player_cards)
print(p1.player_cards)
print(dealer.total)
print(p1.total)
Game = False
This happened because list is a mutable object, and it is created once only when defining the class, that is why it becomes shared when you create two instances. Therefore, to solve this problem, we can use constructor like what I have mentioned above. When we put the list in constructor, whenever the object is instantiated, the new list will also be created.

python: setting attributes from module to module

Im teaching myself python and I've come upon a snag in a simple game project I'm working on.
I would like to keep the players stats in a different module from the rooms that are being run by the game engine. Problem is when I try to set a Playerattribute from a different module, it doesn't save the new attribute and instantiates the original attribute.
here is the Playerclass in the entities module
class Player(object):
def __init__(self):
self.name = ' '
self.hp = 0
self.current_hp = 0
self.strength = 0
self.dexterity = 0
self.constitution = 0
And here is how im trying to manipulate and test the attributes in the rooms module
class CharacterCreation(Scene):
def enter(self):
character = entities.Player()
character.hp = 10
print character.hp
return 'barracks'
class Barracks(Scene):
def enter(self):
character = entities.Player()
print character.hp
return 'shop'
When I test this with the rest of my code, here is what I get.
-------------------------------------------------------------------------------
10
-------------------------------------------------------------------------------
0
-------------------------------------------------------------------------------
So what am I missing here? I thought I could set that attribute using =but it seems I'm mistaken? the first time I did it, it worked, but then how do i get python to set the new value of hp to 10?
You're creating a new Player object in each scene, changing its attributes, and then throwing it away.
You should be explicitly passing one single player into each scene:
def enter(self, player):
... do something with player ...
It looks like you're creating a new Player instance on every enter method...
If you're going to have only one player in the game, you could have it as a global variable (usually not very good idea) or even better, as a singleton class:
http://blog.amir.rachum.com/post/21850841339/implementing-the-singleton-pattern-in-python
I made some tweakings to the code. It adds the PlayerPool class (which is more like a cache, actually). It may give you some ideas :)
#!/usr/bin/env python
#http://stackoverflow.com/questions/14629710/python-setting-attributes-from-module-to-module/14629838#14629838
class Player(object):
def __init__(self):
self.name = ' '
self.hp = 0
self.current_hp = 0
self.strength = 0
self.dexterity = 0
self.constitution = 0
class PlayerPool(object):
_players = dict()
#classmethod
def getPlayerByName(cls, name):
if not name in cls._players:
newPlayer = Player()
newPlayer.name = name
cls._players[newPlayer.name] = newPlayer
return cls._players[name]
class Scene(object):
pass
class CharacterCreation(Scene):
def enter(self):
character = PlayerPool.getPlayerByName("foobar-hero")
character.hp = 10
print "%s has %s points of hp" % (character.name, character.hp)
return 'barracks'
class Barracks(Scene):
def enter(self):
character = PlayerPool.getPlayerByName("foobar-hero")
print "%s has %s points of hp" % (character.name, character.hp)
return 'shop'
if __name__ == "__main__":
step1 = CharacterCreation()
if step1.enter() == "barracks":
step2 = Barracks()
step2.enter()
That outputs:
borrajax#borrajax-comp:~/Tests/Python/Stack Overflow$ python ./players.py
foobar-hero has 10 points of hp
foobar-hero has 10 points of hp
Welcome to python. I'm sure you'll find it has really cool features... such as the ability to return functions, or pass functions as parameters, inspect the classes defined in any module... Looks like things you could find useful.

Simple OOP query. Python: saving object names in __init__

As close to the title as possible. I am very new to OOP (and coding in general) and would like to create a program that plays Blackjack. I want to save the objects I create into a list automatically so once it's created I can use the list to cycle through them (I want to create player objects, but save the variable names (right word???) to a list so once it's created using user input I can automatically access them.
So far I've built this:
ROSTER = []
class Player():
"""player in the game"""
def __init__(self, name, score= 0):
self.name = name
self.score = score
ROSTER.append(self.name)
But of course this only gives me the names put into the variable self.name... how can I capture the variable names (right term once again?). self.name won't (afaik) let me access the individual objects via:
excuse the crap formatting plz. =/
Also, if I'm using the wrong terms plz correct me. Learning on your own is kinda hard as far as mastering all the terms.
EDIT: sorry, my post was confusing. The code I posted was meant to show a dead end, not what I am looking for, and my terminology is pretty bad (I feel like a foreigner most of the time). When I said variable names, I think I should have said 'object names' (?) so:
p1 = Player("bob")
p2 = Player("sue")
I want ["p1","p2"] (or if a string format will give me problems when I try to call them, whatever the appropriate way is.)
Once again, sorry for the super confusing first post. Hopefully this edit is a little clearer and more focused.
You could put self in the roster instead. I.e.:
ROSTER = []
class Player():
def __init__(self, name, score = 0):
self.name = name
self.score = score
ROSTER.append(self)
Then you would use the ROSTER list like this:
>>> p1 = Player("Jane")
>>> p2 = Player("John")
>>> ROSTER
[<__main__.Player instance at 0x10a937a70>, <__main__.Player instance at 0x10a937a28>]
>>> for p in ROSTER:
... print p.name, p.score
...
Jane 0
John 0
Or, perhaps better, you could make ROSTER a dictionary:
ROSTER = dict()
class Player():
def __init__(self, name, score = 0):
self.name = name
self.score = score
ROSTER[self.name] = self
That way you can access the player objects by name using ROSTER[name], and you can cycle through them with ROSTER.values(). For example:
>>> p1 = Player("Jane")
>>> p2 = Player("John")
>>> print ROSTER["Jane"].name, ROSTER["Jane"].score
Jane 0
>>> print ROSTER["John"].name, ROSTER["John"].score
John 0
>>> for p in ROSTER.values():
... print p.name, p.score
...
Jane 0
John 0
Are you talking about this?
ROSTER = []
class Player():
def __init__(self, name, score= 0):
self.name = name
self.score = score
ROSTER.append(self)
a=Player('Jack',100)
b=Player('Blackk',1000)
c=Player('Mike')
for x in ROSTER:
print(x.name,x.score)
output:
Jack 100
Blackk 1000
Mike 0

Categories