1

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?

Mochamethod
  • 266
  • 3
  • 13
  • You should probably have those classes in other files and import them into this file. That will allow you to reference functions from those classes. You will also need (either in-code or via parser) a library/database of spells defined. You could use a notion of time, and a list of actions taken with their points in time to help determine cooldowns. I think this question might be a bit too open-ended to get excellent answers here though. – BlackVegetable Oct 27 '15 at 20:12

4 Answers4

1

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.'
John Gordon
  • 19,454
  • 6
  • 24
  • 42
  • This is the right idea. However, `cast_spells` is a dictionary that "*keeps track of the last time they cast each spell*", so the name `cast_spells` is not accurate or specific. It should be named something like `spells_previous_cast_time`. – kdbanman Oct 27 '15 at 20:43
1

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!
kdbanman
  • 9,073
  • 8
  • 40
  • 74
woot
  • 6,942
  • 2
  • 34
  • 53
  • 1
    Classes are definitely the right approach here, but you [should not use exceptions for control flow](http://stackoverflow.com/a/729412/3367144). A spell that hasn't cooled down yet is normal behavior, not exceptional, so an exception should not be used here. – kdbanman Oct 27 '15 at 20:53
1

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.


kdbanman
  • 9,073
  • 8
  • 40
  • 74
0

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()
woot
  • 6,942
  • 2
  • 34
  • 53