Intermission: wiring things for behave

After reflecting the status of the building pieces for spell casting, I realised that I can wire things a bit further on behave side.

I started by fixing a step definition for behave:

@when('{caster_name} casts {spell_and_target}')
def impl(context, caster_name, spell_and_target):
    # Simon casts magic missile on Uglak
    # Simon casts healing wind
    caster = get_character(context, caster_name)
    spell = spell_and_target

    make(caster, cast_spell(spell_name = spell))

Since healing wind spell will heal only the caster, I make some assumptions here (like not caring about who I’m casting the spell at). I used cutesy here too, just like in the melee combat, in order to keep the step definition short and decoupled from the actual implementation details.

Implementation for cast_spell – function is as follows:

class CastSpell():
    Class representing casting a spell
    def __init__(self, spell_name):
        Default constructor
        :param spell_name: name of the spell to cast
        :type spell_name: string
        super(CastSpell, self).__init__()
        self.spell_name = spell_name
    def __call__(self, caster):
        Performs the casting
        :param caster: character doing the casting
        :type caster: Character
        caster.old_values = {}
        caster.old_values['hit points'] = caster.hit_points

        action_factory = (ActionFactoryBuilder()

        caster.cast(direction = 1,
                    spell_name = self.spell_name, 
                    action_factory = action_factory)

def cast_spell(spell_name):
    Cast a spell

    :param spell_name: name of the spell to cast
    action = CastSpell(spell_name)
    return action

This hopefully decouples behave steps enough from actual implementation, in case they change later. CastSpell class is used to cast spells and it is callable (__call__ method has been implemented). This allows me to store context about the action in the object’s attributes and still call it like it would be a function. Another option would of course be to create a function and handle context parameters with closures.

cast_spell – function is just a convenient way of constructing CastSpell object with given parameters.

I’m keeping track of casters hit points, because further down the line I want to verify that they change after the spell has been cast. Later on I should add other attributes like mana points for tracking.

I’m also creating a new instance of ActionFactory. That is an object that is used to create actions, like moving, fighting and casting spells. Having a builder object makes it easy to construct the ActionFactory.

Final step was to implement one more behave step:

@then('{character_name} should be in full health')
def impl(context, character_name):
    character = get_character(context, character_name)

    assert_that(character.hit_points, equal_to(character.max_hp))

The step is short and it verifies that the character in question is in full health.

After running the behave I got following output:

  Scenario: Healing                       # features\magic.feature:29
    Given Simon is Wizard                 # features\helpers\
    And Uglak is Goblin                   # features\helpers\
    And Uglak is almost dead              # features\helpers\
    And Uglak is standing in room         # features\helpers\
    And Simon is standing away from Uglak # <string>:50
    Given Simon is almost dead            # features\helpers\
    When Simon casts healing wind         # <string>:24
    Then Simon should be in full health   # <string>:109
      Assertion Failed: 
      Expected: <5>
           but: was <1>

So Uglak and Simon, both in weak condition, are standing in a room. Simon casts healing wind, but does not have full health. Every step should have been implemented at this point, meaning I can keep coding and running the test to see when the code satisfies it.

Code for this change can be found from commit 3a32c172c65f19e7fcee9360eda4c301994ad8e8.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s