Attempting OOP for Python RPG - python

I am trying to learn Python OOP and have been struggling to understand scope, passing values and how to encapsulate. My earlier attempts have rapidly become spaghetti code (likely because my only programming experience was on 8 bit BASIC 40 years ago), and thus, I am trying classes and objects.
Here it is:
import random
class Player:
def __init__(self, weapon, health):
self.weapon = weapon
self.health = health
def reduce_health(self, amount):
self.health -= amount
def check_dead(self):
if self.health > 0:
return "Player not dead"
else:
return "Player dead"
def calculate_damage(self, weapon):
damage_inflicted = random.randint(3, 15) + weapon
return damage_inflicted
def fight(self):
player.reduce_health(self.calculate_damage(self.weapon))
print(player.health)
player.check_dead()
player = Player(1, 15)
player.fight()
The calculate_damage function is flagged as being 'static' and the check_dead function seemingly has no effect at all.
I need some hints to get my footing. Thanks.

There are a few errors in your code.
First, you are using the player variable in the fight function. This works, because you've called the instance of the Player class player, but it wouldn't if you would give it any other name. To fix your issue, use self instead ofplayer in all class functions. The self argument is automatically passed in by python and does always refer to the object on which the the function is called.
Second, the check_deadfunction does nothing because it only returns a value. There is no attribute set or value printed out in this function. In order to make it work, you need to print the value, either inside the function or outside, with the return value of the function.
This code should work:
import random
class Player:
def __init__(self, weapon, health):
self.weapon = weapon
self.health = health
def reduce_health(self, amount):
self.health -= amount
def check_dead(self):
if self.health > 0:
return "Player not dead"
else:
return "Player dead"
def calculate_damage(self, weapon):
damage_inflicted = random.randint(3, 15) + weapon
return damage_inflicted
def fight(self):
self.reduce_health(self.calculate_damage(self.weapon))
print(player.health)
print(self.check_dead())
player = Player(1, 15)
player.fight()
In this code, we print the aliveness of the player directly instead of returning it:
import random
class Player:
def __init__(self, weapon, health):
self.weapon = weapon
self.health = health
def reduce_health(self, amount):
self.health -= amount
def check_dead(self):
if self.health > 0:
print("Player not dead")
else:
print("Player dead")
def calculate_damage(self, weapon):
damage_inflicted = random.randint(3, 15) + weapon
return damage_inflicted
def fight(self):
self.reduce_health(self.calculate_damage(self.weapon))
print(player.health)
self.check_dead()
player = Player(1, 15)
player.fight()

The problem that you are having is probably due to the way you refer to the functions of check_dead(), reduce_health() and the variable health in fight(). You are calling out the player class instance, instead of the instance the function is running in, that meant that, if you were to change the name of the player class instance to something different, it would spill out an error that "player is not defined".
Why the check_dead() is not working could also be because it doesn't print out the values, it just returns them, and it you don't do anything with them when it returns them to the calling function it just scraps them.
I recommend checking the code if it is spelled right and also replace every reference in the class to self. And also make the check_dead() function wrapped in print in the fight() function. That would end up looking something like this:
import random
class Player:
def __init__(self, weapon, health):
self.weapon = weapon
self.health = health
def reduce_health(self, amount):
self.health -= amount
def check_dead(self):
if self.health > 0:
return "Player not dead"
else:
return "Player dead"
def calculate_damage(self, weapon):
damage_inflicted = random.randint(3, 15) + weapon
return damage_inflicted
def fight(self):
self.reduce_health(self.calculate_damage(self.weapon))
print(self.health)
print(self.check_dead())
player = Player(1, 15)
player.fight()
If this still doesn't work it's propablly of your running environment or your ide/editor.
Also:
Static means, that the function standalone
If a function is static it doesn't use any of the functions/variables the class gives it (which are commonly gotten by using self.[function/variable name]). By that it is independent and it could be written outside of the class and it would still work as intended.

Related

Using an "if" statement in a class function to level-up Player class instance

I am working on creating a class for a Player (user) in a text-based Python game. The level is expressed as the level function in the Player class. I include a level_up() function for when users successfully complete a quest.
Completing a quest should increase the user's level and return the user's new level. However, when I run the program, the level is not increased by 1, nor does the function return the user's level.
I appreciate any feedback, suggestions, and recommendations.
class Player(object):
def __init__(self, name:str, health:int, level:int, strength:int, quest):
self.name = name
self.health = health
self.level = level
self.strength = strength
self.quest = False
def __str__(self):
return "%s stats:\n HP %s\n Level %s\n Strength %s" % (self.name, self.health, self.level, self.strength)
def level_up(self):
if self.quest is True:
self.level += 1
return "You have leveled up. You are level %s! Congratulations." % (self.level)
else:
pass
user = Player('User_name', 1, 1, 1, False)
print(user)
user.quest = True
user.level_up()
You return the "you have leveled up" string but you never print it. You have to print it for it to be visible:
def level_up(self):
if self.quest is True:
self.level += 1
print("You have leveled up. You are level %s! Congratulations." % (self.level))
Or
# Only do this if level_up returns a string instead of printing one
print(user.level_up())

How to call a variable from one function into another function?

Note: this is a very long question.
I'm practicing python by making a game. I need to take a variable from another function (that I called spawn()) and use in a different function (that I called damage_taken()).
This is the file enemy.py. Its main job is to spawn an enemy:
import random
import player
class Enemy(object):
def types(self):
type = ["slime", "ghost", "demon"]
enemy = random.choice(type)
return enemy
class Slime(Enemy):
def types(self):
colour = ["red", "green", "blue"]
type = random.choice(colour)
return type
def health(self):
health = random.randint(1,5)
return health
class Ghost(Enemy):
def types(self):
form = ["spirit", "spectre", "phantom"]
type = random.choice(form)
return type
def health(self):
health = random.randint(10,30)
return health
class Demon(Enemy):
def types(self):
being = ["demon", "hell hound", "wendigo"]
type = random.choice(being)
return type
def health(self):
health = random.randint(15,35)
return health
This is the important code. I need to take the variable health from this function and use it in another function.
def spawn():
enemy = Enemy()
bad = enemy.types()
if bad == "slime":
slime = Slime()
target = slime.types()
health = slime.health()
print(f"A {target} {bad} has appeared. It has {health} HP")
return health
elif bad == "ghost":
ghost = Ghost()
target = ghost.types()
health = ghost.health()
print(f"A {target} has appeared. It has {health} HP")
return health
elif bad == "demon":
demon = Demon()
target = demon.types()
health = demon.health()
print(f"A {target} has appeared. It has {health} HP")
return health
This is where i am struggling. I am trying to take the variable health from the function spawn() and use it in the following function. However, it keeps on telling me health does not exist. How do i take a variable from another function and use it in this function.
def damage_taken():
spawn()
health = spawn.health - player.fight()
return health
damage_taken()
The code
spawn.health
is my failed attempt at trying to call the variable into the function.
The code:
player.fight()
is calling a function from a different file called player.py. Its main purpose is to deal with mechanics related to the player such as creating the character and deciding how much damage they deal.
If I well understood your question you just need to assign the return value from your spawn function into a variable:
def damage_taken():
spawn_health = spawn()
health = spawn_health - player.fight()
return health
I'm guessing that you would actually rather the spawn function, return the enemy object, rather than the health of the enemy, so I would recommend the following:
def spawn():
enemy = Enemy()
bad = enemy.types()
if bad == "slime":
enemy = Slime() # each of these now is assigned to the same variable name, "enemy"
target = slime.types()
health = slime.health()
print(f"A {target} {bad} has appeared. It has {health} HP")
elif bad == "ghost":
enemy = Ghost()
target = ghost.types()
health = ghost.health()
print(f"A {target} has appeared. It has {health} HP")
elif bad == "demon":
enemy = Demon()
target = demon.types()
health = demon.health()
print(f"A {target} has appeared. It has {health} HP")
return enemy
def damage_taken():
enemy = spawn()
health = enemy.health - player.fight()
return health
damage_taken()
now, there is still the problem that the enemy object will be entirely dropped after damage_taken() is called. If you want the enemy to persist, you likely will want to initialize both player and enemy outside of the scope of the damage_taken function. Something more like this:
enemy = spawn()
damage_taken(enemy)

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

How do you take an input and call an instance of a class based on the input?

I am currently developing a short text-based adventure so I can learn how to use Classes within Python. As part of this, I am trying to create a combat system where the player could choose an NPC to attack.
The aim is that the player can enter the name of the NPC and the weapon they want to use. A method in the target's class will then be called, to lose health based on the damage of the weapon.
My current code is below:
class npc:
def __init__(self, name, alliance):
self.name = name
self.alliance = alliance
def loseHealth(self, health, dmg):
self.dmg = dmg
self.health = self.health - dmg
def usePotion(self, health, pType):
if pType == "great":
self.health = min(self.health + 50,self.maxHealth)
elif pType == "normal":
self.health = min(self.health + 25,self.maxHealth)
else:
pass
def attack(self, target, weaponDmg):
if target in npcList:
target.loseHealth(self.health, weaponDmg)
class human(npc):
maxHealth = 100
health = 100
def __init__(self, name, alliance):
super().__init__(name, alliance)
class orc(npc):
maxHealth = 200
health = 200
def __init(self, name, alliance):
super().__init__(name, alliance)
weaponDmg = {'sword':10,'axe':20}
alice = human("alice","good")
bob = orc("bob","evil")
npcList = [alice, bob]
target = input("Enter Target:")
weapon = input("Enter weapon:")
for x in range(3):
alice.attack(target,weaponDmg[weapon]) #using alice temporarily until I have a person class sorted
print(target.health)
The simple and pythonic answer is to use a dict of NPCs keyed by name, the same way you’re already doing it with weapons:
npcs = {‘alice’: alice, ‘bob’: bob}
target = input("Enter Target:")
weapon = input("Enter weapon:")
for x in range(3):
alice.attack(npcs[target], weaponDmg[weapon])
print(target.health)
And if you want to look up the attacking NPC by user-supplied name as well as the attackee, you can do the same thing there:
npcs[attacker].attack(npcs[target], weaponDmg[weapon])
If you really want to do this inside the attack method you can keep passing in target as a name (string) and do this:
if target in npcs:
npcs[target].loseHealth(self.health, weaponDmg)
... but that probably isn’t a very good design. It means you’re sharing a global variable, and your NPC objects all “know” about that global dict and all the NPCs in it, which doesn’t seem like part of their responsibility.
You can make this a little less repetitive by creating the dict with a comprehension:
npcs = {npc.name: npc for npc in (alice, bob)}
... or by just creating them directly in the dict instead of in variables that you’re probably never going to otherwise use:
npcs = {}
npcs[‘alice’] = human("alice","good")
npcs[‘bob’] = orc("bob","evil")
You can call a method on an instance by using getattr, here is an example:
>>> class Test:
... def my_method(self, arg1, arg2):
... print(arg1, arg2)
...
>>> t = Test()
>>> getattr(t, 'my_method')('foo', 'bar')
foo bar

AttributeError: type object ' ' has no attribute ' '

I already asked something similar on the site, but I can't get over this error. The code that I'm using is:
class Enemy:
# Base class to create all future enemies
def __init__(self, name, hp, dmg, desc):
self.name = name # Enemy name
self.hp = hp # Enemy hp
self.dmg = dmg # Enemy damage
self.desc = desc # Enemy description (Will make sense later)
def is_alive(self):
return self.hp > 0
The code above is used to define all future enemies, as I stated on with #. Then, I created three enemies for testing, as my game will generate random encounters from a list. However, I'm only going to list one because the error happens with any enemy:
class Goblin(Enemy):
def __init__(self):
super().__init__(name = "Goblin",
hp = 40,
dmg = 33,
desc = "An ordinary goblin runs in your direction!")
Then, as the enemies do, the rooms of the dungeon also have a template class and a list for random purposes, but as I did before, I will only list one because it's the one that is causing errors to me:
class Room:
# Template to define all future rooms
def __init__(self, intro):
self.intro = intro
class VsRoom(Room):
# Room with an enemy
duelist = None
def __init__(self):
self.duelist = duelist = random.choice(Enemy_List)
super().__init__(intro = "There's an enemy here! \n%s" %(duelist.desc))
And then there's the "def" to run the game (currently just for testing):
def print_room():
# Tells the player the type of room they are
print("You enter the next room...")
print()
gen_room = random.choice(Rooms)() # ("Rooms" is a list)
print(gen_room.intro)
if gen_room == VsRoom:
combat() # No need to list the combat() definition, I think.
But when I run, I get this:
AttributeError: type object 'Goblin' has no attribute 'desc'
I know that AttributeError is quite easy to find here, but it seems that none of the existing questions suits me. If anybody knows why the code fails, please tell me.

Categories

Resources