For some reason my while loop is stopping after two tries and I can't figure out what's wrong...
It's supposed to be an ant farm, where you can choose to breed and make a new ant, etc.
I just don't understand why it's stopping...
Here's my code:
import random
class Colony(object):
workerAnts = 0
list = []
temp = []
foodAmount = 10
def breedWorker(self):
if Colony.foodAmount < 5:
print "Sorry! You do not have enough food to create a new worker ant!"
else:
Colony.foodAmount -= 5
Colony.workerAnts += 1
Colony.list.append("ant")
def step(self):
number = 'ant'
for number in Colony.list:
a = Ant()
a.forage()
if Colony.foodAmount > 0:
Colony.foodAmount -= 1
if Colony.foodAmount < len(Colony.list):
for number in Colony.list[Colony.foodAmount+1:]:
Ant.health -= 1
def purge(self):
number = 'ant'
for number in Colony.list:
if Ant.health > 0:
Colony.temp.append("ant")
Colony.list = Colony.temp
class Ant(object):
health = 10
def forage(self):
if Ant.health == 0:
Colony.workerAnts -= 1
if random.randint(0,100) > 95:
Ant.health = 0
print "Ant has died from a horrible accident!"
Colony.workerAnts -= 1
elif random.randint(0,100) < 40:
newFood = random.randint(1,5)
print "Ant has found %s food!!" % newFood
Colony.foodAmount += newFood
elif random.randint(0,100) < 5:
Ant.health = 10
Colony.foodAmount += 10
print "You've found sweet nectar! Your ant has returned to full health and has brought 10 food back to the colony!"
else:
print "Ant returned empty-handed!"
def main():
queen = Colony()
queen2 = Ant()
while queen.workerAnts > 0 or queen.foodAmount >= 5:
print "========================================================"
print """
Your colony has %s ants and %s food, Your Majesty.\nWhat would you like to do?\n0: Do nothing.\n1: Breed worker. (Costs 5 food.)""" % (queen.workerAnts, queen.foodAmount)
answer = int(raw_input(">"))
if answer != 1 and answer != 0:
print "Sorry, invalid input!"
if answer == 0:
queen.step()
queen.purge()
if answer == 1:
print "Breeding Worker..."
queen.breedWorker()
queen.step()
queen.purge()
if queen.workerAnts <= 0 and queen.foodAmount < 5:
print "I'm sorry! Your colony has died out!"
You don't have constructors (__init__(self, ...)) and do not initialize object's properties
in methods you call this object property by self.property, not by Classname.property; in python you explicitly pass instance or class object to method, by convention they should be 'self' for instance, or 'cls' for class.
If you want use any Colony properties in Ant object or vice versa, you need to explicitly pass the reference, and store it as property. The most sensible would be to create Ant from Colony by calling something ants.append(Ant(self)); Ant's constructor should have signature `def init(self, colony):'
Well, that's because of the following line in def purge(self):
Colony.list = Colony.temp
The first time purge() is ran, it makes both Colony.list and Colony.temp point to the same array in memory. So the second time you run purge(), you go into an infinite loop, where you for number in Colony.list: do Colony.temp.append("ant"), which actually increases Colony.list as well, and the loop never exits, since it will always have a new member.
In python, for loops create iterators for the given object (if it's not already an iterator). In every iteration, python will call the iterator's next() method (in this case - the list). If next() can't yield a new value to iterate over, it raises StopIteration, and the loop exits. Don't worry, this exception is automatically handled for you by the for statement. In your case, Colony.list.next() always finds a new value (since you've just appended to it), and will never reach the end.
To fix your code, try slicing. This means that the array is copied, instead of pointing the two names to the same array:
Colony.list = Colony.temp[:]
You made Ant.health a class variable (shared between all Ant instances).
As soon as one ant's health goes to 0, they all die.
Here is an improved version. The following code is Python 2 and 3 compatible, and I think fixes all the errors!
import random
import sys
if sys.hexversion < 0x3000000:
# Python 2.x
inp = raw_input
rng = xrange
else:
# Python 3.x
inp = input
rng = range
def get_int(prompt, lo=None, hi=None):
"""
Prompt until an integer value in [lo..hi] is entered, then return it
"""
while True:
try:
val = int(inp(prompt))
if (lo is None or lo <= val) and (hi is None or val <= hi):
return val
except ValueError:
pass
class InsufficientFoodError(Exception):
pass
class Colony:
def __init__(self, workers=0, food=10):
self.food = food + Ant.cost * workers
self.ants = []
for i in rng(workers):
self.add_ant()
def add_ant(self):
try:
self.ants.append(Ant(self))
except InsufficientFoodError as e:
print(e)
def step(self):
# all ants eat, then all ants forage:
for ant in self.ants:
ant.eat()
for ant in self.ants:
ant.forage()
# bring out yer dead!
self.ants = [ant for ant in self.ants if ant.is_alive()]
def add_food(self, amount):
self.food += amount
def take_food(self, amount):
amt = min(amount, self.food)
self.food -= amt
return amt
def num_ants(self):
return len(self.ants)
class Ant:
cost = 5
max_health = 10
def __init__(self, colony):
# try to get enough food to produce an ant
food = colony.take_food(Ant.cost)
if food < Ant.cost:
# Failed! return any taken food and throw an error
colony.add_food(food)
raise InsufficientFoodError('The colony does not have enough food to make a new Ant')
else:
# Success!
self.colony = colony
self.health = Ant.max_health
def eat(self):
if self.health > 0:
self.health -= 1 - self.colony.take_food(1)
if self.health == 0:
print("An ant starved to death.")
def forage(self):
if self.is_alive():
dice = random.randint(0, 100)
if dice <= 5:
self.health = Ant.max_health
self.colony.add_food(10)
print("You've found sweet nectar! Your ant has returned to full health and has brought 10 food back to the colony!")
elif dice <= 40:
found_food = random.randint(1, 5)
self.colony.add_food(found_food)
print("Ant has found {} food!".format(found_food))
elif dice <= 95:
print("Ant returned empty-handed!")
else:
self.health = 0
print("Ant has died from a horrible accident!")
def is_alive(self):
return self.health > 0
def main():
colony = Colony()
while True:
print(
"========================================================\n"
"\n"
"Your colony has {ants} ants and {food} food, Your Majesty.\n"
"What would you like to do?\n"
" 1: Do nothing\n"
" 2: Breed worker (costs {cost} food)"
.format(ants=colony.num_ants(), cost=Ant.cost, food=colony.food)
)
opt = get_int("> ", 1, 2)
if opt == 2:
print("Breeding Worker...")
colony.add_ant()
colony.step()
if colony.num_ants() == 0 and colony.food < Ant.cost:
print("I'm sorry! Your colony has died out!")
break
if __name__=="__main__":
main()
This answer is a little off, but seems like it will be a valuable piece of knowledge.
A big issue here is that your classes are being used in an undesirable way.
The main advantage of a class is to hold an instance of variables/functions, such that you can have many independent groupings of them.
By calling Colony.<var> you're changing the var of the base( or super) class variable. This works if you only want to have one Colony,... but what if you want two,. or three! or a million!!?
Notice how you get an error when you don't enter self as the first parameter to your class functions? What you need to realize is that you're passing an instance of your class as the first parameter. This is how the class knows what grouping of variables to use.
say we have a class Antzilla
class Antzilla:
antvar = "antzilla var"
def PrintSuperAntvar(self):
print Antzilla.antvar
def PrintInstanceOfAntvar(self):
print self.antvar
notice that PrintSuperAntvar calls the base var and that PrintInstanceOfAntvar prints an instance of Antzilla
If I make az1 and change az1.antvar it will not change the Antzilla.antvar value.
az1 = Antzilla()
az1.antvar = "new var"
az1.PrintSuperAntvar()
>>> antzilla var
az1.PrintInstanceOfAntvar()
>>> new var
I can now create a new Antzilla instance with the original starting value because I never changed base class value
az2 = Antzilla()
az2.PrintSuperAntvar()
>>> antzilla var
az2.PrintInstanceOfAntvar()
>>> antzilla var
However if you were to change this super value then you would see that new Antzilla's start with this new value, but Antzilla's that have been already changed remain the same.
Antzilla.antvar = "newest var"
az3 = Antzilla()
az3.PrintSuperAntvar()
>>> newest var
az3.PrintInstanceOfAntvar()
>>> newest var
az1.PrintSuperAntvar()
>>> new var
WATCH OUT!!! notice what happens when we call az2!
az2.PrintSuperAntvar()
>>> newest var
az2 was never changed from the super variable, so when we changed Antzilla.antvar from "antzilla var" to "newest var", az2 will continue to cling to the super value.
How do we avoid this conflict!? Its simple!
Just add a constructor to your class that copies the super value, or a new value into its own variable..
If present, the __init__ function will be called if when you make a new Antzilla instance
class Antzilla:
antvar = "antzilla var"
def __init__(self):
self.antvar = Antzilla.antvar
...
You can also add the var as a requirement to your constructor such that every instance is unique.
class Antzilla:
antvar = "antzilla var"
def __init__(self, antvar ):
self.antvar = antvar
...
az1 = Antzilla("antzilla unique swag")
It is important to note however that when dealing with variables like lists, that you will need to specifically create a new list for every instance. Luckily the best place to do this is also the constructor.
So now getting back to your issue,.. for your two classes I would add constructors like this
For Colony:
class Colony(object):
workerAnts = 0
list = []
temp = []
foodAmount = 10
def __init__(self):
self.workerAnts = 0
self.list = []
self.temp = []
self.foodAmount = 10
....
For Ant
class Ant(object):
health = 10
def __init__(self):
self.health = 10
....
And finally what you need to do before getting into the math or logic errors is replace all the spots where you call a base or super variable with self or the name of the variable in the given scope.
ie, things like:
Colony.foodAmount -= 5
change to:
self.foodAmount -= 5
============================================================================
PS that spot where you write :
Colony.temp.append("ant")
is actually appending a string to your base list. You probably want to change that to the constructor for an Ant.. which returns a new instance of the Ant class and make it add the variable to an instance of colony instead of the base colony
self.temp.append(Ant())
============================================================================
Hope this helps!!
Cheers,
Cal
Related
I am making a text based adventure game in python. Once the game begins, I would like to create an instance of a class called "Character" which is the player's character object. I would like the user to be able to choose the race of the character they want to play. So far I have:
class Race:
def __init__(self, name, passive, hp):
self.name = name
self.passive = passive
self.hp = hp
and
class Lizard(Race):
def __init__(self, name, passive, hp):
super().__init__(name, passive, hp)
self.name = 'Lizardman'
self.passive = 'Regrowth'
self.hp = 20
def regrowth(self):
if 0 < self.hp <= 18:
self.hp += 2
and
def race_select():
races = ['Lizard']
while True:
for i, j in enumerate(races):
print(f"[{i + 1}]", j)
choice = int(input('Pick a race:'))
if choice <= len(races):
print('You are a ', races[choice - 1])
return races[choice - 1]
else:
continue
If I understand correctly, if I wanted the race to be a Lizard, I would still have to do
character = Lizard('Lizardman', 'Regrowth', 20)
Is there an easy way to let the user choose the race and the object to be created accordingly? Thanks
A simple solution would be to map a name to a class using a dictionary. As a simple example:
race_map = {"lizard": Lizard,
"human": Human} # I'm adding a theoretical other class as an example
choice = input('Pick a race:')
race_initializer = race_map.get(choice, None) # Get the chosen class, or None if input is bad
if race_initializer is None:
# They entered bad input that doesn't correspond to a race
else:
new_creature = race_initializer(their_name, their_passive, their_hp)
new_creature is now the new object of the chosen class.
You may want to standardize the input using choice.lower() to ensure that capitalization doesn't matter when they enter their choice.
I changed it to allow for specifying a race by a string name instead of a number. If you wanted a number, you could keep your list, but apply the same idea. Something like:
race_list = races = [('Lizard', Lizard), ('human', Human)]
choice = int(input('Pick a race:'))
try:
race_initializer = race_list[choice][1] # 1 because the class object is the second in the tuple
new_creature = race_initializer(their_name, their_passive, their_hp)
except IndexError:
# Bad input
I included the name in the race_list so that you can loop over the list and print out index->name associations for the user to pick from.
You may also want to use a more robust structure than a plain tuple to store name->initializer mappings, but it works well in simple cases.
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
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.
Let's say I have a spell named heal. How can I prevent a user from spamming heal every time they are damaged. I have considered applying this to individual combat functions; however, I am not sure how to implement a global rule for this? This code may clear it up:
available_spells = ['fireball', 'heal']
equipped = {'Weapon': "Staff",
'Armor': "Robes",
'Spells': ['fireball', 'heal']}
print "Your available spell(s) is(are) '%s'. " % equipped["Spells"]
inp = raw_input("Type the name of a spell you want to use.: ").lower()
lst = [x for x in available_spells if x.startswith(inp)]
if len(lst) == 0:
print "No such spell"
print ' '
elif len(lst) == 1:
spell = lst[0]
print "You picked", spell
#COMBAT FUNCTIONS HERE
else:
print "Which spell of", equipped["Spells"], "do you mean?"
If I were to make a class that defines certain actions for spells to take, how could I implement that into the code I have? For example if I have a class of spells, with functions defining damage rules, cool down times, etc., how could I reference that function in the code I already have? i.e. the player types 'heal' and I want it to reference an above class that has those values defined to check if the player recently played the spell, and what it does when played.
Am I clear enough in this question? How should I write a spell cool-down mechanic? How can I implement this mechanic into the code above?
Instead of storing all available spells as a list, you could store them as a dictionary, which allows you to also store the desired cooldown duration:
available_spells = {
# spell name: cooldown duration in seconds
'fireball': 3.0,
'heal': 5.0,
}
Each player could have another dict that keeps track of the last time they cast each spell. When the game starts, it would be empty:
cast_spells = {}
When the player attempts to cast a spell, check if the spell name is in the cast_spells dict. If it's not, then they have not yet cast it this game, so they are allowed to cast it:
if spell_name not in cast_spells:
cast_spells[spell_name] = datetime.now()
Otherwise, if the spell name is in the cast_spells dict, check if the required cooldown has elapsed:
elif cast_spells[spell_name] + datetime.timedelta(seconds=spells[spell_name]) < datetime.now():
cast_spells[spell_name] = datetime.now()
Otherwise, the cooldown is still in effect.
else:
print 'Spell not ready.'
I would probably do it using with, an exception handler, and a simple timer. That way you can just repeat the cooldown pattern, have shared cooldowns (like shown below), or even global cooldowns, etc.
Here are the classes:
import time
class CooldownException(Exception):
pass
class Cooldown(object):
def __init__(self, seconds):
self.seconds = seconds
self.expire = None
def __enter__(self):
if not self.expire or time.time() > self.expire:
self.expire = time.time() + self.seconds
else:
raise CooldownException('Cooldown not expired!')
def __exit__(self, type, value, traceback):
pass
heal_cooldown = Cooldown(5)
def heal():
try:
with heal_cooldown:
print 'You heal yourself!'
except CooldownException as e:
print e
def apply_bandage():
try:
with heal_cooldown:
print 'You bandage yourself!'
except CooldownException as e:
print e
def drink_potion():
try:
with heal_cooldown:
print 'You quaff a potion!'
except CooldownException as e:
print e
And here's how they're used:
>>> heal()
You heal yourself!
>>> time.sleep(3)
>>> drink_potion()
Cooldown not expired!
>>> time.sleep(3)
>>> apply_bandage()
You bandage yourself!
If I were to make a class that defines certain actions for spells to take, how could I implement that into the code I have?
As you guessed, your problem is very well suited to classes.
Am I clear enough in this question?
Yes.
Your program, but with classes
Here is your program modified to use two custom classes, FireballSpell and HealSpell. Each one has a .name, which is a string, and a .cast(), which is a custom behaviour. It's nearly identical to your original code, so it should be easy for you to understand:
available_spells = [FireballSpell(), HealSpell()]
equipped = {'Weapon': "Staff",
'Armor': "Robes",
'Spells': [FireballSpell(), HealSpell()]}
while True:
print "Your available spell(s) is(are) '%s'. " % [spell.name for spell in equipped["Spells"]]
inp = raw_input("Type the name of a spell you want to use.: ").lower()
lst = [spell for spell in available_spells if spell.name.startswith(inp)]
if len(lst) == 0:
print "No such spell"
print ' '
elif len(lst) == 1:
spell = lst[0]
print "You picked", spell.name
spell.cast()
else:
print "Which spell of", [spell.name for spell in equipped["Spells"]], "do you mean?"
print ""
Run it and give it a try! Here is the complete script. I'm pretty sure it does exactly what you want.
Specific spells
Each specific class has a name, cooldown time, and specific behaviour. The parent Spell class (see bottom) handles the rest.
class FireballSpell(Spell):
def __init__(self):
self.name = "fireball"
self.cooldown_seconds = 5
def spell_specific_behaviour(self):
# do whatever you like with fireball
# this is only called if the spell has cooled down
print "casting fireball"
class HealSpell(Spell):
def __init__(self):
self.name = "heal"
self.cooldown_seconds = 10
def spell_specific_behaviour(self):
# same applies here as from FireballSpell
print "casting heal"
Spell class
This is a generic Spell class - the parent of all spells. It knows the name, cooldown time, and behaviour from the specific spells (child classes above). It also has generic cooldown mechanic that's shared by the spells:
class Spell:
# spell data - filled in by specific spells
name = "default"
cooldown_seconds = 0
last_cast_time = 0
def cast(self):
# only cast the spell if it has cooled down
if self.is_cooled_down():
# call the behaviour set by the specific spell
self.spell_specific_behaviour();
# set the last cast time to the current time
self.last_cast_time = time.time()
else:
print self.name + " has not cooled down yet!"
def spell_specific_behaviour(self):
# implement in specific spell subclasses
return
def is_cooled_down(self):
current_time_seconds = time.time()
cooldown_expire_time_seconds = self.last_cast_time + self.cooldown_seconds
return current_time_seconds > cooldown_expire_time_seconds
Again, here is the whole thing in one working script. Have fun!
META: decorators, exceptions, and with blocks? Whoa, guys. OP is just now learning about classes. Let's keep it simple here.
Here is another example using decorators...
from functools import wraps
class Cooldown(object):
def __init__(self, seconds, cooldown_message):
self.seconds = seconds
self.expire = None
self.cooldown_message = cooldown_message
def decorator(self, fail_message_callback):
def _wrap_decorator(foo):
def _decorator(*args, **kwargs):
if not self.expire or time.time() > self.expire:
self.expire = time.time() + self.seconds
result = foo(*args, **kwargs)
return result
else:
if fail_message_callback:
fail_message_callback(self.cooldown_message)
return None
return wraps(foo)(_decorator)
return _wrap_decorator
heal_cooldown = Cooldown(5, 'Cooldown not expired!')
def display(message):
print message
#heal_cooldown.decorator(display)
def heal():
display('You heal yourself!')
#heal_cooldown.decorator(display)
def apply_bandage():
display('You bandage yourself!')
#heal_cooldown.decorator(display)
def drink_potion():
display('You quaff a potion!')
heal()
time.sleep(3)
drink_potion()
time.sleep(3)
apply_bandage()
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.