Actions

This section will have a look at actions, how they are created and handled during play and how to add new actions.

Overview of Action system

Actions are used to represent actions taken by characters. This include things like moving, fighting and drinking potions. Every time an action is taken by a character, new instance of Action class (or rather subclass of it) needs to be created.

Action creation during play

Actions are instantiated via ActionFactory, by giving it correct parameter class. For example, for character to move around, it can do it by:

action = self.action_factory.get_action(MoveParameters(self,
                                                       direction,
                                                       'walk'))
action.execute()

This creates a WalkAction and executes it, causing the character to take a single step to given direction.

Adding a new type of action

Lets say we want to create an action that allows characters to wait for specific amount of ticks.

Overview

Actions are located in pyherc.rules. Custom is to have own package for each type of action. For example, code related to moving is placed in pyherc.rules.move while code for various attacks is in pyherc.rules.attack. Parameter classes are placed to pyherc.rules.public and imported to top level for ease of use.

Last important piece is factory that knows how to read parameter class and construct action class based on it. Factories are placed in the same location as the actions.

Creating a sub action factory

Sub action factory is used to create specific types of actions. Start by inheriting it and overriding type of action it can be used to construct:

from pyherc.rules.factory import SubActionFactory

class WaitFactory(SubActionFactory):

    def __init__(self):
        self.action_type = 'wait'

Next we need to define factory method that can actually create our new action:

def get_action(self, parameters):
    wait_time = parameters.wait_time
    target = parameters.target
    return WaitAction(target, wait_time)

Defining new parameter class

WaitParameters class is very simple, almost could do without it even:

class WaitParameters(ActionParameters):

    def __init__(self, target, wait_time):
        self.action_type = 'wait'
        self.target = target
        self.wait_time = wait_time

Constructor takes two parameters: target who is character doing the waiting and wait_time, which is amount of ticks to wait. action_type is used by the factory system to determine which factory should be used to create action based on parameter class. It should match to the action_type we defined in WaitFactory constructor.

Creating the new action

WaitAction is not much more complex:

class WaitAction(object):

    def __init__(self, target, wait_time):
        self.target = target
        self.wait_time = wait_time

    def is_legal(self):
        return True

    def execute(self):
        self.target.tick = self.target.tick + self.wait_time

Constructor is used to create a new instance of WaitAction, with given Character and wait time.

is_legal can be called by system before trying to execute the action, in order to see if it can be safely done. We did not place any validation logic there this time, but one could check for example if the character is too excited to wait.

Calling execute will trigger the action and in our case increment internal timer of the character. This will effectively move his turn further in the future.

Configuring ActionFactory

pyherc.rules.public.ActionFactory needs to be configured in order it to be able to create our new WaitAction. This is done in pyherc.config.Configuration:

wait_factory = WaitFactory()

self.action_factory = ActionFactory(
                                    self.model,
                                    [move_factory,
                                    attack_factory,
                                    drink_factory,
                                    wait_factory])

Adding easy to use interface

Last finishing step is to add easy to use method to Character class:

def wait(self, ticks, action_factory):
    action = action_factory.get_action(WaitParameters(self,
                                                      ticks))
    action.execute()

Now we can have our character to wait for a bit, just by calling:

player_character.wait(5, action_factory)

Notice how we are passing ActionFactory from outside of Character objects, instead of defining it as an attribute of Character. We do not want to inject service objects into domain objects, because it would complicate saving and loading later on.

Whole code

Below is shown the whole example of wait action and demonstration how it changes value in character’s internal clock.

from pyherc.data import Character, Model
from pyherc.rules import ActionFactory, ActionParameters
from pyherc.rules.factory import SubActionFactory
from random import Random

class WaitParameters(ActionParameters):

    def __init__(self, target, wait_time):
        self.action_type = 'wait'
        self.target = target
        self.wait_time = wait_time

class WaitAction(object):

    def __init__(self, target, wait_time):
        self.target = target
        self.wait_time = wait_time

    def is_legal(self):
        return True

    def execute(self):
        self.target.tick = self.target.tick + self.wait_time

class WaitFactory(SubActionFactory):

    def __init__(self):
        self.action_type = 'wait'

    def get_action(self, parameters):
        wait_time = parameters.wait_time
        target = parameters.target
        return WaitAction(target, wait_time)

model = Model()
wait_factory = WaitFactory()
action_factory = ActionFactory(model = model,
                               factories = [wait_factory])
character = Character(model)
action = character.create_action(WaitParameters(character, 5),
                                 action_factory)

print('Ticks {0}'.format(character.tick))
action.execute()
print('Ticks after waiting {0}'.format(character.tick))

The output shows how it is character’s turn to move (tick is 0), but after executing wait action, the tick is 5.

Ticks 0
Ticks after waiting 5