Balladeer

Drama

The previous sections of this manual have introduced you to the classes which Balladeer defines so that you can build your narrative.

The place where you write your code is called the Drama.

class Drama(self, *args, world=None, config=None, **kwargs)

One important aspect of the Drama class is that inherits from Entity. This means you can assign State to it.

It’s a good idea to customize your drama with Python properties so that you have access to useful characteristics. One of the supplied examples shows how the Drama class keeps track of the current location of the narrative.

    @property
    def here(self):
        return self.get_state("Spot")

In addition to properties, there are three ways of adding functionality.

Interlude

In Balladeer the interlude gets called every turn of a Story. This makes it the ideal place to put game rules which are invariant.

drama.interlude(self, *args, **kwargs)

Apply rules once every story turn.

Returns:

The Drama object

Return type:

Entity.

Directive handler

SpeechMark has a feature called directives. When a drama object implements a directive handler, it may be invoked from Speech.

Handlers must be instance methods whose name begins with the prefix on_. They take an argument which is the primary entity of the directive. Any other entities are supplied as positional arguments.

drama.on_xxxing(self, entity: Entity, *args: tuple[Entity], **kwargs):

Handles the SpeechMark directive ‘xxxing’.

Parameters:
  • entity – The primary entity of the directive

  • args – The associated entities of the directive

Return type:

None

This is a snippet from the Balladeer examples. In the scene, one character attacks another:

<WEAPON.attacking@FIGHTER_2>

    _Whack!_

Here is the Drama class with the corresponding handler:

class Fight(Drama):
    def on_attacking(self, entity: Entity, *args: tuple[Entity], **kwargs):
        for enemy in args:
            enemy.set_state(0)

Dialogue generator

Dialogue generators must be instance methods whose name begins with the prefix do_. They take keyword arguments, each of which must have an annotation.

The method must contain a docstring which defines the text that triggers the method. Docstrings may contain format specifiers which reference the keyword arguments.

drama.do_xxx(self, this, text, director, *args, **kwargs):

Annotations for keyword arguments may be:

  • An iterable type eg: an Enum/State.

  • A string which gives attribute access via the drama object to an iterable of entities.

This method is a generator of Speech objects. The speech objects must be of these three classes:

Prologue

Speech which belongs at the top of a page.

Dialogue

Speech interleaved with the main content of the scene.

Epilogue

Speech which belongs at the bottom of a page.

This sounds complicated, but it’s easily demonstrated with a couple of examples.

This do_move method declares in its docstring that text like n, north, go n or go north will activate the method. The method will be invoked with the corresponding Compass class member supplied via the heading parameter.

    def do_move(self, this, text, director, *args, heading: Compass, **kwargs):
        """
        {heading.name} | {heading.label}
        go {heading.name} | go {heading.label}

        """
        options = {
            compass: spot for compass, spot, transit in self.world.map.options(self.here)
        }
        if heading not in options:
            yield Prologue(f"<> You can't go {heading.label} from here.")
        else:
            self.set_state(options[heading])

Next we have do_drop, an example of an annotation which is a string accessor on the drama object. The text which triggers the method is drop or discard, so long as it’s followed by the name of any item in the inventory location of the drama object’s WorldBuilder.

    def do_drop(
        self, this, text, director, *args, item: "world.statewise[Spot.inventory]", **kwargs
    ):
        """
        drop {item.names[0]}
        discard {item.names[0]}

        """
        item.set_state(self.here)
        item.aspect = f"It lies on the floor."
        yield Prologue(f"<> You drop the {item.names[0]}.")