Making more dynamic stories

In this part of the story.kts guide, we will explore how to make more dynamic stories.

  1. Kotlin knowledge
  2. Using variables
  3. Storing stuff in the environment

Kotlin knowledge

For this part, it is recommended that you know at least a bit of Kotlin.

This page will cover most of the things you need to know for now. Try to understand this page from “Defining variables” to “Using conditional expressions”. It is recommended to read the entire page to have a fuller understanding of the Kotlin syntax.

Using variables

The easiest way to have variables in your program is by using Kotlin’s var.

Here is a simple example of how you add variables:

var myVariable = 33

story {
    ...
}

Now, if we want to use this variable, we can simply use Kotlin’s string template feature.

var myVariable = 33

story {
    node(1) {
        body {
            """
            This is $myVariable
            """
        }
        ...
    }
    ...
}

Once you open the story, you will see “This is 33”. Since this is a variable, we can also change it. The does block of an option is a good place to do that.

var myVariable = 33
story {
    node(1) {
        body {
            """
            This is $myVariable
            """
        }
        option { "Add one" } does {
           myVariable = myVariable + 1
            null
        }
        option { "Remove one" } does {
            myVariable = myVariable - 1
            null
        }
    }
}

Because the does block always needs to provide a node where the option should lead, we use null to indicate that we simply want to lead back to the current node, that we don’t want to move.

Now, if we load the story, we have two options to increase and decrease our variable.

Note that Kotlin is a statically-typed language, meaning that myVariable will have the same type all the time. Here, it is an integer. It will thus remain an Integer. For example, this is not possible:

var myVariable = 1
myVariable = "Hello!"

Another spot where it might be appropriate to change our variable is right when we reached the node. We can do this using a onNodeReached block.

var wasMyVariableReached = false

story {
    node(1) {
        body {
            """
            Let's go!
            """
        }
        option { "Alright!" } does { nodeRef(2) }
    }

    node(2) {
        body {
            """
            It's $wasMyVariableReached
            """
        }

        onNodeReached {
            wasMyVariableReached = true
        }
    }
}

The onNodeReached block is called after the option’s does block but before the body block. When we click on the option from the first node:

  • The does block is called and tells us to go to the node which has the id 2
  • The onNodeReached block is called. It sets wasMyVariableReached to true
  • The body block is called to determine the text of the node, and, since at this point wasMyVariableReached is true, it will display “It’s true”

Storing stuff in the environment

The “environment” is a generic term for “where your story lives”. The environment has a lot of information, but one of its most interesting aspects is environment delegation.

Later, we will see how to have multiple stories from completely different files and how to jump from one to the other.

variables only exist within the file where you declare them. Because of this, if you have two files like so…

// File 1
var myVariable = 20

story {
    id = "main"
    node(1) {
        body {
            """
            International travel?
            """
        }
        option {"Heck yes!"} does { 
            myVariable = 300
            ref("other", 50) 
        }
    }
}

// File 2

story {
    id = "other"
    node(50) {
        body {
            """
            What is my variable? :(
            """
        }
    }
}

… the second file has no way of knowing what myVariable is, nor can it modify it in a way that would be visible to the first file.

This is where environment-delegated properties come into action! Instead of storing our variable “in the file”, we can store it “in the environment”, making it available for all the stories ran under the same environment. The only restriction is that, in order to be “linked”, the variables need to share the same name.

// File 1
var myVariable by env.delegated(20)

story {
    ...
}

// File 2
var myVariable: Int by env.delegated()

story {
    id = "other"
    node(50) {
        body {
            """
            My variable is $myVariable!
            """
        }
    }
}

Notice that, in the first file, the way we provide a default value (i.e. a value if the environment has never received anything here), is by passing an argument to the delegated call.

Note that the default value is not shared among multiple delegated variables, meaning that you could have an environment variable at two places with the two spots giving different default values. If this happens, the default value used is the one from the variable that gets used first.

If you do not provide a default value, like in File 2:

  • You have to explicitly specify the type of the variable: it is an integer here
  • You have to make sure that you never use the variable before it is initialized
    • Either set it before doing anything with it
    • Or be sure that another file is setting it for you