I'm designing a simple role-playing game (similar to World of Warcraft, League of Legends, etc.) where you basically have a hero or a champion who fights with wild creatures and other heroes/champions.
This hero can then level up by gaining experience points from fights, and each level grants him a skill point, which allows him to level up one of his skills.
Each skill has its own purpose and does its own thing, an example of a skill would be:
class DamageAura(Skill):
"""
This skill increases damage dealt by your hero,
as well as deals damage back to the ones who attack your hero.
"""
name = 'Damage Aura'
description = '...'
# These methods are called when the actual event happens in game
def unit_attack(self, target, damage):
target._health -= (self.level / 100) * damage
def unit_defend(self, attacker, damage):
attacker.deal_damage((self.level / 100) * damage)
Now I would like the players of the game to be able to make their own heroes, and I need it to be simple.
The problem is, I don't want to do "bad" code simply to make creating heroes easier.
Here's what a hero class would ideally look like:
class Warrior(Hero):
name = 'Warrior'
description = 'Ancient Warrior is thirsty for blood.'
maximum_level = 30
agility = 20
intelligence = 10
strength = 30
class DamageAura(Skill):
name = 'Damage Aura'
description = '...'
def unit_attack(...):
...
class StrongerWeapons(Skill):
name = 'Stronger Weapons'
...
Now the problem is, I'd like the name variable to be an attribute of the actual hero (f.e.Warrior) instance, not a class variable.
Is there an easy, yet safe way to merge these two?
Basically the class variables would define the default values for a hero, but if I wanted to create a Warrior instance with a custom name, I could do so by simply doing warrior1 = Warrior(name='Warrior God', maximum_level=40)
This is what the class would look like if I didn't have to worry about subclasses or ease-of-creation:
class Hero(object):
def __init__(self, name, description='', maximum_level=50, skill_set=None, ...):
self.name = name
self.description = description
self.maximum_level = maximum_level
self.skill_set = skill_set or []
...
EDIT:
I forgot to mention; each player could be playing the exact same hero, Warrior in this case, so that's why Warrior is a class, not just an instance of Hero. They would all have the same skill set, but unique levels, etc.
EDIT 2:
I'm having no problem with Python classes or anything, normally I would NOT make the skills nested classes, or anything similar, but in this case I must invest a lot on ease of creating classes, so even the "noobs" can do it without knowing rarely any python at all.
You could allow the end users to create their Hero definitions as a dictionary:
my_hero = dict(name='Warrior'.
description='Ancient Warrior is thirsty for blood.',
maximum_level=30,
...)
Then you can easily create an instance like:
hero = Hero(**my_hero)
(If the syntax is unfamiliar, see What does ** (double star) and * (star) do for parameters?)
For both DamageAura and Warrior, it seems more appropriate for them to be instances than subclasses of Skill and Hero respectively.
Alternatively, consider letting the user create their characters outside of your code (e.g. in JSON, XML, YAML, ...), then parse the files and create appropriate instances from the input data.
Related
I'm trying to create a Python version of Monopoly. I have a separate class that I am using to shuffle and track the Chance and Community Chest cards. The cards are stored in lists chest_cards and chance_cards.
def __init__(self):
self.chance = random.shuffle(chance_cards)
self.chest = random.shuffle(chest_cards)
self.chance_count = 0
self.chest_count = 0
def chance(self):
self.chance_count += 1
return self.chance[self.chance_count - 1]
In my main code, I am just running
p = cards()
print (p.chance())
to test my code, but I get TypeError: 'NoneType' object is not callable for the print line.
Any ideas? Or do you need to see more code?
TIA
EDIT: Here is the full cards class, if it helps
import random
global chance_count
global chest_count
class cards:
global chest_cards
global chance_cards
chest_cards = (["Go to Jail","Get Out of Jail Free","Advance to Go (Collect $200)",
"Bank error in your favor (Collect $200)","Doctor's fee (Pay $50)",
"From sale of stock you get $50", "Grand Opera Night — Collect $50 from every player",
"Holiday Fund matures (Collect $100)", "Income tax refund (Collect $20)",
"It is your birthday (Collect $10)","Life insurance matures (Collect $100)",
"Pay hospital fees of $100", "Pay school fees of $150", "Receive $25 consultancy fee",
"You are assessed for street repairs – $40 per house – $115 per hotel",
"You have won second prize in a beauty contest (Collect $10)", "You inherit $100"])
chance_cards = (["Go to Jail","Get Out of Jail Free","Advance to Go (Collect $200)",
"Advance to Illinois Ave — If you pass Go, collect $200",
"Advance to St. Charles Place – If you pass Go, collect $200",
"Advance token to nearest Utility. If unowned, you may buy it from the Bank. If owned, throw dice and pay owner a total ten times the amount thrown.",
"Advance token to the nearest Railroad and pay owner twice the rental to which he/she is otherwise entitled. If Railroad is unowned, you may buy it from the Bank.",
"Bank pays you dividend of $50", "Go Back 3 Spaces",
"Make general repairs on all your property – For each house pay $25 –For each hotel $100",
"Pay poor tax of $15","Take a trip to Reading Railroad – If you pass Go, collect $200",
"Take a walk on the Boardwalk – Advance token to Boardwalk",
"You have been elected Chairman of the Board – Pay each player $50",
"Your building and loan matures — Collect $150", "You have won a crossword competition (Collect $100)"])
def __init__(self):
self.chance = random.shuffle(chance_cards)
self.chest = random.shuffle(chest_cards)
self.chance_count = 0
self.chest_count = 0
def chance(self):
self.chance_count += 1
return self.chance[self.chance_count - 1]
when you create an instance of your class (assuming it's class cards: before your __init__ function), you create an object with a method called chance, but then during __init__ you overwrite this method with an attribute called chance with the return value of random.shuffle which is always None because shuffle "shuffles" the list in-place rather than creating a new list with a random order:
>>> chance_cards = ['card1', 'card2', 'card3']
>>> chance = random.shuffle(chance_cards)
>>> print(chance)
None
EDIT: A note on globals
options to get rid of global (you should really do some outside learning on variable scope on your own...):
Move your variables outside the class into the "module scope". You can still refer to them in your class.
import random, copy #copy isn't strictly needed but used for clarity
# `new_list=some_list[:]` is functionally equivalent to `new_list=copy.copy(some_list)`
chest_cards = (["Go to..."])
chance_cards = (["Go to Ja..."])
class cards:
def __init__(self):
self.chest_cards = copy.copy(chest_cards) #make a local copy so you don't alter the master list
random.shuffle(self.chest_cards)
If you only need them within the class, leave them as a class attribute and refer to them with either self or by the name of the class.
import random, copy
class cards:
chest_cards = (["Go to..."])
chance_cards = (["Go to Ja..."])
def __init__(self):
#before you over-write self.chest_cards, it refers to the class attribute
self.chest_cards = copy.copy(self.chest_cards) #make a local copy so you don't alter the master list
#after you over-write it, it will refer to the instance attribute as long as you made a copy.
#you can also refer to the class directly to access class-attributes
self.chest_cards = copy.copy(cards.chest_cards)
#if you want to get the class without calling it by name (in case you want to change the name of the class)
self.chest_cards = copy.copy(type(self).chest_cards)
random.shuffle(self.chest_cards)
There are definitely more ways... see if you can find any :)
Firstly :
You have a data attribute called chance, and a method called chance; so when you set self.chance in your init method, it overwrites the reference to the method of the same name - come up with a diffferent name for your chance attribute`
Secondly:
random.shuffle() is an in-place function - it changes the list that you pass it, and retuens NONE - which is why your chance attribute is set to None. If you want your chance attribute to be a version of your chance global which is shuffled - then do this :
def __init__(self):
self.chance = chance[:]
random.shuffle(self.chance)
or
def __init__(self):
self.chance = list(random.choices(chance, k=len(chance))
Thirdly:
Globals - why (they are very bad thing to get into the habit of using)- and if you are going to use them (why) don't set from a class body - that is entirely unneccessary, and confusing.
I have this:
class Klasse1:
variable1 = "haha"
print Klasse1.variable1
and this:
class Klasse1:
variable1 = "haha"
Object1 = Klasse1()
print Object1.variable1
Why would I use the second (too much code) instead of the first (obviously easier)? In many sites and tutorials I've seen people create objects for something they could easily get without creating them.
You create objects to hold state. If you had something like:
class Wallet(object):
balance = 0
It wouldn't make sense to share the same balance between multiple instances of the class. You'd want to create a new instance of Wallet for each bit of money you're trying to track.
Because you'd use it by doing something like:
fred.wallet = Wallet
jim.wallet = Wallet # same object!
So now when Fred spends money
fred.wallet.balance -= 3.50
Jim spends that money too.
>>> print(jim.wallet.balance)
3.50
Instead you'd want to create INSTANCES of the class Wallet and give each one its own state
class Wallet(object):
def __init__(self, balance=0):
# __init__ is called when you instantiate the class
self.balance = balance
fred.wallet = Wallet()
jim.wallet = Wallet()
Now when Fred spends money:
fred.wallet.balance -= 3.50
Jim won't be affected because his Wallet object is a different instance
>>> print(jim.wallet.balance)
0
This is a tough question to answer succinctly but it comes down to when you want to have more than one instance of Klasse1. If you know you'll only ever need the class and never instantiate it, you'll be fine. If you do instantiate it, (ie calling the class and using the returned object), you can change the variable1 member without modifying the class' variable:
class Klasse1:
variable1 = "haha"
foo = Klasse1()
print foo.variable1
>>>'haha'
foo.variable1 = 123
print Klasse1.variable1
>>>'haha'
If you made another instance of Klasse1, it's variable1 would still be 'haha' even after altering foo's variable1.
bar = Klasse1()
print bar.variable1
>>>'haha'
What's sort of tricky is that if you change Klasse1.variable1, only bar.variable1 would change, foo.variable1 would remain 123.
Klasse1.variable1 = "this isn't funny"
print foo.variable1
>>>123
print bar.variable1
>>>"this isn't funny"
print Klasse1.variable1
>>>"this isn't funny"
I am trying to develop a car-dealership-customer model to really understand OOP using Python.
The problem I am running into is that I'd like to define Car as a separate class object from the Dealer class. This causes confusion; how can I use Car attributes and upload them into the dealership inventory, for example, and then make transaction updates depending on customer acquisition?
I broke it down to just these two classes for now (Car & Dealer). I'd like to create an inventory dictionary with model being the key and the output from my retail_cost function being the cost of vehicle. How do i insert instances of car into the dealership inventory in the class dealer?
For example:
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory and sales components"""
car_inventory = {}
def __init__(self, dealer_name):
self.dealer_name = dealer_name
The basic idea is that you add each instance of a car to the dealership, but note that I changed car_inventory to be an instance member of Dealer. This is because you want each new dealership you make to have its own car_inventory. If you left it as a class member of Dealer (the way you have it) then every dealership you make will have the same inventory.
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory sales components"""
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = []
car1 = Car("ford", "fiesta", '18,000')
car2 = Car("ford", "fiesta", '12,000')
dealer = Dealer("tom's dealership")
dealer.car_inventory.append(car1)
dealer.car_inventory.append(car2)
print(dealer.car_inventory)
First, car_inventory should probably be an instance variable, not a class variable. This allows each dealer to have their own inventory.
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = {}
Now you can create Dealer objects:
dealer_alice = Dealer('Alice')
dealer_bob = Dealer('Bob')
Create Car objects:
car_1 = Car('Corolla', 'V4', 12000)
car_2 = Car('Focus', 'V4', 13000)
And add them to the dealers' inventories:
dealer_alice.car_inventory['Corolla'] = car_1
dealer_bob.car_inventory['Focus'] = car_2
Or instantiate new cars and add them directly to the inventories:
dealer_alice.car_inventory['Jetta'] = Car('Jetta', 'V6', 18000)
dealer_bob.car_inventory['Mustang'] = Car('Mustang', 'V8', 17000)
Each dealer now has an inventory dictionary with keys that are strings representing the model and values that are Car objects. You can print them like this:
for model,car in dealer_alice.car_inventory.items():
print(model, car)
Note that this will print a nice model name like 'Jetta', but the Car objects will only print basic information - namely, the name of the class and the object's location in memory. If you want nicer output, you'll have to define a __str__() method for the Car class. It would also be good to do that for the Dealer class so that you can just print(dealer_bob) and get tidy output.
I'd also recommend picking some other kind of key (like the VIN) - as it is, if the dealer gets another car of the same model it'll just overwrite the existing one.
EDIT: CHECK AT THE BOTTOM FOR A MORE CLEAR VIEW OF WHAT I AM DOING, PLEASE!
As an example, let's say I have information on three cars:
Car One
500hp
180mph
15mpg
Car Two
380hp
140mph
24mpg
Car Three
450hp
170mph
20mpg
I want to put that in a dictionary, or SOMETHING, so that I can easily access it through a function.
def fuel_eco(car):
return("The fuel economy for %s is %s" % (car, mpg))
def top_speed(car):
return("The top speed for %s is %s" % (car, speed))
def horsepower(car):
return("The horsepower for %s is %s" % (car, hp))
Basically have a module with some functions and a list/dictionary/whatever of the information, and then have another script that asks what car they want to view info on, and what information they want to know.
import carstats
car = input("What car do you want to find out about?")
stat = input("What information do you want to know?")
getStat = getattr (carstats, stat)
print(getStat(car))
How do I store the information for the three vehicles (And more if I add them) in a dictionary, so I can retrieve the information?
Okay, these are the actual files I am working with:
File one is asoiaf.py:
def sigil (house):
"""
Function to return a description of the sigil of a specified Great House.
Takes one argument, the name of the House.
"""
house = house.lower ()
if house == "stark":
sigil = "a grey direwolf on a white field."
elif house == "lannister":
sigil = "a golden lion rampant on a crimson field."
elif house == "targaryen":
sigil = "a red three-headed dragon on a black field."
else:
sigil = "Unknown"
house = str(house[0].upper()) + str(house[1:len(house)])
return("The sigil for House %s is %s" % (house, sigil))
def motto (house):
"""
Function to return the family motto of a specified Great House.
Takes one argument, the name of the House.
"""
house = house.lower ()
if house == "stark":
motto = "Winter is coming!"
elif house == "lannister":
motto = "Hear me roar!"
elif house == "targaryen":
motto = "Fire and blood!"
else:
motto = "Unknown"
house = str(house[0].upper()) + str(house[1:len(house)])
return("The motto for House %s is: %s" % (house, motto))
The second file is encyclopedia.py:
import asoiaf
#import sl4a
#droid = sl4a.Android ()
#sound = input ("Would you like to turn on sound?")
info = "yes"
while info == "yes":
#if sound == "yes":
# droid.ttsSpeak ("What house do you want to learn about?")
house = input ("What house do you want to learn about?")
house = str(house[0].upper()) + str(house[1:len(house)])
#if sound == "yes":
# droid.ttsSpeak ("What do you want to know about House %s?" % house)
area = input ("What do you want to know about House %s?" % house)
getArea = getattr (asoiaf, area)
#if sound == "yes":
# droid.ttsSpeak (getArea (house))
print (getArea (house))
#if sound == "yes":
# droid.ttsSpeak ("Would you like to continue learning?")
info = input ("Would you like to continue learning?")
if info == "no":
print ("Goodbye!")
You'll see a lot of commenting out in the last code, because I had to comment out the TTS that I have for my phone, since most people are not on an Android right now. As you can see, I am using IF, ELIF, ELSE in the functions, and I am just trying to see if there is an easier way. I apologize if it is/was confusing.
Creating a class should be the best way to do it:
class Car: # define the class
def __init__(self, name, speed, hp, mpg):
# This is the constructor. The self parameter is handled by python,
# You have to put it. it represents the object itself
self.name = name
self.speed = speed
self.hp = hp
self.mpg = hp
# This bind the parameters to the object
# So you can access them throught the object
You can then use the object this way:
my_car1 = Car('Car One', 180, 500, 15)
my_car1.speed # Will return 180
Concercing the __init__ name, it has to be this name, all constructors have this name (that's how Python know it is the class constructor). The __init__ method is called when you call Car('car one', 180, 500, 15). You have to ommit the self parameter, Python handle it.
You can add other function to your class, like
def top_speed(self):
return 'The top speed is {}'.format(self.speed)
Then you simply have to do my_car1.topspeed()
In every function you define in a class self must be the first parameter (except some rare cases such as classmethod or staticmethods). Obviously the topseed function works only if you create it in the class Car: block.
I'd suggest you should read more about object oriented programming (OOP) in Python. Just google OOP python and you will have a lot of serious ressources explaining you how to create classes and how to use them.
This official python classes tutorial should help you a lot in understanding the concept.
EDIT:
Regarding the accessing of the class in an other script. It's simple:
let's say you save the code above in a car.py file. Just place that file in the same folder as your other script, and in your other script do:
from car import Car # car is the name of the .py file, Car is the class you want to import
name = input('Car name: ')
speed = int(input('Car speed: ')) # input return a string, you have to cast to an integer to have a number
hp = int(input('Car hp: '))
mpg = int(input('Car mpg : '))
my_car = Car(name,speed,hp,mpg) # Then you just create a Car Object with the data you fetched from a user.
stuff = my_car.speed * my_car.hp # An example of how to use your class
print('The given car have {} mph top speed and have {} horsepower'.format(my_car.speed,my_car.hp))
What you have to understand is that a Class is some kind of a formated data type. When creating a Car class, you are defining how to create a car object. And Each time you call Car(...), you actually create one of these object, the value you put in the object are whatever values you want to put. It could be random number, user input or even network fetched data. You can use this object as you want.
Edit 2:
Given your code. Creating classes will change some things. Let's Give an example.
File 1 houses.py:
class House: # defining a house class
def __init__(self,name, sigil, motto):
self.name = name
self.sigil = sigil
self.moto = motto
# Then, in the same file, you create your houses.
starks = House('starks','grey direwolf on a white field','Winter is coming!')
lannisters = House('lannisters', 'a golden lion rampant on a crimson field', 'Hear me roar!')
# let's skip targaryen, it's the same way...
unknown_house = House('unknown','unknown','unknow')
houses = [starks, lannisters]
def get_house(name):
for house in houses:
if house.name == name:
return house
return unknow_house # if no house match, return unknow
Then in your second file. You just se that:
import houses
house_wanted = input('What house do you want to know about?')
my_house = houses.get_house(house_wanted)
print('this is the house {}; Sigil {} and motto {}'.format(my_house.name, my_house.sigil, my_house.motto))
If you plan on working on biggers set. You should have a look at Enums. That could fit what you want.
If you want to getting a precise attribute, you can do it this way:
import houses
house_wanted = input('What house do you want to know about?')
my_house = houses.get_house(house_wanted)
attr= input('What do you want to know about that house?')
print(getattr(my_house,attr.lower()))
Note this last thing will raise an error if you call for non-existent attr (like foo).
There are many ways to solve the broader problem you describe in the text of your question (the question of how to store multiple pieces of information about an object). Classes maybe one good one. Classes have the advantage of better robustness than dictionaries.
To answer the specific question in the summary/title: "how to have more than one item associated with one key in a dictionary" - use dictionaries as the values, like this:
car_info = {'CarOne': {'power': 500, 'speed': 180, 'mileage': 18},
'CarTwo': {'power': 380, 'spead': 200, 'mileage': 10}
}
print "Car Two has power %d mileage %d" % (car_info['CarTwo']['power'], car_info['CarTwo']['mileage'])
You can see that this is not especially robust by trying to access the 'speed' for "CarTwo". If you look closely you will see that because I made a deliberate typo in the initializer for CarTwo, it does not have a speed at all, it has a spead. Classes will catch this error, dictionaries will not.
This is not a reason not to do it with dictionaries - just something to be aware of when deciding for your particular case.
You could create a class, called car, with whatever attributes you want!
Here's a great tutorial on how to do that: class tutorial
I'm on the road right now, but if you're having trouble, please tell me so that I can write some useful code...
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.