Trouble understanding classes in Python 3. (Maybe a scope issue) - python

I'm having trouble with understanding classes and the way their scopes work.
This is the code that I have now.
class Player():
def __init__(self):
self.wheatFields = 1 #line 3
self.wheatProduction = self.wheatFields * 10 #line 4
player = Player()
def main(player):
print('Wheat fields: ' +str(player.wheatFields))
print('Production: ' +str(player.wheatProduction))
print('What do you want to do?\n1) Add wheat field')
if input() == '1':
player.wheatFields += 1
Now I know this is wrong, but if someone can explain why I am wrong, but I would like to know why.
On line 3 and 4, they declare the variables wheatFields and wheatProduction. That means that I can use player.wheatFields and player.wheatProduction to call them. On line 11, it is saying to increase the wheatFields by one. This variable successfully changes and saves on the main menu? How come the wheatProduction variable doesn't change if it is equal to wheatFields * 10?
Is it because it's in the initializer and only runs once?
If that is the case, how can I make it to where it updates every time I add a wheat field to my player?
I tried doing this, too, but it said that the variable wasn't defined.
class Player():
def __init__(self):
self.wheatFields = 1
wheatProduction = wheatFields * 10
This tells me that wheatFields is not defined, but it is just in the line above it.
EDIT:
Thank you, dm03514, but I am still having some trouble. What your're saying makes sense, and I don't understand why this isn't working.
class Player():
def __init__(self):
self.wheatFields = 1
self.wheatProduction = self.wheatFields * 10
def wheatProduction(self):
return self.wheatFields * 10
player = Player()
def main(player):
print('Wheat fields: ' +str(player.wheatFields))
print('Production: ' + str(player.wheatProduction))
print('What do you want to do?\n1) Add wheat field')
if input() == '1':
player.wheatFields += 1
while 1:
main(player)
This still causes wheat fields to go up, but when I call player.wheatProduction, it is staying the same for some reason.

it doesn't change because it is only assigned 1 time when your class is initialized
There are a number of ways you can address it. If wheatproduction should always be based on wheatfields multiple you could turn it into a property so it is calculated every time you access it:
class Player():
#property
def wheatProduction(self):
return self.wheatFields * 10

Related

Variables dont update to new values between classes

I am making a basic RPG style game. I have made different classes for the various parts of the code, one for each of the main items involved (hero, door, monsters etc.)
For both the hero and door, i assign them random locations, shown below in the code, but for the door I run a while loop which makes sure that the door is a certain distance from the hero (using pythagorus).
However the while loop in the door class won't work as it always uses a value of 0 for both heroC and heroR (row and column of the hero). I am relatively new to using classes, but it doesnt seem to make sense as in HeroLocation I assign a random integer to these variables, and HeroLocation is called before DoorLocation.
Any help would be greatly appreciated!!
class Hero(Character):
def __init__(self):
super(Hero, self).__init__(10, 10, 1, 1, 0, 1)
self.herolocations = list(range(1,6)) + list(range(10,14))
self.heroC = 0
self.heroR = 0
def HeroLocation(self):
#place hero
self.heroC = random.choice(self.herolocations)
self.heroR = random.choice(self.herolocations)
class Door:
def __init__(self):
self.hero = Hero()
self.doorC = 0
self.doorR = 0
def DoorLocation(self):
while ((self.hero.heroC-self.doorC)**2+(self.hero.heroR-self.doorR)**2) <= 128:
self.doorC = random.randint(1, 13)
self.doorR = random.randint(1, 13)
class game:
def __init__(self, parent):
self.hero = Hero()
self.door = Door()
def MakeMap(self):
self.hero.HeroLocation()
self.herol = self.Main_game.create_image(15+30*self.hero.heroC,15+30*self.hero.heroR, image = self.heroimage)
self.door.DoorLocation()
self.doorl = self.Main_game.create_image(15+30*self.door.doorC,15+30*self.door.doorR, image = self.exitdoor)
NB there is a lot more code, but i have only posted what i felt was the relevant stuff, if you need more to crack the puzzle message me!
You are not calling the good Hero instance in Door.DoorLocation.
Btw I really advice you to change class & methods name following Pep 8.
In Door.__init__, first line:
self.hero = Hero()
Here, you are instantiating a new Hero's instance. But, in game.MakeMap you are calling self.hero.HeroLocation().
This self.hero instance is not the same, because it was instantiated in game.__init__ and not in Door.__init__.
I didn't try, but check what behaviour gives this update:
class game:
def __init__(self, parent):
self.door = Door()
self.hero = self.door.hero
With this you now are calling the instance defined in Door.__init__, so when doing self.hero.HeroLocation() in game and (self.hero.heroC-self.doorC [...] in Door you are pointing the same instance.
Last thing, this solution may works, but is surely not what you really wants, I think a door should not store a hero, a hero should not store a door too, but here is more complex question about patterns.

Create a class containing data without user input?

I have recently started my end of the year project in computer science (self proposed, also my first year taking it so only have about 4 months of python experience) and decided I wanted to do a simple Pokemon simulator in it.
I've recently run into a roadblock: Let's say the user chooses Pikachu as their starter. I don't want the user to input their health, attack, speed, and moves. Is their a way to create a Pikachu class that already has that data in it?
I tried something like this:
class Pikachu(object):
def __init__(self):
self.data = []
def health(self):
self.health = 22
print self.health
def attack(self):
self.attack = 7
def speed(self):
self.speed = 6
Pikachu.health()
When I do this (in an attempt to check and see if the health is stored and will be returned on demand), it gives the error:
TypeError: unbound method health() must be called with Pikachu instance as first argument (got nothing instead)
I'm confused as to what the error means as well. Usually this would work and you wouldn't need an argument in the parentheses, but in this instance it doesn't.
Is there some different way to create a class and store data in it? Is there some way to just create an object or do something like this:
def Pikachu:
health = 22
attack = 7
speed = 6
it is because your variable names are the same as your method names. and also why don't you initialise everything in the constructor:
class Pikachu(object):
def __init__(self):
self.data = []
self.attack = 7
self.health = 22
self.speed = 6
def get_health(self):
return self.health
def get_attack(self):
return self.attack
def get_speed(self):
return self.speed
if __name__ == '__main__':
p = Pikachu()
print p.get_health()

How can I prevent my python game from reiterating and instead carry on?

I've made a simple game using pygame and livewires, where a sprite has to avoid falling mushrooms. The number of mushrooms falling at a certain time is meant to increase as the score increases. Here is what I mean:
from livewires import games,color
import random
games.init(screen_width=633,screen_height=479,fps=50)
class Stick_Man(games.Sprite):
def update(self):
self.x=games.mouse.x
if self.left<0:
self.left=0
if self.right>games.screen.width:
self.right=games.screen.width
self.check_collision()
def check_collision(self):
if self.overlapping_sprites:
self.over_message()
def over_message(self):
b=games.Message(value="Game Over", size=100, color=color.red,x=games.screen.width/2,y=games.screen.height/2,lifetime=250,after_death=games.screen.quit)
games.screen.add(b)
class Mushroom(games.Sprite):
score=0
start=200
score_required=100
level=1
total_score=0
speed=1
mushroom=games.load_image("mushroom.jpg")
x_position=random.randrange(640)
#staticmethod
def next_level():
indicate='Level ', + Mushroom.level, ' cleared'
message=games.Message(value=indicate,size=50,color=color.red,x=games.screen.width/2,y=games.screen.height/2, lifetime=150)
games.screen.add(message)
Mushroom().score_required+=50
Mushroom().score-=Mushroom.score_required
Mushroom().start-=150
Mushroom().speed+=5
Mushroom().level+=1
if Mushroom().start==20:
Mushroom().start+=10
def __init__(self):
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom==games.screen.height:
self.destroy()
Mushroom.score+=50
Mushroom.total_score+=Mushroom.score
if Mushroom().score==Mushroom.score_required:
self.next_level()
def check2(self):
if self.top==Mushroom.start:
self.duplicate()
def duplicate(self):
new_mush=Mushroom()
games.screen.add(new_mush)
background_image=games.load_image("background.jpg", transparent=False)
games.screen.background=background_image
stickman_image=games.load_image("stickman.png", transparent=True)
stickman=Stick_Man(image=stickman_image,left=1,bottom=480)
games.screen.add(stickman)
games.mouse.is_visible=False
b=Mushroom()
c=Mushroom()
a=Mushroom()
games.screen.add(b)
games.screen.add(a)
games.screen.add(c)
games.screen.event_brab=True
games.screen.mainloop()
The code is pretty self explanatory and whenever one of the mushrooms is equal to start, then a new object is created thus meaning a new mushroom comes in. However, what happens is that code doesn't function properly a second time and the mushrooms don't get faster spawn much faster either. Also, when the game first starts, the minute the first mushroom hits the bottom it says level one cleared, when it should be after two mushrooms. The sprite is just a red mushroom and also a stickman which can be found on g images if you want to simulate.
So my question is how do i make the object's STATS carry on from where it left off whenever another mushroom appears and also display the message at the right time
Your problem is in all of the lines that look like this:
Mushroom().score_required+=50
There are a number of problems here, which all together add up to make this have no useful effect:
Mushroom() creates a new Mushroom instance (which goes away as soon as this line is done).
Assigning (including update-assigning) to an attribute through an instance always creates or updates an instance attribute, even if there was a class attribute of the same name.
The += operator doesn't mutate immutable values like integers in-place (because that would be impossible); a += b is effectively the same as a = a + b.*
So, when you put that together, what you're doing is creating a new value equal to Mushroom.score_required + 50, then assigning that value to a new instance attribute of a temporary instance (which immediately goes away). This has no effect on the class attribute, or on any of the other instances.
You have a related, but different, problem in the lines like this:
x_position=random.randrange(640)
Unless you want all of the mushrooms to have the same x_position, this should not be a class attribute, but an instance attribute, and you're going to run into all kinds of strange problems.
Storing game stats as class attributes of a random class is a strange thing to do. There are ways you could make that work, but there's no good reason to even try. Class attributes are useful for constants that all instances of the class might as well share, but they're not useful as a substitute for global variables.
A better design would be something like this:
class Game(object):
def __init__(self):
self.score = 0
self.start = 200
self.score_required = 100
self.level = 1
self.total_score = 0
def next_level(self):
indicate = 'Level ', + Mushroom.level, ' cleared'
message = games.Message(value=indicate, size=50, color=color.red,
x=games.screen.width/2, y=games.screen.height/2,
lifetime=150)
games.screen.add(message)
self.score_required += 50
self.score -= self.score_required
self.start -= 150
self.speed += 5
self.level += 1
if self.start == 20:
self.start += 10
def update_score(self, n):
game.score += n
game.total_score += game.score
if self.score == self.score_required:
self.next_level()
class Mushroom(games.Sprite):
mushroom=games.load_image("mushroom.jpg")
def __init__(self, game):
self.x_position=random.randrange(640)
self.game = game
super(Mushroom,self).__init__(image=Mushroom.mushroom,x=games.mouse.x,y=0)
def update(self):
self.dy=Mushroom.speed
self.check()
self.check2()
def check(self):
if self.bottom == games.screen.height:
self.destroy()
game.update_score(50)
def check2(self):
if self.top == Mushroom.start:
self.duplicate()
def duplicate(self):
games.screen.add(Mushroom(self.game))
game = Game()
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.add(Mushroom(game))
games.screen.event_brab=True
* That's not completely true. In fact, a = a + b is equivalent to a = a.__add__(b), while a += b is equivalent to a = a.__iadd__(b) if such a method exists, falling back to __add__ only if it doesn't. For mutable objects like lists, this makes a big difference, because __iadd__ can change self in-place and then return it, meaning you end up assigning the same object back to a that was already there. But for immutable objects, there's no difference.

Get the return value from a function in a class in Python

I am trying to simply get the value out of my class using a simple function with a return value, I'm sure its a trivial error, but im pretty new to python
I have a simply class set up like this:
class score():
#initialize the score info
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
# Score Info
def setScore(num):
self.score = num
# Enemy Info
def getEnemies():
return self.num_enemies
# Lives Info
def getLives():
return self.getLives
etc.....
Than I create an instance of the class as such:
scoreObj = score()
for enemies in range(0, scoreObj.getEnemies):
enemy_sprite.add(enemy())
I get the error saying that an integer is expected, but it got an instancemethod
What is the correct way to get this information?
Thanks!
scoreObj.getEnemies is a reference to the method. If you want to call it you need parentheses: scoreObj.getEnemies().
You should think about why you are using a method for this instead of just reading self.num_enemies directly. There is no need for trivial getter/setter methods like this in Python.
The first parameter for a member function in python is a reference back to the Object.
Traditionally you call it "self", but no matter what you call the first parameter, it refers back to the "self" object:
Anytime I get weird errors about the type of a parameter in python, I check to see if I forgot the self param. Been bit by this bug a few times.
class score():
#initialize the score info
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
# Score Info
def setScore(self, num):
self.score = num
# Enemy Info
def getEnemies(self):
return self.num_enemies
# Lives Info
def getLives(foo): #foo is still the same object as self!!
return foo.num_lives
#Works but don't do this because it is confusing
This code works:
class score():
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
def setScore(self, num):
self.score = num
def getEnemies(self):
return self.num_enemies
def getLives(self):
return self.getLives
scoreObj = score()
for enemy_num in range(0, scoreObj.getEnemies()):
print enemy_num
# I don't know what enemy_sprite is, but
# I commented it out and just print the enemy_num result.
# enemy_sprite.add(enemy())
Lesson Learned:
Class functions must always take one parameter, self.
That's because when you call a function within the class, you always call it with the class name as the calling object, such as:
scoreObj = score()
scoreObj.getEnemies()
Where x is the class object, which will be passed to getEnemies() as the root object, meaning the first parameter sent to the class.
Secondly, when calling functions within a class (or at all), always end with () since that's the definition of calling something in Python.
Then, ask yourself, "Why am I not fetching 'scoreObj.num_lives' just like so instead? Am I saving processing power?" Do as you choose, but it would go faster if you get the values directly from the class object, unless you want to calculate stuff at the same time. Then your logic makes perfect sense!
You made a simple mistake:
scoreObj.getEnemies()
getEnemies is a function, so call it like any other function scoreObj.getEnemies()

Python: Pokemon battle (classes, functions)

I just started learning python and I am hoping you guys can help me comprehend things a little better. If you have ever played a pokemon game for the gameboy you'll understand more as to what I am trying to do. I started off with a text adventure where you do simple stuff, but now I am at the point of pokemon battling eachother. So this is what I am trying to achieve.
Pokemon battle starts
You attack target
Target loses HP and attacks back
First one to 0 hp loses
Of course all of this is printed out.
This is what I have for the battle so far, I am not sure how accurate I am right now. Just really looking to see how close I am to doing this correctly.
class Pokemon(object):
sName = "pidgy"
nAttack = 5
nHealth = 10
nEvasion = 1
def __init__(self, name, atk, hp, evd):
self.sName = name
self.nAttack = atk
self.nHealth = hp
self.nEvasion = evd
def fight(target, self):
target.nHealth - self.nAttack
def battle():
print "A wild appeared"
#pikachu = Pokemon("Pikafaggot", 18, 80, 21)
pidgy = Pokemon("Pidgy", 18, 80, 21)
pidgy.fight(pikachu)
#pikachu.fight(pidgy)
Full code here: http://pastebin.com/ikmRuE5z
I am also looking for advice on how to manage variables; I seem to be having a grocery list of variables at the top and I assume that is not good practice, where should they go?
If I was to have fight as a instance method (which I'm not sure I would), I would probably code it up something like this:
class Pokemon(object):
def __init__(self,name,hp,damage):
self.name = name #pokemon name
self.hp = hp #hit-points of this particular pokemon
self.damage = damage #amount of damage this pokemon does every attack
def fight(self,other):
if(self.hp > 0):
print("%s did %d damage to %s"%(self.name,self.damage,other.name))
print("%s has %d hp left"%(other.name,other.hp))
other.hp -= self.damage
return other.fight(self) #Now the other pokemon fights back!
else:
print("%s wins! (%d hp left)"%(other.name,other.hp))
return other,self #return a tuple (winner,loser)
pikachu=Pokemon('pikachu', 100, 10)
pidgy=Pokemon('pidgy', 200, 12)
winner,loser = pidgy.fight(pikachu)
Of course, this is somewhat boring since the amount of damage does not depend on type of pokemon and isn't randomized in any way ... but hopefully it illustrates the point.
As for your class structure:
class Foo(object):
attr1=1
attr2=2
def __init__(self,attr1,attr2):
self.attr1 = attr1
self.attr2 = attr2
It doesn't really make sense (to me) to declare the class attributes if you're guaranteed to overwrite them in __init__. Just use instance attributes and you should be fine (i.e.):
class Foo(object):
def __init__(self,attr1,attr2):
self.attr1 = attr1
self.attr2 = attr2v
You don't need the variables up the top. You just need them in the init() method.
The fight method should return a value:
def fight(self, target):
target.nHealth -= self.nAttack
return target
You probably want to also check if someone has lost the battle:
def checkWin(myPoke, target):
# Return 1 if myPoke wins, 0 if target wins, -1 if no winner yet.
winner = -1
if myPoke.nHealth == 0:
winner = 0
elif target.nHealth == 0:
winner = 1
return winner
Hope I helped.
I am only going to comment on a few obvious aspects, because a complete code review is beyond the scope of this site (try codereview.stackexchange.com)
Your fight() method isn't saving the results of the subtraction, so nothing is changed. You would need to do something like this:
def fight(target, self):
target.nHealth -= self.nAttack
# check if target is dead now?
I might even recommend not imposing a modification on your target directly. It may be better if you can call an attack(power) on your target, and let it determine how much damage is done. You can then check if the target is dead yet. Ultimately I would think you would have some "dice" object that would determine the outcomes for you.
As for globals... just stop using them. It is a bad habit to have them unless you really have a good reason. Have functions that return results to the caller, which you then make use of:
def func(foo):
return 'bar'
You can however have a module of constants. These are a bunch of values that don't change for the life of the application. They are merely variables that provide common values. You might create a constants.py and have stuff like:
UP = "up"
DOWN = "down"
DEAD = 0
...
... And in your other modules you do:
from constants import *

Categories

Resources