World Engine Tutorial

Stories Characters
World Engine

1. Introduction

Welcome to the World Engine tutorial! On this page you can learn how to utilize the World Engine to create almost any kind of text-based game or experience on Deep Realms. More specifically, you'll learn how to write code, which can be understood and executed by the World Engine. The language used to communicate with the World Engine is the Python programming language. If you're already familiar with Python, writing code for the World Engine will feel like second nature. However, if you're not, don't worry! Python is a relatively easy programming language and you only need to grasp the basics to be able to build something cool on Deep Realms.

Alternatively, if you prefer to avoid programming entirely, you can skip to this section, in order to start building by using code templates.

2. Basic examples

We'll start off with two basic examples, to give you a taste of the kind of things you can achieve with the World Engine. We'll then build upon these examples and dive deeper into the possibilities offered by the World Engine. This tutorial does not cover the basics of the Python programming language, but instead assumes you are already somewhat familiar with it.

2.1. Create games with precise numerical elements.

player_exp = 0

def post_generate():
    if ai.check("Main protagonist befriended a dragon."):
        player_exp += 50
        ui.display("You have gained 50 experience poins!")

The World Engine can be used to create games with precise numerical elements. For example, the first line in the code above initializes a variable called player_exp and sets its value to 0. Next, a function is defined with the name post_generate(). The post_generate() function is special, since any code placed in this function will run each time the AI generates some text. More specifically, code placed in the post_generate() function will run after the AI generates a story continuation. In contrast, any code placed in a function called pre_generate() will run before the AI generates a story continuation (more on that later). And finally, any code that is placed in the global scope - meaning it is outside of any function (such as the player_exp = 0 expression) - will run only once, right after a story is created.

Ok, let's focus on the post_generate() function and take a look at what the code inside it does. First there's the following conditional statement if ai.check("Main protagonist befriended a dragon."):. The ai.check() function takes in one argument, which is a string containing a statement in natural language. This should be a statement that is either true or false. The AI will attempt to evaluate this statement based on the story text. Depending on this evaluation, the ai.check() function will either return the value True or False (both of which are of type boolean). In this example, we can see that if the main protagonist of a story befriends a dragon, the ai.check() function should return True , which would then lead to the execution of the indented code belonging to conditional statement. In this case it is player_exp += 50, which simply adds 50 to the variable player_experience, followed by ui.display("You have gained 50 experience poins!") which displays a message. Take a look at the example below, which shows how such a message would appear to a player.

Example:

"Peace," I whispered, breaking off a piece of the chocolate, its scent mingling with the musk of the forest. "I come bearing gifts, not swords."

To my astonishment, the dragon uncoiled like a silent wind, its massive head descending to my level, nostrils flaring as it sampled the air. Gently, with the care one might use when approaching a skittish deer, I extended my hand, offering the morsel. The beast hesitated, a low rumble vibrating from its throat, then, with a grace that belied its size, it took the chocolate from my palm. The sensation of its breath was like the heat from a forge, but its touch was precise, delicate even.

In the moment that followed, a silent accord was struck. Two beings, one of myth, the other of mortality, finding common ground in the sharing of a simple earthly pleasure. A friendship began, not with a roar, but with the melting of chocolate and the breaking of barriers.

You have gained 50 experience points!

2.2. Create rich sets of rules that shape how the story unfolds.

def pre_generate():
    if ai.check('A woman named "Emily" is present in the current story scene.'):   
        ai.enforce(
            "Emily is a birdwatching enthusiast. She can be a bit shy when meeting someone new"
            " but she becomes more animated when the conversation turns to birds."
        )
        if ai.check("Someone asked Emily what's her favorite bird."):
            ai.enforce("Emily should respond that her favorite bird is a Blue Jay.")
    else:
        ai.enforce('Make a woman named "Emily" appear in the story (do it in a way that makes sense).')

In the example above, all of the code is nested within the pre_generate() function. As mentioned previously, this is a special function that runs each time before the AI generates a story continuation. This is useful if we want to shape the contents of the story continuation. In order to do this, we can use the ai.enforce() function that takes in one argument, which is a string containing some rules/guidelines for the AI to follow when generating a story continuation.

The code above first checks whether or not there is a woman named "Emily" present in the current scene; if there is, some guidelines are added for the AI to follow, which describe Emily's personality. The AI will try to adhere to these guidelines when generating text. Additionally, if an "Emily" is present in the current scene, the code also checks if someone asked Emily what her favorite bird is - if yes, some more guidelines are added that tell the AI how Emily should respond.

You might be wondering why can't we just add all of the guidelines, without checking whether some events have happened or not. While we could do this when we have a small number of guidelines, this is not a viable solution when we have a lot of potential guidelines we want the AI to follow. This is because the AI has limited memory, therefore it's best to only add the guidelines, which the AI needs to know about in the current step.

Lastly, there's one more thing we haven't gone over yet. Namely, if there is no woman named "Emily" in the story, the code above adds guidelines, which tell the AI to make a woman named "Emily" appear in the story. You can add this code to any story and press send, to see this happen. Take a look at the example below:

Example:

I find myself standing alone, in the middle of nowhere, surrounded only by the desert.

The sun is hovering above me, bringing with it sweltering heat and dust. As I scan the horizon for any signs of life, I see a figure in the distance. It's a woman, her silhouette shimmers against the heat waves as she approaches. As she nears, I discern her long, curly hair and the backpack she carries. "Hi," she says with a smile. "I'm Emily. Do you need any help?"

3. General code structure

As mentioned before, the World Engine code should either have a pre_generate() function, a post_generate() function, or both. Other than that, you may define any other function with a valid name in Python, for any purpose. The World Engine understands all valid Python code, so you may create classes or even import modules from the standard library. The only limitation is that your code must run in under 7 seconds; however, that's plenty of time to do cool things! Lastly, there are also two special objects provided to you, so that you can interact with the AI and UI in Deep Realms. These two objects are the ai object and the ui object, which we will go over in detail in the subsequent sections.

4. The ui object

The ui object contains methods and attributes that enable interaction with the Deep Realms UI. This includes displaying messages in the story textbox or hint modal, as well as retrieving the story text, action, author's note, memory section, and generated text.

4.1. ui.display()

This method takes in one argument named message that is usually a string. You can also pass in a ui.Image object, or ui.ImageCollection object, but more on that later.

Example:

def post_generate():
    ui.display("This is a message")
This is a message

You can also use the following color tags: <red> <green> <blue> <orange>. And the following style tags: <b>, <i>. Below is an example:

Example:

def post_generate():
      ui.display("<green>This is <b>a message</b></green>")
This is a message

4.2. ui.messages_to_display

This attribute is a list of strings containing the messages that should be displayed. Whenever ui.display() is called, the message is appended to ui.messages_to_display. However, you can also manually modify this list like in the example below.

Example:

def post_generate():
      ui.messages_to_display = ["<green>This is <b>a message</b></green>", "This is another message"]
This is a message
This is another message

4.3. ui.story_text

This attribute is a string containing the last 700-1200 words of the story text. You may override this attribute inside the pre_generate function, in order to change the story text sent to the AI.

4.4. ui.action

This attribute is a string containing the action. You may override this attribute inside the pre_generate function, in order to change the action sent to the AI. You may also then override this attribute inside post_generate, in order to change the action displayed to the user in the UI.

4.5. ui.authors_note

This attribute is a string containing the text in author's note. You may override this attribute inside the pre_generate function, in order to change the text sent as "author's note" to the AI.

4.6. ui.memory_section

This attribute is a string containing the text in the memory section. You may override this attribute inside the pre_generate function, in order to change the text sent as "memory section" to the AI.

4.7. ui.generated_text

This attribute is a string containing the text generated by the AI (available only in post_generate). You may override this attribute inside the post_generate function, in order to change the generated text displayed to the user in the UI.

4.8. ui.story_info

This attribute is a list of strings, which you can dynamically set in order to display information via the story hint button () on the play page. If you're making an RPG, this is a good place to put information such as character stats, current quest etc.

You can use the same color tags (<red> <green> <blue> <orange>) and style tags (<b>, <i>) as in ui.display(). Additionally, you have two more tags available: <title>, <divider>

Example:

story_info = [
    "<title>This is a title</title>", 
    "<orange>This is some orange, <i>italicized text.</i></orange>",
    "<divider>", 
    "This is some more, default text."
  ]
This is a title
This is some more, default text.

4.9. ui.redoing_generation

This attribute is a boolean corresponding to whether or not the "redo generation" button has been pressed.

4.10. ui.display_actions

This attribute is a boolean determining whether or not actions should be displayed in the UI. You can either set it once outside of the pre_generate and post_generate functions, or set it dynamically within those functions.

4.11. ui.Image

Pass in the following arguments, when creating a ui.Image object:

1. url - a string (str) containing the url to the image. Make sure you own the rights to the image. Deep Realms own a license to use images from Lexica.art, but you may also use images that you have a right to use from other websites.

2. title - an optional argument. This is a string (str) containing the image title.

3. text - an optional argument. This is a string (str) containing the text that should be displayed when a user clicks on the image.

4. height - an optional argument. This is an integer (int) corresponding to the height of the image in pixels (the width will be scaled to maintain image proportions).

5. padding - an optional argument. This is an integer (int)

6. action - an optional argument. This is a string (str) that corresponds to the action associated with the image (it turns the image into a button that sends a specific action). You can also pass in the actions argument which is a list of strings (List[str]) providing multiplie action options per image.

7. send_action_on_click - an optional argument. This is a boolean (bool) determining whether or not an action associated with an image shold be sent immediately upon clicking on the image/button. The default value is True

A ui.Image object can be passed into ui.display() in order to display an image. Take a look at the example below.

def pre_generate():
    image_1 = ui.Image("https://image.lexica.art/full_webp/1342ef6f-9fd8-4cc4-947a-cd3c9988a4e3")
    
    image_2 = ui.Image(
      url="https://image.lexica.art/full_webp/1342ef6f-9fd8-4cc4-947a-cd3c9988a4e3",
      title="<b>A colorful bird</b>",
      text="Here is some sample text to display for the user",
      padding=5,
      action="Take a closer look at the bird.",
      send_action_on_click=False
    )

    image_3 = ui.Image(
      url="https://image.lexica.art/full_webp/1342ef6f-9fd8-4cc4-947a-cd3c9988a4e3",
      actions=["action 1", "action 2", "action 3"]
    )
    
    ui.display(image_1)
    ui.display(image_2)
    ui.display(image_3)
  

4.12. ui.ImageCollection

Pass in the following arguments, when creating a ui.ImageCollection object:

1. images - a list of ui.Image objects.

2. num_images_per_row - an optional argument. This is an integer (int) between 1 and 12, which determines how many images will be displayed per row (this number may vary when using a device with a small screen, such as a phone).

3. spacing - an optional argument. This is an integer (int) between 1 and 12, which determines the spacing between images.

A ui.ImageCollection object can be passed into ui.display() in order to display a collection of images nicely. Take a look at the example below.

def pre_generate():
    url ="https://image.lexica.art/full_webp/1342ef6f-9fd8-4cc4-947a-cd3c9988a4e3"
    
    image_1 = ui.Image(url)
    image_2 = ui.Image(
      url=url
      text="Clicking on the image displays this text."
    )
    image_3 = ui.Image(url)
    images = [image_1, image_2, image_3]

    image_collection = ui.ImageCollection(images, num_images_per_row=3, spacing=2)
    ui.display(image_collection)
  

4.13. ui.Choice

Pass in the following arguments, when creating a ui.Choice object:

1. title - an optional argument. This is a string (str) containing the "title" of the choice.

2. text - an optional argument. This is a string (str) containing the text that should be displayed when a user clicks on the choice.

3. action - an optional argument. This is a string (str) that corresponds to the action associated with the choice (it turns the choice into a button that sends a specific action). You can also pass in the actions argument which is a list of strings (List[str]) providing multiplie action options per choice.

4. send_action_on_click - an optional argument. This is a boolean (bool) determining whether or not an action associated with a choice shold be sent immediately upon clicking on the image/button.

A ui.Choice object can be passed into ui.display() in order to display a choice. Take a look at the example below.

def pre_generate():
    choice_1 = ui.Choice("This is some text")
    
    choice_2 = ui.Choice(
      title="Examine the book",
      action="I pick up the book and examine it carefully, looking for any clues."
    )

    choice_3 = ui.Choice(
      title="Examine the book",
      actions=["action 1", "action 2", "action 3"]
    )
    
    ui.display(choice_1)
    ui.display(choice_2)
    ui.display(choice_3)
  

4.14. ui.ChoiceCollection

Pass in the following arguments, when creating a ui.CHoiceCollection object:

1. choices - a list of ui.Choice objects.

2. num_choices_per_row - an optional argument. This is an integer (int) between 1 and 12, which determines how many choices will be displayed per row (this number may vary when using a device with a small screen, such as a phone).

3. spacing - an optional argument. This is an integer (int) between 1 and 12, which determines the spacing between choices.

A ui.ChoiceCollection object can be passed into ui.display() in order to display a collection of choices nicely. Take a look at the example below.

def pre_generate():
    choice_1 = ui.Choice("This is some text")
    
    choice_2 = ui.Choice(
      title="Examine the book",
      action="I pick up the book and examine it carefully, looking for any clues."
    )

    choice_3 = ui.Choice(
      title="Examine the book",
      actions=["action 1", "action 2", "action 3"]
    )

    choices = [choice_1, choice_2, choice_3]

    choice_collection = ui.ChoiceCollection(choices, num_choices_per_row=3, spacing=2)
    ui.display(choice_collection)
  

5. The ai object

The ai object contains methods and attributes that enable interaction with the Deep Realms AI. This includes asking the AI to check if certain events occurred in the story, asking the AI for information based on the story, or adding adding guidelines for the AI to follow when generating text. Additionally, the ai object allows for skipping the text generation step entirely.

5.1. ai.check()

This method takes in two arguments:

1. statement - a string (str) containing some statement expressed in natural language, which the AI will evaluate as either true or false based on the source.

2. source - a string (str) containing either the value "story" or "action". The default value is "story".

If the value of argument source is "story", then the AI will evaluate statement based on the last story fragment generated by the AI. If the value of source is "action", then the AI will only evaluate statement based on the action specified by the user.

If the statement is true based on the contents of the specified source, then the ai.check() method returns the boolean True, whereas if the statement is false, it returns the boolean False. If there isn't enough information to determine whether or not a statement is true, the ai.check() function returns False. This includes the situation when the specified source is "action", but the user did not provide any action.

If ai.check() is called inside the post_generate() function with "story" as source, the story text will include the text generated by the AI.

Example:

def post_generate():
    if ai.check("Main protagonist befriended a dragon."):
        ui.display("Quest completed: Befriend a dragon!")

Example:

def pre_generate():
    if ai.check("I give the dragon chocolate", "action"):
        ui.display("Chocolate has been removed from your inventory.")

Sometimes it may be helpful to phrase a statement in a way that references the action itself. When doing so, you should reference the action using one of the following phrases: "the action", "the specified action", "the specified character action". Below is an example:

def pre_generate():
      if ai.check("The specified action involves purchasing an item.", "action"):
          ui.display("You have purchased an item!")

Sometimes it may also be helpful to phrase a statement in a way that references the story text that is provied to the AI. When doing so, you should use the phrase "the story fragmen". Below are examples:

def pre_generate():
      if ai.check("The specified action involves a character that does not appear in the story fragment.", "action"):
          ui.display("You have purchased an item!")

5.2. ai.enforce()

This method takes in one argument named guideline that is a string (str) containing some guideline expressed in natural language, which the AI will try to follow when generating text. This method should only be used inside the pre_generate() function.

Example:

def pre_generate():
    if ai.check("Main protagonist offers the dragon some chocolate."):
        ai.enforce("The dragon loves chocolate, so it becomes more friendly.")
    else:
        ai.enforce("The dragon is quite grumpy.")

5.3. ai.guidelines_to_enforce

This attribute is a list of strings containing the guidelines that should be enforced when the AI is generating text. Whenever ai.enforce() is called, the guideline is appended to this list. However, you can also manually modify this list like in the example below.

Example:

def pre_generate():
    ai.guidelines_to_enforce = ["A guideline to be enforced", "Another guideline to be enforced."]

5.4. ai.get()

This method takes in three arguments:

1. information - a string (str) describing the information, which the AI should retrieve from the specified source (argument number 3). If the AI cannot retrieve the information, the method will return None.

2. return_type - a string describing the return type of the data. For example: "str", "int", "bool", "List[str]" etc. The default value is "str".

3. source - a string (str) containing either the value "story" or "action". The default value is "story".

If the value of argument source is "story", then the AI will extract information from the last story fragment generated by the AI. If the value of source is "action", then the AI will only extract information from the action specified by the user.

The information is returned as a Python object of the specified type (return_type). If the information could not be extracted by the AI, then the ai.get() function returns None. This includes the situation when the specified source is "action", but the user did not provide any action.

If ai.get() is called inside the post_generate() function with "story" as source, the story text will include the text generated by the AI.

Example:

def pre_generate():
    character_name = ai.get("The name of the main protagonist.")
    if character_name is not None:
        ui.display("You have declared your name to be: " + character_name)

Example:

def pre_generate():
    dollars_earned = ai.get("Number of dollars earned", "int")
    target = 10
    if dollars_earned > target:
        ui.display(f"You have reached the target amount!")
    else:
        ui.display(f"You still need to earn {target - dollars_earned} more dollars....")

Example:

def pre_generate():
    dollars_spent = ai.get("Number of dollars spent", "int", "action")
    if dollars_spent is not None:
        ui.display(f"You spent ${dollars_spent}!")

5.5. ai.perform_generation

This attribute is a bool determining whether or not the AI should generate text. By default it is set to True before the pre_generate function runs.

Example:

def pre_generate():
    if ui.action == "/commands":
        ai.perform_generation = False
        ui.display("Here are the available commands: /help, /commands, /go")

5.6. ai.perform_action

This attribute is a bool determining whether or not the AI should include the action specified by the user, when generating text. By default it is set to True before the pre_generate function runs.

Example:

def pre_generate():
    if ui.action == "/transform":
        ai.perform_action = False
        ai.enforce("The main protagonist transforms into a black raven")

5.7. ai.model_name

This attribute is a str that contains the name of the model that is being used to generate text.

5.8. ai.model_stop_text

Assigning a string (str), or an iterable containing strings, to this attribute will cause the model to stop generating more text, once it generates the specified string. This is useful if you want to discard the text generated by a model after some point. By using ai.model_stop_text you don't have to wait for the model to generate that part of the text, which results in a shorter wait time.

Example:

def pre_generate():
    ai.model_stop_text = "." # The model will stop generating text once it generates a period.

Example:

def pre_generate():
    # The model will stop generating more text once it generates one of these punctuation symbols.
    ai.model_stop_text = (".", "?", "!")

5.9. ai.future_check()

This method takes in the same arguments as ai.check() - that is statement and source - and it "registers" a statement, so that it can be dynamically passed in as an argument to ai.check() (we'll explain what this means in a bit). Whenever code is ran by the World Engine, Deep Realms will statically analyze the code, looking for patterns such as ai.check("some string") or ai.check('some string'). However, this means that the code below would fail, since it would require a dynamic analysis (the code must be executed to determine the value of x).

def pre_generate():
    x = "Charles entered the city of Atlantis."
    if ai.check(x):
        ui.display("Welcome to Atlantis!")

To solve this issue, the ai.future_check() method was introduced. This method "registers" a statement, so that it can later be passed into ai.check() dynamically. The only caveat is that statements must be registered in the global scope. Therefore, the example below is a valid way of using ai.future_check():

x = "Charles entered the city of Atlantis."
ai.future_check(x)
  
def pre_generate():
    if ai.check(x):
        ui.display("Welcome to Atlantis!")

While the example below is

def pre_generate():
    x = "Charles entered the city of Atlantis."
    ai.future_check(x)
    if ai.check(x):
        ui.display("Welcome to Atlantis!")

Remember to also pass in the correct value for source when registering a statement.

# tip: the ai.future_check() function returns arguments that are passed in
statement, source = ai.future_check("Charles opens the gate to Atlantis.", "action")
  
def pre_generate():
    if ai.check(statement, source):
        ui.display("Welcome to Atlantis!")

However, since all Python code in the global scope is valid, you can call ai.future_check() anywhere in the global scope and it will work as intended. Take a look at the example below:

class Character:

    def __init__(self, name, description):
        self.name = name
        self.description = description
        
        self.check_presence_text = f"{name} is present in the current story scene."
        ai.future_check(self.check_presence_text)
    
    def pre_generate(self):
        if ai.check(self.check_presence_text):
            ai.enforce(
              f"Make sure to portray {self.name} in accordance with the following description: {self.description}"
            )

chararcteres = [
  Character("Annie", "A grumpy cat.")
  Character("Theodore", "A very grumpy cat"),
]

def pre_generate():
    for character in characters:
        character.pre_generate()

To be completely honest, you can register a string with ai.future_check() inside the pre_generate() or post_generate() functions. However, if you do so, you must wait until the next "post_generate" or "pre_generate" step before passing it into ai.check(). This means that if you register a string inside pre_generate(), you cannot pass it into ai.check() during this "pre_generate" step or the next "post_generate" step. You must wait until the next "pre_generate" step. Similarly, if you register a string inside post_generate(), you cannot use it within the same "post_generate" step or the next "pre_generate" step. You must wait until the next "post_generate" step before passing it into ai.check().

5.10. ai.future_get()

This method works analogously to ai.future_check() - see description above.

5.11. ai.remove_future_check()

This method allows you to unregister a string that was previously registered with ai.future_check(). In effect this will cause the additional credit cost of your world/story to decrease. It is a good practice to do so whenever you are certain that a previously registered string will never again be passed in as an argument to ai.check().

Example:

x = "Some statement"
ai.future_check(x)  # additional credit cost of world/story increased to 1

current_stage = 1

def pre_generate():
    global current_stage

    if current_stage == 1:
        if ai.check(x):
            current_stage == 2
            ai.remove_future_check(x) # additional credit cost drops back down to 0
    elif current_stage == 2:
        ui.display("You reached stage 2!")

Remember to pass in the correct value for source as well.

x = "Some statement"
y = "action"
ai.future_check(x, y)  # additional credit cost of world/story increased to 1

current_stage = 1

def pre_generate():
    global current_stage

    if current_stage == 1:
        if ai.check(x, y):
            current_stage == 2
            ai.remove_future_check(x, y) # additional credit cost drops back down to 0
    elif current_stage == 2:
        ui.display("You reached stage 2!")

You can register a string again after it has been unregistered, however, if you do so, you must wait until the next "post_generate" or "pre_generate" step before passing it into ai.check(). This means that if you register a string inside pre_generate(), you cannot pass it into ai.check() during this "pre_generate" step or the next "post_generate" step. You must wait until the next "pre_generate" step. Similarly, if you register a string inside post_generate(), you cannot use it within the same "post_generate" step or the next "pre_generate" step. You must wait until the next "post_generate" step before passing it into ai.check().

5.12. ai.remove_future_get()

This method works analogously to ai.remove_future_check() - see description above.

6. Examples

Now that you have familiarized yourself with all the functions and objects, a good next step is to look at some examples. Below are links to some examples of real code taken from existing worlds in the World Gallery:

1. Challenge Accepted (under 100 lines of code).

2. Zara Starling (under 200 lines of code).

3. Foreshadowed (under 400 lines of code).

Alternatively, you can take a look at the code templates in the next section, which might be easier at first, since they contain less code.

7. Code templates

This section contains some examples in the form of code templates, which are easy to modify to your liking, without the need to change the underlying code.

7.1. Character descriptions

This code template allows you to easily add characters with long descriptions to the story. First add all of the code below to a story, then take a look at the characters added below the text # Add characters below , and modify them to your liking (or add more).

class Character:

    def __init__(self, name, description):
        self.name = name
        self.description = description
    
    def pre_generate(self):
        if self.name in ui.story_text:
            ai.enforce(
              f"Make sure to portray {self.name} accurately."
              f" Here is a description of {self.name}: {self.description}"
            )


def pre_generate():
    for character in characters:
        character.pre_generate()

# Add characters below
#################################################################
characters = [
    Character("Annie", "A grumpy cat."),
    Character("Theodore", "A very grumpy cat"),
    Character(
      "Joanna",
      "Joanna is a curious and adventurous young woman with a penchant for solving mysteries."
      " Her bright green eyes and auburn hair tied back in a messy bun often give her the appearance"
      " of a mischievous sprite, especially when she's knee-deep in one of her impromptu investigations."
    )
]

7.2. Chat with a character

The code for the Zara Starling chat world is in the form of a template that is easy to modify, in order to create a chat with your own character.

7.3. Constraints

This code adds constraints to your story, limiting the the actions you can take and how successful they are (based on some level of realism).

import random

impossible_action_check = ai.future_check(
  "This action defies the laws of physics or is otherwise impossible.",
  "action"
) 
action_success_get = ai.future_get(
  "On a scale of 1 to 100 (representing percentage),"
  " what are the chances of the character performing this action successfully?",
  "int",
  "action"
)

def pre_generate():
    if ai.check(*impossible_action_check):
        ai.perform_generation = False
        ui.display("<red>This action is impossible. Please specify a different action</red>")
    action_success_chance = ai.get(*action_success_get)
    if random.randint(1, 100) < action_success_chance:
        ai.enforce("Make the character successfully perform the specified action.")
    else:
        ai.enforce("Make the character fail at performing the specified action.")
  

7.4. Suggested actions

This code template allows you to easily add suggested actions to a story written in the first person perspective. Simply add the code below to any story.

suggested_actions_get = (f'Provide a list of 3 actions that the main protagonist could take at this point. Make sure that the actions are written in the first person perspective.', "List[str]", "story")
ai.future_get(*suggested_actions_get)

def suggest_actions():
  suggested_actions = ai.get(*suggested_actions_get)
  if type(suggested_actions) is list and suggested_actions:
    choices = []
    for suggested_action in suggested_actions:
      if suggested_action[0].upper() == "I":
        action = suggested_action
      else:
        action = "I " + suggested_action[0].lower() + suggested_action[1:]
      choices.append(ui.Choice(title=suggested_action, action=action)) 
    ui.display(ui.ChoiceCollection(choices, num_choices_per_row=3, spacing=3))

def post_generate():
    suggest_actions()

7.5. Long story generation via plot points.

This code template allows you to easily generate a long story by specifying a list of plot points, along with the number of generations the AI should spend on each plot point. Simply edit or add plot points in the # 1. CONFIGURATION section. Additionally, once you load the story on the "Play" page, you can change the "Number of Generations" setting to some higher number, to make generating a long story more convenient.

# 1. CONFIGURATION
###############################################################################
# The plot points and the number of generations the AI should focus on each plot point 
plot_points = [
  ("Elizabeth sees a strange fox and decides to follow it.", 2),
  ("It turns out that the fox can talk like a human.", 3),
  ("The fox tells Elizabeth that there's a magical portal nearby.", 5)
]

# You can remove all the text if you don't want a prefix
plot_point_prefix = "Focus on the following plot point and don't advance the story beyond it: "

data = {
  "current_step": 0,
  "current_plot_point_id": 0,
  "finished": False
}


# 2. CODE
#################################################################################################
def pre_generate():
  # If we cycle through all plot points, this story goes back to behaving like a normal story
  if data["current_plot_point_id"] >= len(plot_points):
    if not data["finished"]:
      ui.display("All plot points have been incorporated into the story.")
      data["finished"] = True
    return

  # Get current plot point, add prefix (if it isn't blank), enforce the plot point
  current_plot_point = plot_points[data["current_plot_point_id"]][0]
  if plot_point_prefix:
    current_plot_point = plot_point_prefix + current_plot_point
  ai.enforce(current_plot_point)

  # Update current step and plot point id
  data["current_step"] += 1
  current_num_generations = plot_points[data["current_plot_point_id"]][1]
  if data["current_step"] % current_num_generations == 0:
    data["current_plot_point_id"] += 1
    data["current_step"] = 0

7.6. Events

This code template allows you to easily add events to a story, based on some action performed by the user. First add all of the code below to a story, then take a look at the events added below the text # Add events below , and modify them to your liking (or add more).

class Event:

    def __init__(self, condition, event):
        if condition:
            self.condition = ai.future_check(condition, "action")
        else:
            self.condition = None
        self.event = event

    def pre_generate(self):
        if self.condition:
            if ai.check(*self.condition):
                ai.enforce(self.event)


def pre_generate():
    for event in events:
        event.pre_generate()

# Add Events below
#################################################################
events = [
    Event("The action involves giving chocolate to the dragon.", "The dragon becomes more friendly."),
    Event("The action involves hugging the dragon", "The dragon becomes more friendly.")
]

7.7. Lore retrieval based on keyword presence

This code template contains a simple lore retrieval system based on the presence of keywords in the story text.

# On the left, define the keywords (separate them with a comma ",").
# On the right, define the corresponding lore text that should be retrieved.
lore = {
    "maria, vasquez": "The main protagonist of this story is a teenage girl named Maria Vasquez. She has a keen sense of justice and a quiet strength, has always been intrigued by the mysteries that seem to cloak Morrow Creek.",
    "david, johnston": "David Johnston is a bright and curious teenager with a knack for technology and a passion for solving puzzles. Raised in Morrow Creek, David's upbringing was shaped by the loss of his father, a local firefighter, in a mysterious blaze that the town never quite forgot.",
    "morrow, creek, town": "Morrow Creek is a small town in the middle of nowhere.",   
}

settings = {
    "include_entire_lore": False
}

def pre_generate():
    # check if the /all command was used and enable or disable "full lore" mode
    if ui.action == "/all":
        ai.perform_generation = False
        settings["include_entire_lore"] = settings["include_entire_lore"] == False
        if settings["include_entire_lore"]:
            ui.display("The entire lore will now always be included in the model context.")
        else:
            ui.display("The lore will now be retrieved based on the presence of keywords in the story text.")
        return

    if settings["include_entire_lore"]:
        for lore_text in lore.values():
            ui.memory_section += "\n\n" + lore_text
        return

    story_text_lower = ui.story_text.lower()
    for key, value in lore.items():
        keywords = [keyword.strip() for keyword in key.split(",")]
        for keyword in keywords:
            if keyword in story_text_lower:
                ui.memory_section += "\n\n" + value
                break

"""
# Uncomment to see if things work properly
def post_generate():
    ui.display(ui.memory_section)
"""