Endjinn Interactive Story Language

What if it was really easy to tell stories...?

Endjinn is a language designed to tell stories. Unlike a normal programming language it is designed to focus on expressive content, rather than implementing algorithms. Endjinn therefore uses a 'Wiki-like' format, where blocks of text are grouped into contextual sections that might represent paragraphs in an article, or rooms in an adventure game.

Endjinn is designed to run in a browser, to deliver dynamic, engaging content on a static site, without the need for a back-end server. In it's barest case it resembles a simple, sequential document where the reader (or listener!) can step through a story in a linear fashion.

However, Endjinn provides a number of ways to build interactivity into a story. Readers can take actions that change the state of the world, add new content or jump to different sections. Content can react to changes, altering to reflect world state, or changing each time a section is visited. Each section can define behaviour and objects that only apply within that context, and as readers visit a section they can invoke those behaviours to interact with the objects they encounter.

Endjinn does not dictate the medium through which a story is told. Whilst the text-first nature of the language is well suited to a web-document like interface using traditional links to navigate, it can also be driven through a more interactive interface, where actions are invoked through menus, buttons or text input. Alternatively, it can drive audio narratives or mixed media where images and even video are linked with other content.

Similarly, whilst Endjinn owes a lot to languages designed for interactive adventure games, it is not intended purely as an adventure scripting tool. An Endjinn script can define an article that helps the reader explore a subject more thoroughly, taking their own route through a topic, or experimenting with interactive examples that demonstrate concepts.

The key goal of Endjinn is to combine the power of an object-oriented language with the simplicity of wiki markup and an expressive natural language syntax.

Note that this is a proposal - the language specification is very much a work in progress, and the details provided here are open to discussion. If you find these ideas interesting (or horrific), drop me a message.

Endjinn in Practice

In practice, a minimal Endjinn script consists of an introductory block, then one or more sections. An Endjinn interpreter outputs the content of the start section, then waits for the reader to chose an action. Actions can output new content or move to a new section, which will be added to the document. When an end section is reached, the interpreter finishes.

There are many more features that can be added to create sophisticated and engaging interactions. An Endjinn script is an executable program rather than a static document. However, authors only need use the features that enhance their story. Endjinn is designed to put that story front and foremost rather than tangling it up with the clutter of programming.

This is an example of a simple script:

= Example Script =
   ## intro

   * Author : "J. R. Hartley"
   * Date   : ~2021-11-27
   * Start  : Introduction

= Introduction =
    Welcome to Endjinn. 

    This simple introduction shows you how to write a story with two sections.
    
    This is the first section, called 'Introduction'. [Read on > go next] for more information.

    > go next: to Summary

= Summary =
    ## end
    This is the second, end section.

    Endjin has many more features that enable sophisticated, engaging stories to be told. 
    
    For now, the story ends here.
    

Notice that this looks a lot like a normal Wiki file, with blocks of natural text and headings. It can be edited and viewed with a simple text editor. However Endjinn sees this as a program that can be executed. Each section is an object that can change and respond to the reader, and the story can build itself interactively.

When run with the Endjinn Interpreter, the reader will be presented with content as follows:

Introduction

Welcome to Endjinn.

This simple introduction shows you how to write a story with two sections.

This is the first section, called 'Introduction'. Read on for more information.

Clicking 'Read on' will add the next section to the output:

Introduction

Welcome to Endjinn.

This simple introduction shows you how to write a story with two sections.

This is the first section, called 'Introduction'. Read on for more information.

Summary

This is the second, end section.

Endjin has many more features that enable sophisticated, engaging stories to be told.

For now, the story ends here.

~ Endjinn ~

Flexible Interactions

By default, Endjinn builds an expanding narrative as the reader navigates their story. In other words, sections are appended to create a growing document, and the reader's options are presented inline as links. However, the way in which a story is told can be customised in many ways.

When the reader visits a section, instead of appending it's content to the document it may clear the document entirely or replace an existing section (including itself). A document template can define a page layout, allowing content to be directed to particular areas of the page.

Actions don't need to be confined to links. At each point in the narrative, Endjinn knows what options are available to the user, and can build a menu of available actions. Document templates allow these menus to be styled and presented in many different ways. Alternatively, a reader can drive the narrative through a text prompt, much like a traditional adventure game.

Through these options, an Endjinn script can build a document, behave more like a navigable website, or even present itself through a custom user interface.

Dynamic, Responsive Content

When telling a story that is interactive, content needs to be dynamic and responsive. Dynamic content avoids repetition and changes to reflect the state of the world it describes. Responsive content alters in response to the reader's actions and input. Endjinn provides tags that can be used to select the right content each time the reader visits a section, and injectors that insert calculated values into the text.

A tag turns the immediately following text on or off, depending on the condition within it. Tags are enclosed in square brackets, and preceded with a question mark, like so: [? condition], or an exclamation mark to negate the condition : [! condition]

Here are some examples:

= Demonstration =
    This content is shown by default. If no tags override it, every time the reader visits this section they 
    will see the text in this paragraph.

    [? after 1] After the first visit to this section, this content will be shown instead of the paragraph above.

    [? at 2] This content is shown on the second visit to this section.

    [? every 3] This content is shown every third visit.

    [? random 1] First random content
    [? random 2] Second random content
    [? random 3..5] Third to fifth random content

    [! room is lit] This content is shown if the room is not lit.

    [? brief] This content is used when only a brief description is required.

Values can also be injected into content using curly brackets {value}. Values are taken from the current context (the section being visited), the world state and the reader's state. Value injection also handles plurals with the bar character separating singluar and plural text, chosen according to the value of the previous injection.

For example:

= Values In Content =
    Welcome {reader.name}.

    This section has been visited {visits} time{|s}

Actions and Functions

Within an Endjinn script, the reader is presented with a choice of actions to take at any given point. Unlike links in a web document, an action tag separates out the presentation of the choice from the function that will be invoked. The action itself is a semantic representation of the choice the reader is making (for instance 'go north', 'take lamp'). The function is defined separately from the body of the content, but within the section that it applies to.

= Action and Function =
    An action tag links text to an action [Like this > go next].

    > go next: to Summary

In the example above, the action tag is displayed as a link with the text 'Like this'. When the link is clicked, the action 'go next' is invoked. The line > go next: to Summary defines the function for the 'go next' action. In this case it takes the reader to the section called Summary.

Actions need not be represented as action tags in the content shown to the user. If they are defined within the current section, or the context of the reader, Endjinn takes note that they are available. Endjinn can use the list of available actions to either display menus and user interface elements to the reader (through the document template), or respond to textual commands from the user, through an input prompt.

Functions can span multiple lines, and Endjinn uses indentation to define scope (in a similar manner to Python). Any text following a colon : is treated as a function expression. Content within a function (with no preceeding colon) is displayed to the reader when the function is invoked. For instance:

= Action and Function =
    When you're ready [click here > go next].

    > go next: if visits is 1
        Sorry to see you go, hopefully you'll return one day
        : to Summary
      : else
        It was great to see you again, enjoy your trip
        : to Summary

Function expressions support the common language constructs, including assignment, conditionals, iteration and invoking other actions. To welcome authors who might be put off by traditionally terse constructs, a syntax is adopted that attempts to be more 'English-like' - for instance if some.value is true

Object Orientation

Endjinn Scripts are an Object Oriented specification for an executable program. From a programmer's point of view sections are instances of objects created when the script is executed. Objects have state, consisting of untyped fields and objects they contain. The 'contains' relationship is explicit in an Endjinn Script and represented by successive indentation of sections.

In addition sections can specify actions which behave like event listeners. Whilst most actions are made available for the reader to invoke (either through links within the content or other user interface interactions), actions can be invisible to the user. These event actions can be invoked by system events (timers, counters, lifecycle), or 'called' by any function expression. When a section is visited, the describe event action is invoked, which by default outputs the content defined for that section.

Besides a number of default fields and actions inherited by all objects, objects can also inherit traits. A trait can specify state and actions that can be applied to many objects. Objects can inherit multiple traits, and can be tested. For instance :if this is a key tests to see if the enclosing object inherits the key trait. Traits are defined in an Endjinn script in much the same way as a section, but with the title enclosed in hash characters (#), rather than equals

When both sections and their traits specify actions, they are processed in a strict order - first any actions defined in the section itself, then in the order that traits are specified in the script and finally any action defined by the root object. An action defined with a single greater than symbol '>' prevents any further actions being processed (the action is not propagated further). Defining an action with two greater than symbols '>>' allows the action to propagate - the defined function is executed first, then within any subsequent traits.

The following script demonstrates some of these features. It uses built in room and item traits to implement a common adventure trope. Note that a comment is preceeded by a pair of forward slashes, // and is ignored by the interpreter.

= The Cellar =
    ## dark
    ## room // Rooms list any items they contain

    This is a damp, dirty cellar that long ago might have been used to store coal.

    = Lantern =
      ##item // Items can be carried
      ##lamp (unlit) // Lamps are a source of light

      An ornate lantern. You can [light it > light this] if you wish.

      [? lit] A brightly glowing lantern.

      >> light this
          The lantern splutters into life, emitting a bright, warm light.

# lamp #
    * State: {lit, unlit}

    > light this : set state lit

# dark #
    // This overrides the default description of a room
    > on describe: if a lamp is here and it is lit
            : propagate // Allow the room to describe itself
        : else 
            This place is dark, you cannot see a thing.