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.
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.
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.
"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.
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:
I find myself standing alone, in the middle of nowhere, surrounded only by the desert.
{ send was pressed at this point }
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?"
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!
Moreover, 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.
Lastly, there is a third special object - the data
object - which is particularly useful when developing code for the World Engine, but can also be used in other ways. We'll also go over it in detail in another section.
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.
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")
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>")
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"]
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.
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.
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.
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.
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.
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."
]
ui.redoing_generation
This attribute is a boolean corresponding to whether or not the "redo generation" button has been pressed.
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.
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)
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
)
3. spacing
- an optional argument. This is an integer (int
)
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)
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)
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)
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.
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.
Take a look at this section to see how you can improve the reasoning capabilities of the ai when using ai.check()
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!")
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.")
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."]
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.
Take a look at this section to see how you can improve the reasoning capabilities of the ai when using ai.get()
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}!")
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")
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")
ai.model_name
This attribute is a str
that contains the name of the model that
is being used to generate text.
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 = (".", "?", "!")
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 invalid:
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()
.
ai.future_get()
This method works analogously to ai.future_check()
- see
description
above.
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()
.
ai.remove_future_get()
This method works analogously to ai.remove_future_check()
- see
description
above.
data
objectWhen developing code for the World Engine, you might find yourself frequently changing and testing it as you play through a story. Whenever you change the code for a specific story, you can either click "Save" (in the code editor) to update the pre_generate()
and post_generate()
functions, while preserving any existing variables and data in memory;
or you can click "Reinitialize", which will execute all of the code again, including the code placed outside of the pre_generate()
and post_generate()
functions. However, when doing so, you will lose any existing data that was formed as a result of the code being executed as you play through the story.
The data
object is a regular Python dictionary that can be used to store
data in a way that preserves it, even when the code is changed and the "Reinitialize" button is clicked. The only caveat is that the data in this dictionary must be JSON compatible. This means that the keys must be strings, while values can only be strings, numbers, lists or dictionaries.
Take a look at the example below:
# We initialize the player data only if it doesn't already exist.
if "player" not in data.keys():
data["player"] = {}
if "name" not in data["player"].keys():
data["player"]["name"] = "John Doe"
if "exp" not in data["player"].keys():
data["player"]["exp"] = 0
if "stats" not in data["player"].keys():
data["player"]["stats"] = {
"strength": 0,
"dexterity": 0,
"intelligence": 0
}
if "skills" not in data["player"].keys():
data["player"]["skills"] = {
"swordsmanship": 0,
"archery": 0,
"magic": 0,
"climbing": 0,
"acrobatics": 0,
"alchemy": 0
}
if "items" not in data["player"].keys():
data["player"]["items"] = ["Rags", "Health Potion"]
# Now we define a player class outside of the pre_generate() and post_generate() functions.
class Player:
def __init__(self, data):
self.data = data
def add_exp(self, exp):
self.data["exp"] += exp
self.check_if_level_up()
def check_if_level_up(self):
if self.data["exp"] >= 100:
self.data["exp"] -= 100
self.data["skills"]["swordsmanship"] += 1 * self.data["stats"]["strength"]
self.data["skills"]["archery"] += 1 * self.data["stats"]["dexterity"]
self.data["skills"]["magic"] += 1 * self.data["stats"]["intelligence"]
self.data["skills"]["climbing"] += 1 * self.data["stats"]["strength"]
self.data["skills"]["acrobatics"] += 1 * self.data["stats"]["dexterity"]
self.data["skills"]["alchemy"] += 1 * self.data["stats"]["intelligence"]
# ... other methods can be added here.
# We create a player object and pass in the player data.
player = Player(data=data["player"])
def post_generate():
if ai.check("The main protagonist wins in combat."):
player.add_exp(25)
If you define a Player
class
outside of the pre_generate()
and post_generate()
functions,
then using the data
object is the only way you can ensure that when you change the Player
class code while playing through a story, and you click "Reinitialize", you will not lose existing data (eg. name, exp, stats, skills, items).
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.
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.
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."
)
]
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.
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.")
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()
This code template showcases how you can improve the reasoning of a model when using ai.get()
or ai.check()
.
This can come in handy when you want the model to retrieve complex information or evaluate complex statements.
# Defining functions and modifying `ai` object
##############################################################################
def future_complex_get(information, return_type, source):
information = f'Provide the following information: "{information}".' \
' Before you provide this information, you should show the step-by-step reasoning process used to obtain it.' \
' Your answer should be a JSON that adheres to the following format:' \
' {"reasoning": "provide the step-by-step reasoning here", "information": "provide the requested information here"}'
return_type = '{"resoning": "str", "information": ' + return_type + '}'
return ai.future_get(information, return_type, source)
def complex_get(information, return_type, source):
return ai.get(information, return_type, source)["information"]
def future_complex_check(statement, source):
statement = f'Evaluate the following statement (as true or false): "{statement}".' \
' Before you evaluate the statement, you should show the step-by-step reasoning process used to evaluate it.' \
' Your answer should be a JSON that adheres to the following format:' \
' {"reasoning": "provide the step-by-step reasoning here", "statement_evaluation": "provide a boolean here corresponding to whether the statement is true or false"}'
return_type = '{"resoning": "str", "statement_evaluation": bool}'
return ai.future_get(statement, return_type, source)
def complex_check(information, return_type, source):
return ai.get(information, return_type, source)["statement_evaluation"]
ai.future_complex_get = future_complex_get
ai.complex_get = complex_get
ai.future_complex_check = future_complex_check
ai.complex_check = complex_check
# Example usage of ai.complex_get and ai.complex_check
#################################################################
artifact_found_check = ai.future_complex_check(
"The main protagonist discovered an ancient artifact.",
"story"
)
artifact_value_get = ai.future_complex_get(
"Estimate the value (in dollars) of the ancient artifact discovered by the main protagonist." \
"You must provide the estimate based on the artifact's description.",
"int",
"story"
)
def post_generate():
if ai.complex_check(*artifact_found_check):
estimated_price = ai.complex_get(*artifact_value_get)
ui.display(f"The estimated value of the artifact is: ${estimated_price}")
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
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.")
]
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)
"""