Changeable function call from dict - python

I'm relatively new with a decent amount of experience and I'm trying to make a text based adventure, I'm making a fighting system and wish to have enemy's that have different abilities. Instead of recreating the fight for a different enemy every time, I'm trying to use interchangeable dictionaries for each enemy. My goal is to create a function call that varies depending on what enemy is in the fight without getting into objects. I have an example below and would like to know if there is a way to do something similar.
wolf = {'ability': 'bite'}
bear = {'ability': 'claw'}
enemy = {}
def claw():
print('stuff')
def bite():
print('different stuff')
def use_ability():
enemy = wolf
enemy['ability']()
use_ability()

In python functions are first class objects. You can just use them as values in your dictionary.
wolf = {'ability': bite}
bear = {'ability': claw}
However be careful as there is no forward referencing in python. So make sure you define your functions before you assign them to a dictionary.
def claw():
print('stuff')
def bite():
print('different stuff')
wolf = {'ability': bite}
bear = {'ability': claw}
def use_ability():
enemy = wolf
enemy['ability']()
use_ability()

You can do it:
def claw():
print('stuff')
def bite():
print('different stuff')
wolf = {'ability': bite}
bear = {'ability': claw}
def use_ability(enemy):
enemy['ability']()
use_ability(wolf)
# different stuff
It really doesn't mean you should do it this way, though.
Use Object-Oriented programming. If you only want to use dicts and functions, you probably should write Javascript instead.

I can't help myself but to make a little program explaining how it should be done in an Object Orientated Language:
You should look up some guides how OOP-Languages work, because when making a game it will be really helpfull if you do it that way
http://www.python-course.eu/object_oriented_programming.php
# This is the SUPERCLASS it holds functions and variables
# that all classes related to this object use
class Enemy(object):
# Here we initialise our Class with varibales I've given an example of how to do that
def __init__(self, HP, MAXHP, ability):
self.HP = HP
self.MAXHP = MAXHP
self.ability = ability
# This function will be used by both Bear and Wolf!
def use_ability(self):
print(self.ability)
# This is our Wolf Object or Class
class Wolf(Enemy):
# Here we init the class inheriting from (Enemy)
def __init__(self, ability, HP, MAXHP):
super().__init__(HP, MAXHP, ability)
# Here we call the superfunction of this Object.
def use_ability(self):
super().use_ability()
# Same as Wolf
class Bear(Enemy):
def __init__(self, ability, HP, MAXHP):
super().__init__(HP, MAXHP, ability)
def use_ability(self):
super().use_ability()
# How to init Classes
wolf_abilities = 'bite'
w = Wolf(wolf_abilities, 10, 10)
bear_abilities = 'claw'
b = Bear(bear_abilities, 10, 10)
# How to use methods from Classes
b.use_ability() # This will print 'bite'
w.use_ability() # This will print 'claw'

Related

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

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.

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.

Python - Can't create instances from a list or random instances

I am making a small text RPG to help me learn the Python language, and I am trying to create multiple instances of a class from a list of names.
I have a class of enemies (Named: Enemy) and would like to create between 1 and 3 "goblin" enemies at at time.
class Enemy:
def __init__(self, health):
self.health = health
How I have approached the problem so far is to use a for loop to run through letters 'a', 'b', and 'c' and append a list of enemies.
for i in ['a', 'b', 'c']:
enemylist.append('goblin' + (i))
This gives me a list of three goblins:
['goblina', 'goblinb', 'goblinc']
Now I would like to take each newly appended "goblin" in the list and create an instance of the enemy class using that goblin's name (Example: "goblina = enemy(10)"... "goblinb = enemy(10)...)
But when I try to create an instance using any number of ways including the following (which is probably the absolutely wrong way to):
for i in range (1, 3):
enemylist[i] = enemy(10)
All that I get is a single instance named enemylist[i].
Can someone please help me. Like I said, I am new to the language so please be gentle with the explanation but I am a fast learner and willing to read and research.
I spent the better part of 2 days (on and off) trying to get to the bottom of this and could not find a solution that worked.
Perhaps it would make more sense to keep the enemy's name as a member of the Enemy class as well:
import random
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def create_enemies():
enemies = []
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies.append(Enemy(name, health))
It's hard to make a suggestion without knowing how you're going to use the enemies. If you want to be able to look an enemy up by name, a dictionary would be a better data structure in which to store them:
def create_enemies():
enemies = {} # Initialize empty dict
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies[name] = Enemy(name, health)
return enemies
def main()
enemies = create_enemies()
ga = enemies['goblina']
I think adding a name to the class is good, but creating a dict with a key that's equal to the same name, but also storing the name and health as a dict value seems a bit redundant (as shown in answer above).
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def __repr__(self):
return self.name
This will give you a name to access. You can still store each instance of the enemy class in a list, using list comprehension if you like (or any other list creation):
for i in enemylist:
i = Enemy(i, 10)
enemies.append(i)
Now you have a list enemies, consisting of three instantiated objects of the class Enemy, that have a name that can be accessed. For instance
goblina.__name__
will return goblina and
isintance(goblina, Enemy)
will return True.

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