Python: Pokemon battle (classes, functions) - python

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 *

Related

(Classname) has no attribute (attribute)

I'm trying to create a DnD style dungeon crawler game. I'm using the 5E SRD and other publicly available information as the base for my characters and gameplay.
Currently I'm working on the character generator, and it seems to be going well, but I've hit a roadblock when trying to assign the racial bonuses. I've got the races set up as their own subclasses, each with it's unique bonuses. When I try to assign the appropriate bonuses based on the character's race I get a (Classname)has no attribute (attribute) error.
python
class Race:
def __init__(self, race):
self.name = race
self.racial_str_bonus = 0
self.racial_char_bonus = 0
class Dragonborn(Race):
def __init__(self):
super()
self.name = "Dragonborn"
self.racial_str_bonus = +2
self.racial_char_bonus = +1
def get_racial_bonus(race):
race = race
racial_str_bonus = 0
racial_char_bonus = 0
if race == "Dragonborn":
racial_str_bonus = Dragonborn.racial_str_bonus
racial_char_bonus = Dragonborn.racial_char_bonus
return racial_str_bonus, racial_char_bonus
class BaseCharacter:
def __init__(self, racial_str_bonus, racial_char_bonus):
self.racial_str_bonus = racial_str_bonus
self.racial_char_bonus = racial_char_bonus
#classmethod
def generate_player_character(cls):
cls.race = input("Race: ")
get_racial_bonus(cls.race)
BaseCharacter.generate_player_character()
What I'm looking for is something along the line of:
'''
Race: Dragonborn
print(my_player_char.racial_str_bonus)
2
'''
Where am I goofing up?
Thanks, everyone for the feedback. In cleaning up the code to get it minimally reproducible, I figured out the issue. Per Jonrsharpe's note, I corrected the inhertance invocation to 'super().init(self)'. Once that was correct, I realized that the way they had been defined, I had to include parentheses in the property call: "Dragonborn().racial_str_bonus".
Thanks again, and I will remember to improve my submissions in the future.

Python dictionary nested within method auto-executes all values (methods) when outer method is called

I'm working on a simple skeleton for a game, and in an effort to try and be more "pythonic", I'm using objects/classes/dictionaries to try and capture all my actions/behaviors (as methods over functions, etc).
For some reason, every time I execute the method 'act' within the class "Player", the dictionary embedded within act runs all of its values (which are, in turn, methods from within the same instance of the class "Player"). In other words, the player chooses "attack, heal, and flee" every time, all at once, before being prompted.
I'm sure there's a simple explanation, but I've been looking for hours and can't find another example of someone's dictionary auto-running all the methods embedded within. Can you help?
Thanks!
- Jake
from random import randint
### BEGIN ALL CLASSES HERE
# To be used for all game objects (living and non-living)
class gameObject(object):
def __init__(self, name):
self.name = name
# To be used for all characters who can act in some way/be killed/change
class livingThing(gameObject):
def __init__(self, name, HP=1):
self.name = name
self.HP = HP
# The playable character(s)
class Player(livingThing):
def __init__(self,name="The Stranger", HP=4, MP=5, strength=1, intellect=1, spirit=1, luck=5, gil=6):
self.name = name
self.HP = HP
self.MP = MP
self.gil = gil
self.strength = strength
self.intellect = intellect
self.spirit = spirit
self.luck = luck
def act(player, enemy):
actions = {
"attack" : player.attack(enemy),
"heal" : player.heal(enemy),
"flee" : player.flee()
}
#Takes input from the player
decision = input("What would you like to do? ")
if decision.lower() in actions:
actions[decision.lower()]
else:
print("That didn't work! Try again.")
# Prints both player and enemy HP
def printHP(player, enemy):
print("{0}'s' HP: {1} \n{2}'s HP: {3}".format(player.name, player.HP, enemy.name, enemy.HP))
# Allows the player to attack an enemy (currently functional)
def attack(player, enemy):
enemy.HP -= player.strength
print("You strike {0} for {1} damage!".format(enemy.name, player.strength))
player.printHP(enemy)
# Allows the player to heal a certain amount of health based on its "spirit" stat (currently functional)
def heal(player, enemy):
healed = randint(0, player.spirit)
player.HP += healed
print("You've healed for {0}!".format(healed))
player.printHP(enemy)
#Allows the player to attempt to run away
def flee(player):
randluck = randint(0, player.luck)
if randluck > 3:
print("You successfully escaped!")
return player.HP
else:
print("You weren't able to escape!")
# Anything that can act with/against the player
class Actor(livingThing):
def __init__(self, name="Unknown Entity", HP=10, MP=2, gil=3):
self. name = name
self.HP = HP
self.MP = MP
self.gil = gil
### END ALL CLASSES ###
### DICTIONARIES CONTAINING ACTIONS ###
### CHARACTERS ###
fighter = Player()
monster = Actor()
fighter.act(monster)
I see the problem. When you are executing Python code, and you have a dictionary as you do, Python evaluates the dictionary fully. If you wanted your values (in your key:value) pairs to be the results of those methods, this is surely one way to do it.
In your case, what you can do is reference the function itself, and not invoke it. You can do this by getting rid of the parentheses, like this:
player.attack
instead of
player.attack()
Then, to call the function you can do something like
actions[decision.lower()](enemy)
Since one of your functions, flee, doesn't accept any parameters, you could give flee a parameter that you simply don't use in the function. If you were designing many many methods that your player can act with, then one strategy would be to give them all only named parameters, like this:
def f1(enemy=None,something=None,foo=None):
if enemy is None:
raise Exception("enemy cannot be None")
#process_enemy
If however, you also have a very high amount of parameters, then you could do this:
def attack(**kwargs):
#kwargs is a dictionary of parameters provided to the function
enemy = kwargs.get('enemy',None)
if enemy is None:
raise Exception("enemy cannot be None")
def eat(**kwargs):
food = kwargs.get('food',None)
if enemy is None:
raise Exception("food cannot be None")
attack(enemy="someenemyobject")
eat(food="somefoodobject")
attack() # raises Exception
attack(food="somefoodobject") # raises Exception
food(enemy="someenemyobject") # raises Exception
food(food="somefoodobject",enemy="someenemyobject") # does not raise Exception

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.

Programming a function that saves and returns values in python

I am currently experimenting with Python and programming a little text-adventure. In my game the player has certain properties like hp, attack damage and inventory slots for items.
I want to be able to call these properties from everywhere in my code. For that I created a function that receives three values:
"edit": to specify if a variable should be edited
"info_id": to specify which variable should be accessed
"value": the new value for the variable
This is what it looks like in my code:
def player_info(edit, info_id, value):
if edit == 1:
##function wants to edit value
if info_id == 1:
player_hp = value
print ("Assigned hp to: ", player_hp)
##the "prints" are just to check if the asignments work -> they do
return player_hp
elif info_id == 2:
player_attack = value
print ("Assigned attack to: ", player_attack)
return player_attack
elif info_id == 3:
item_1 = value
return item_1
elif info_id == 4:
item_2 = value
return item_2
elif info_id == 5:
item_3 = value
elif edit == 0:
##function wants to retrieve value
if info_id == 1:
return player_hp
elif info_id == 2:
return player_attack
elif info_id == 3:
return item_1
elif info_id == 4:
return item_2
elif info_id == 5:
return item_3
There are actually 10 item slots (going up to info_id==13) but they are all the same anyway.
I define all variables at the beginning of my code:
player_info(1,1,20)
player_info(1,2,5)
n=3
while n<=13:
player_info(1,n,0)
n=n+1
##items are not fully implemented yet so I define the item slots as 0
The definition works, I can tell because of the control "print" I implemented in the code. Still when I call a variable, e.g. the health like this:
player_info(0,1,0)
I get an error:
local variable 'player_hp' referenced before assignment
Does the function not save the variable properly? Or what is the problem?
Is there a better way to save variables? Are global variables the way to go in this case?
Thanks for the help!
First of all, your error is caused because of retrieving a variable that is not assigned - that just doesn't work. When you edit player_hp, it's not stored anywhere. you are returning it to the function that called it and not assigning it to anything. It just gets lost.
Second of all, you should really indent with 4 spaces (or tabs) - it's much more readable than 2 spaces. Not only for you, but for anyone trying to help too.
And lastly, the proper way to go about this would be to learn about classes. Global variables should never be used in python, only in special cases, or when you are learning, but just skip ahead to the class.
You should create something like
class Player:
def __init__(self):
self.hp = 20 # or another starting hp
self.attack = 3 # or another starting attack
self.inventory = []
Then you can just create an instance of Player class and pass it to the functions where it's relevant
player1 = Player()
print(player1.hp) # Prints out player's hp
player1.hp -= 5 # Remove 5 hp from the player. Tip: Use method to do this so that it can check if it reaches 0 or max etc.
player1.inventory.append("axe")
print(player1.inventory[0]) # Prints out axe, learn about lists, or use dictionary, or another class if you want this not to be indexed like a list
You asked, "Does the function not save the variable properly?"
In general, Python functions do not save their state. The exception is functions that use the yield statement. If you write a function like this
def save_data(data):
storage = data
and call it like this
save_data(10)
you will not be able to get the value of storage later. In Python, if you need to save data and retrieve it later, you would normally use classes.
Python classes allow you do do things like this:
class PlayerData(object):
def __init__(self, hp=0, damage=0):
self.hp = hp
self.damage = damage
self.inventory = list()
self.max_inventory = 10
def add_item(self, item):
if len(self.inventory) < self.max_inventory:
self.inventory.append(item)
def hit(self, damage):
self.hp -= damage
if self.hp < 0:
self.hp = 0
def attack(self, other):
other.hit(self.damage)
if __name__ == '__main__':
player1 = PlayerData(20, 5)
player2 = PlayerData(20, 5)
player1.attack(player2)
print player2.hp
player1.add_item('sword')
player1.add_item('shield')
print player1.inventory
Output
15
['sword', 'shield']
This really only scratches the surface of how to use classes. In a more complete implementation, you might have an Item base class. Then you might create Sword and Shield classes that inherit from Item.

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.

Categories

Resources