StoryTeller


  1. [if(currentPage() equal "start")]Introduction[else][link("start")]Introduction[end][end]
  2. [if(currentPage() equal "gettingStarted")]Getting Started[else][link("gettingStarted")]Getting Started[end][end]
  3. [if(currentPage() equal "pages")]Pages[else][link("pages")]Pages[end][end]
  4. [if(currentPage() equal "links")]Links[else][link("links")]Links[end][end]
  5. [if(currentPage() equal "includes")]Including One Page In Another[else][link("includes")]Including One Page In Another[end][end]
  6. [if(currentPage() equal "decoration")]Decorating Your Story[else][link("decoration")]Decorating Your Story[end][end]
  7. [if(currentPage() equal "viewports")]Viewports[else][link("viewports")]Viewports[end][end]
  8. [if(currentPage() equal "variables")]Variables[else][link("variables")]Variables[end][end]
  9. [if(currentPage() equal "usingVariables")]Using Variables[else][link("usingVariables")]Using Variables[end][end]
  10. [if(currentPage() equal "localVariables")]Local Variables[else][link("localVariables")]Local Variables[end][end]
  11. [if(currentPage() equal "conditional")]Conditional Statements[else][link("conditional")]Conditional Statements[end][end]
  12. [if(currentPage() equal "randomness")]Randomness[else][link("randomness")]Randomness[end][end]
  13. [if(currentPage() equal "pageFunctions")]Page Functions[else][link("pageFunctions")]Page Functions[end][end]
  14. [if(currentPage() equal "lists")]Lists[else][link("lists")]Lists[end][end]
  15. [if(currentPage() equal "loops")]Loops[else][link("loops")]Loops[end][end]
  16. [if(currentPage() equal "listFunctions")]List functions[else][link("listFunctions")]List Functions[end][end]
  17. [if(currentPage() equal "sets")]Sets[else][link("sets")]Sets[end][end]
  18. [if(currentPage() equal "lookup")]Sets As Lookup Tables[else][link("lookup")]Sets As Lookup Tables[end][end]
  19. [if(currentPage() equal "relations")]Relations[else][link("relations")]Relations[end][end]
  20. [if(currentPage() equal "customFunctions")]Custom Functions[else][link("customFunctions")]Custom Functions[end][end]
  21. [if(currentPage() equal "functionDetails")]Sets and Relations in Custom Functions[else][link("functionDetails")]Sets and Relations in Custom Functions[end][end]
  22. [if(currentPage() equal "blockFunctions")]Block Functions[else][link("blockFunctions")]Block Functions[end][end]
  23. [if(currentPage() equal "customVariables")]Custom Variables[else][link("customVariables")]Custom Variables[end][end]

Introduction

StoryTeller is a JavaScript application which allows you to create simple interactive stories as HTML documents.

This document was created using StoryTeller. It will serve as both an example and a reference.

[include("contents")]

Getting Started

To begin you will need to create a new text file with a .html extension.

Next, copy the following HTML to create an empty story.

<!DOCTYPE html>            
<html>
  <head>
    <style>
      div[[data-page-id]] {display: none;}
      .error {color: #FF0000;}
    </style>        
    <script type="text/javascript"  src="storyteller.js"></script>
  </head>
  <body>    
    <div data-page-id="start" class="page">    
    </div>
  </body>
</html>                
            

When you distribute your story, you will need to include "storyteller.js" with it. If you want your story to be completely self-contained, you can copy the content of storyteller.js into the <script> element and remove 'src="storyteller.js"':

<!DOCTYPE html>            
<html>
  <head>
    <style>
      div[[data-page-id]] {display: none;}
      .error {color: #FF0000;}
    </style>       
    <script type="text/javascript">
      {Replace this with the content of storyteller.js}
    </script>
  </head>
  <body>    
    <div data-page-id="start" class="page">    
    </div>
  </body>
</html>  			
			
[include("contents")]

Pages

Like a book, your story will consist of a number of pages.

Each page is a <div> element with the the attribute "data-page-id" set to a unique name for that page. Without this attribute, StoryTeller will not recognise it as a page.

Example:

<div data-page-id="start">
  <h3>Welcome to my story!</h3>
</div>           
            

This is a page with the name "start". It will show the message "Welcome to my story!" You can include any HTML elements you like to structure the content of your pages.

Styling can be done with CSS just like any other HTML document. However, make sure that you keep "div[[data-page-id]] {display: none;}" in the <style> element. This ensures that the reader won't see the unprocessed pages of your story.

"start" is a special page name. It indicates to StoryTeller that this page is the first one a reader should see. If no "start" page is found StoryTeller will use the first page in the document.

Square brackets ('[[' and ']]') indicate special instructions for StoryTeller. If you want to include square brackets in the text shown to the reader then use double brackets ('[[[[' and ']]]]'). These will be replaced by single brackets in the text shown to the reader.

[include("contents")]

Links

In order to allow you reader to make choices you must add links from the current page to other pages in the story.

A link is added like this: [[link("{pageId}")]]{linkBody}[[end]]

Replace {pageId} with the name of the page you are linking to and {linkBody} with the content you want to display as the link.

Example:

<div data-page-id="start">
  <h3>Welcome to my story!</h3>
  [[link("page2")]]next page[[end]]
</div>     
<div data-page-id="page2">
  The End
</div>   
            

This example has two pages, "start" and "page2". The link will take the reader from "start" to "page2" when they click on the text "next page"

[include("contents")]

Including One Page In Another

Sometimes you'll have content which you want to show on a number of different pages.

The [[include("{pageId}")]] command will display the content of another page as though it was a part of the current page. Just replace {pageId} with the name of the page to be included

Example:

<div data-page-id="start">
  <h3>Welcome to my story!</h3>
  [[include("subpage")]]
</div>     
<div data-page-id="subpage">
  I hope you like it.
</div>   
            

This will display "Welcome to my story!" and "I hope you like it." all on one page.

[include("contents")]

Decorating Your Story

You can add some style to your story with CSS but to achieve the look you want you may need to use HTML elements like <div>s

By default, StoryTeller replaces the entire content of the <body> element with the current page. This will overwrite any extra elements you might have added.

In order to avoid this, you must tell StoryTeller to use a specific <div> element to display pages. This is done by adding a "data-viewport-id" attribute to that element and setting it to "main". The pages will be displayed inside this element and the content outside of it will remain unaltered.

Example:

<!DOCTYPE html>            
<html>
  <head>
    <style>
      div[[data-page-id]] {display: none;}
      .error {color: #FF0000;}     
      .banner {background-color: #000000; color: #FFFFFF;}
    </style>        
    <script type="text/javascript"  src="storyteller.js"></script>
  </head>
  <body>
    <div class="banner">
        <h1>My Story</h1>
    </div>
    <div data-viewport-id="main">
        <div data-page-id="start">
            Welcome
        </div>
    </div>
  </body>
</html>    
            

This story will have a permanent header which reads "My Story". The pages can be defined anywhere in the <body>. They won't be visible anyway.

[include("contents")]

Viewports

It is also possible to have more than one viewport. These allow you to display more than one page at any time. For example, you might display details like character health or inventory permanently on the side of the screen.

In order to define another viewport, create a <div> and set its data-viewport-id to something other than "main". You must also add the data-viewport-page attribute and specify which page this viewport will display. Note: if any viewports are specified, the main viewport must exist.

Example:

<!DOCTYPE html>            
<html>
  <head>
    <style>
      div[[data-page-id]] {display: none;}
      .error {color: #FF0000;}   
      * {padding: 0; margin: 0;}
      html, body {height: 100%; padding: 0px; margin: 0px;}
      #wrap {width:100%; height: 100%; margin:0 auto;}
      #mainViewport {float:left; width:76%; height: 100%; padding: 2%; background: #AAAAAA;}
      #sidebarViewport {float:right; width:16%; height: 100%; padding: 2%; background: #666666;}
    </style>        
    <script type="text/javascript"  src="storyteller.js"></script>
  </head>
  <body>
    <div id="wrap"> 
        <div id="mainViewport" data-viewport-id="main">    
        </div>
        <div id="sidebarViewport"  data-viewport-id="sidebar" data-viewport-page="sidebarPage">        
        </div>
    </div>
    <div data-page-id="start">
        <h3>Page 1</h3>
        [[link("page2")]]to page 2[[end]]
    </div>
    <div data-page-id="page2">
        <h3>Page 2</h3>
        [[link("page1")]]to page 1[[end]]
    </div>
    <div data-page-id="sidebarPage">
        Sidebar
    </div>
  </body>
</html>              
            

This will create two viewports, side by side. "main" will show the current active page ("start" or "page2"). "sidebar" will show "sidebarPage".

In order to be able to change the page displayed in a secondary viewport it must be made dynamic. This is done by adding the "data-viewport-type" attribute and setting it to "dynamic".

Example:

<div id="sidebarViewport"  data-viewport-id="sidebar" data-viewport-page="sidebarPage" data-viewport-type="dynamic">
</div>
            

The viewport's page can now be changed with [[viewport({viewportId},{pageId})]] where {viewportId} is the name of the viewport and {pageId} is the name of the page.

[include("contents")]

Variables

[variablesPageViews=variablesPageViews+1]

Variables allow your story can remember what has happened so far. This information can be used to change the content of a page.

For example. [if(variablesPageViews equal 1)]This is your first visit to this page[else if(variablesPageViews lessThanOrEqual 20)]You've seen this page [variablesPageViews] times[else]You've seen this page more than 20 times[end] since you opened this document.

Before you can use these variables in your pages, they must be declared in a <script> element after the existing <script type="text/javascript" src="storyteller.js"></script> element.

Example:

<script type="text/javascript"  src="storyteller.js"></script>
<script type="text/javascript"> 	
  variables = [[
    { name : "myBoolean", value : new BooleanVariable(true) },
    { name : "myNumber", value : new NumberVariable(0) },    
    { name : "myInteger1", value : new IntegerVariable(0, 100, 0, false) },
    { name : "myInteger2", value : new IntegerVariable(1, 10, 10, true) }
  ]];		
</script>
			

This is a JavaScript array containing a coma-separated list of variable definitions. Each entry takes the form { name : "{variableName}", value : new {variableType}({arguments}) }.

{variableName} is the name which will identify this variable. It may only contain letters, numbers and underscores ('_') and the first character must not be a number.

There are three possibilities for {variableType}.

A BooleanVariable can cold either true or false. It takes one argument, the value it initially holds.

A NumberVariable can hold numbers with fractional parts (like 3.14159 or 0.00001). It can also hold very large numbers. It also takes its initial value as its only argument.

An IntegerVariable can hold whole numbers in a limited range. It has 4 arguments ({minimumValue}, {maximumValue}, {initialValue}, {wrap}). {minimumValue} and {maximumValue} define the range of values which this variable can hold. {initialValue} sets the value which the variable will start as.

{wrap} tells the variable what to do when you try to put a number too big or too small into the variable. If it is false, then then any value less than {minimumValue} will become {minimumValue} and any value greater than {maximumValue} will become {maximumValue}. If it is true then the value will wrap around. For example, myInteger2 has {minimumValue}=1 and {maximumValue}=10. If you try to set the value 11. It will become 1. 23 will become 3 and -1 will become 9.

[if(detail)]

The following might get slightly technical. It's not that important and you can skip it if you like. [link("variables")][detail=false]Click here to hide the technical stuff.[end]

The value of each variable is maintained in the address bar of the reader's web browser. As you read this document you can see a random-looking string of characters after the file name (starting with '#'). This allows the reader to bookmark any position in the story and return to it later without losing the state of they variables. The space in the address bar is limited. That limit varies from browser to browser but the address (including everything before the '#' too) can safely be up to 2000 characters long.

Each variable you create will add to the length. BooleanVariables are the smallest. 6 of them can fit in one character on the address bar. NumberVariables are the largest each will take up to 11 characters (Actually it's slightly less than that, 3 NumberVaraibles will be packed into 32 characters).

The size of an IntegerVariable depends on the range of values it can hold. The following table show some of the possible sizes.

RangeCharacters
641
4 0962
262 1443
16 777 2164
1 073 741 8245

For example. IntegerVariable(0,63,0,false) can hold 64 different values so it will need 1 character in the address bar to hold its state.

[else]

There are some slightly technical details hidden here. You don't really need to read it. [link("variables")][detail=true]Click here to show the technical stuff.[end]

[end] [include("contents")]

Using Variables

Once you have declared a variable, you can use it in your pages. To display the value of a variable simply write its name in square brackets: [[{varaibleName}]]

For example, if you declare a variable named myInteger:

variables = [[
  { name : "myInteger", value : new IntegerVariable(0, 255, 42, false) }
]]; 
            

You can use it in a page like this:

<div data-page-id="start">
  The value of myInteger is [[myInteger]].
</div>			
            

This will display the text "The value of myInteger is 42"

You can update the value of a variable at any point in a page like this: [[{variableName}={expression}]]. {expression} can be a value or a mathematical expression. [[myInteger=83]] will update the value to 83. [[myInteger=myInteger+1]] will increase the value of myInteger by 1.

Expressions can use the mathematical operators '+', '-', '*'(multiply) and '/'(divide) along with brackets for grouping.

Expressions can also compare values with the operators 'equal', 'notEqual', 'lessThan', 'lessThanOrEqual', 'greaterThan' and 'greaterThanOrEqualTo'. For example "7 lessThan 3" will result in the value 'false'.

Boolean values can be combined with the operators 'and' and 'or'. For example "(1 greaterThanOrEqual 2) or (3 lessThan 4)" will be true. The 'not' operator will invert a boolean value. For example "not (1 equal 2)" will be true.

Inside a link variables operate slightly differently. Any changes made between [[link("{pageId}")]] and [[end]] will be undone after [[end]]. They will however, be applied if the reader follows the link.

Example:

<div data-page-id="start">
  [[myInteger=1]]
  <h2>This is the start page</h2>
  <p>[[link("page2")]][[myInteger=5]]Go To Page 2[[end]]</p>
  <p>myInteger is [[myInteger]]</p>
</div>	
<div data-page-id="page2">
  <h2>This is page 2</h2>
  <p>myInteger is [[myInteger]]</p>
</div>		
			

On the start page. The user will see "myInteger is 1". If they click the link they will see "myInteger is 5"

This can be used to allow a user's choice to change values while remaining on the same page.

Here's an example which allows the reader to distribute skill points for their character.

variables = [[
  { name : "pointsRemaining", value : new IntegerVariable(0, 20, 20, false) },
  { name : "strength", value : new IntegerVariable(0, 20, 0, false) },
  { name : "intelligence", value : new IntegerVariable(0, 20, 0, false) },
  { name : "charisma", value : new IntegerVariable(0, 20, 0, false) }
]]; 

...

<div data-page-id="start">
  <h2>Distribute Your Skill Points</h2>
  <p>You have [[pointsRemaining]] points remaining</p>
  <ul>
    <li>Strength: [[strength]] - [[link()]][[pointsRemaining=pointsRemaining-1]][[strength=strength+1]]add[[end]]</li>
    <li>Intelligence: [[intelligence]] - [[link()]][[pointsRemaining=pointsRemaining-1]][[intelligence=intelligence+1]]add[[end]]</li>
    <li>Charisma: [[charisma]] - [[link()]][[pointsRemaining=pointsRemaining-1]][[charisma=charisma+1]]add[[end]]</li>
  </ul>
</div>	
			

Note here that no page name was supplied to the link() function. This creates a link which keeps the reader on the current page.

When the reader clicks on one of the links, it will increase the associated attribute and decrease the points remaining. Note: This will not stop the reader from distributing after pointsRemaining reaches 0. To do that you need [link("conditional")]conditional statements[end].

Note: If your page does not work check the JavaScript console (pressing F12 in most browsers will open it) for any error messages.

[include("contents")]

Local Variables

Sometimes you only need a variable for calculations within a page and it is unnecessary for StoryTeller to remember it after the reader clicks a link.

For this purpose you can use local variables. These do not need to be declared before they are used and do not take up space in the address bar.

Local variable names start with a dollar sign ('$') and, after that, consist of any combination of numbers, letters and underscores. They can be used anywhere in which standard variables can be.

Example:

<div data-page-id="start">
  [[$localVariable=3]]
  <p>$localVariable = [[$localVariable]]</p>
  [[$localVariable=$localVariable*7]]
  <p>$localVariable = [[$localVariable]]</p>
  <p>[[link("page2")]]Go to page 2[[end]]</p>
</div>
<div data-page-id="page2">
  <p>$localVariable = [[$localVariable]]</p>
</div>
            

The start page will display "$localVariable = 3" then "$localVariable = 21". However, when the reader clicks "Go to page 2" the value will be forgotten and will display "$localVariable =" (local variables which have not been set have no value)

Local variables can store any type of value. They can even hold text, something that standard variables cannot. Text values are written between quotation marks.

Example:

<div data-page-id="start">
  [[$myName="Inigo Montoya"]]
  <p>Hello. My name is [[$myName]]!</p>
</div>
            

This will display "Hello. My name is Inigo Montoya."

Text values can also be joined with the '+' operator in expressions.

Note: local variables are not shared with included pages (using the "include" statement).

Example:

<div data-page-id="start">
  [[$myName="Inigo Montoya"]]
  [[include("subpage")]]
  <p>Hello. My name is [[$myName]]!</p>
</div> 
<div data-page-id="subpage">
  [[$myName="Westley"]]
</div>            
            

This will display "Hello. My name is Inigo Montoya.". The $myName local variable in subpage is completely independent of the $myName variable in start.

If you want to include quotation marks inside a string you must escape them with backslash ('\'). For example: "\"My dear Mr. Bennet,\" said his lady to him one day, \"have you heard that Netherfield Park is let at last?\"". Will result in the string:

"My dear Mr. Bennet," said his lady to him one day, "have you heard that Netherfield Park is let at last?"

[include("contents")]

Conditional Statements

Variables are especially useful when you can change the content of a page based on their values. For this, you must use conditional statements.

The simplest conditional statements are written like this: [[if({expression})]]{body}[[end]]. {expression} can be any expression. If it resolves to true then the content of {body} will be processed. false, 0 and unset local variables are all treated as false, everything else is treated as true.

Example:

variables = [[
  { name : "showDetail", value : new BooleanVariable(false) }
]]; 

...
           
<div data-page-id="start">
  <p>[[link("page2")]][[showDetail=false]]Show page 2 without detail[[end]]</p>
  <p>[[link("page2")]][[showDetail=true]]Show page 2 with detail[[end]]</p>
</div>  
<div data-page-id="page2">
  <p>Hello. My name is Inigo Montoya.</p>
  [[if(showDetail)]]
    <p>You killed my father. Prepare to die.</p>
  [[end]]
  <p>[[link("start")]]Go back to start[[end]]</p>
</div>           
            

"You killed my father. Prepare to die." will only be displayed if the reader uses the second link to get to page2.

The [[else]] statement allows you to specify alternative content to process if the expression is false.

The following extends the skill points example from [link("usingVariables")]Using Variables[end].

<div data-page-id="start">
  [[if(pointsRemaining greaterThan 0)]]
    <h2>Distribute Your Skill Points</h2>
    <p>You have [[pointsRemaining]] points remaining</p>
    <ul>
      <li>Strength: [[strength]] - [[link("start")]][[pointsRemaining=pointsRemaining-1]][[strength=strength+1]]add[[end]]</li>
      <li>Intelligence: [[intelligence]] - [[link("start")]][[pointsRemaining=pointsRemaining-1]][[intelligence=intelligence+1]]add[[end]]</li>
      <li>Charisma: [[charisma]] - [[link("start")]][[pointsRemaining=pointsRemaining-1]][[charisma=charisma+1]]add[[end]]</li>
    </ul>
  [[else]]
    <h2>You Have Distributed All Of Your Skill Points</h2>
    <ul>
      <li>Strength: [[strength]]</li>
      <li>Intelligence: [[intelligence]]</li>
      <li>Charisma: [[charisma]]</li>
    </ul>
  [[end]]
</div>
            

Now the reader will be unable to use more than the 20 skill points they are given. Once pointsRemaining is reduced to 0 they will no longer see the links to increase their attributes.

Even more complex statements can be written using [[else if({expression})]] instead of [[else]]. This allows you to specify a second expression to be checked if the first one is false. You can chain as many "else if" statements as you like. StoryTeller will continue to check each expression until it finds one which evaluates to true. A chain of "else-if" statements can also be ended with an "else" statement which will define the content to process if all expressions evaluate to false.

Example

<div data-page-id="start">
  [[$x=1]]
  [[$y=2]]
  [[$z=2]]
  [[if($x equal $y)]]
    [[$theValue=1]]
  [[else if($x greaterThan $y)]]
    [[$theValue=2]]
  [[else if($y equal $z)]]
    [[$theValue=3]] 
  [[else if($x lessThan $z)]]
    [[$theValue=4]]    
  [[else]]
    [[$theValue=5]]
  [[end]]
  <p>The Value is [[$theValue]]</p>
</div>            
            

The first expression is false and so is the second. The third is true so $theValue is set to 3. StoryTeller then jumps to the [[end]] because it has found a true expression.

[include("contents")]

Randomness

Using the features described so far, you can write stories which are completely deterministic. If the reader makes the same choices they will always get the same outcome. Adding some randomness allows you to make stories in which the reader can make the same choices and get a different outcome.

There are three random functions you can use.

Example:

<div data-page-id="start">
  [[if(randomBoolean(0.5))]]
    <p>Sometimes the reader will see this.</p>
  [[else]]
    <p>Other times they will see this.</p>
  [[end]]  
</div>
          
[if(detail)]

The following might get slightly technical. It's not that important and you can skip it if you like. [link("randomness")][detail=false]Click here to hide the technical stuff.[end]

The values generated by these functions aren't actually random. They are generated by a function which appears random if you don't know its internal state. This internal state is stored in the address bar along with your variables. This state is initialised from the browser's random number generator when the story is opened.

This means that the outcomes are actually all decided when the reader opens the story and are saved when they bookmark a location within it. In order to get a new sequence of random events you need to clear the state by removing the hash ('#') and everything after it from the address bar (and then hitting enter to reload).

If you want to build the ability to reset into your stories then use [[reset()]]{body}[[end]] to create a link which will reset the story when the reader clicks it.

[else]

There are some slightly technical details hidden here. You don't really need to read it. [link("randomness")][detail=true]Click here to show the technical stuff.[end]

[end] [include("contents")]

Page Functions

StoryTeller can tell you what page the reader is currently on. The function "currentPage()" will return the pageId of the currently displayed page. "currentPageNumber()" will return an integer which identifies the current page.

These functions may not sound especially useful. If you're writing the page you already know what page it is. However, remember that it is possible to include sub-pages in a page. You can use currentPage() within the sub-page to determine which page it is included within.

This document uses such a technique in the menu at the bottom of each page. The menu is defined as a sub-page and included in each page. It does not display a link to the page you are already on.

The following is an excerpt from the menu sub-page:

[[if(currentPage() equal "start")]]<b>Introduction</b>[[else]][[link("start")]]Introduction[[end]][[end]]
            

Using currentPageNumber() you can get an identifier for the current page which can be stored in an IntegerVariable (It can also be stored in a NumberVariable but that will take up more space.). This requires you to know how many pages your story has (or at least an upper limit) in order to declare the IntegerVariable. To avoid this there is a fake variable type you can use. You can define a variable as PageNumberVariable({pageId}) and StoryTeller will automatically convert it to the right size IntegerVariable based on the number of pages in your story. {pageId} is optional. It is the pageId which page this variable will initially point to. If it is omitted then the start page is used.

Example:

variables = [[
  { name : "pageNumberA", value : new PageNumberVariable() },
  { name : "pageNumberB", value : new PageNumberVariable("page2") }
]];         
            

"link" "include" and "viewport" can use these numeric identifiers in place of the page's name. This allows you to save a page identifier and then use it later to link back to a remembered page.

Example:

<div data-page-id="history">
  [[link(previousPage)]]back[[end]]
  [[previousPage=currentPageNumber()]]
</div>            
            

This will provide a link back to the last page the reader saw and then update previousPage to point to the current page.

You can also get the numeric identifier for the page currently selected for any viewport by calling the "viewport" function without a {pageId}. [[pageVar=viewport({viewportId})]] will save the numeric identifier for the page currently selected for the viewport named {viewportId}.

[include("contents")]

Lists

A list is an ordered collection of values. A list is surrounded by curly brackets ('{' and '}') and the values are separated with commas. For example: {1,2,3,4,5}. You can also define an empty list: {}

Lists can be assigned to local variables (but not standard variables). For example: [[$fellowship={"Frodo","Sam","Merry","Pippin","Gandalf","Aragorn","Legolas","Gimli","Boromir"}]]

A list can also be assigned to a list of variables. Each variable will receive the corresponding value from the list. For Example: [[{$first,$second,$third}={"A","B","C"}]] will set $first to "A", $second to "B" and $third to "C". This can also be done with lists stored in other variables.

Example:

[[$myList={"A","B","C"}]]
[[{$first,$second,$third}=$myList]]
            

If there are fewer variables than values then the extra values will be ignore. If there are more variables than values then the variables with no corresponding value will not be updated.

The assignment will also work with lists that contain other lists. For example: [[{$a,{$b,$c},$d,{$e},$f,$g}={1,{2,3},4,{5,6,7,8},9}]] will result in:

When at least one of the values involves is a list, the '+' operator will join the lists. For example {1,2}+{3,4} gives {1,2,3,4}, {5,6}+7 gives {5,6,7} and 8+{9,10} gives {8,9,10}

The projection operator ('@') can be used to extract individual items or sublists from a list. you use it like this: {list}@{index}. {list} is either a literal list or a local variable holding a list. {index} is either a single index or a list of indices, indicating which values to take from the list. Note: the first index in a list is 0.

Examples:

If you have local variables: [$primes={2,3,5,7,11}] and [$indices={1,3,3,1}] then:

Examples:

You can also use negative indices to indicate an offset from the end of the list. -1 indicates the last item, -2 indicates the second last and so on.

[include("contents")]

Loops

A foreach statement is used to repeat part of a page once for each value in a list. It takes the form: [[foreach {variable} in {list}]]{body}[[end]]. {variable} is the name of a variable (usually a local variable). {list} is a list of values. {body} will be processed and the results displayed once for each member of {list}. Each time {body} is processed, {variable} will be set to the corresponding member.

Example:

<ul>
[[foreach $n in {1,1,2,3,5}]]
    <li>[[$n]]</li>
[[end]]
</ul>
            

Result:

{variable} can also be a list of variables.

Example:

[[foreach {$name, $greeting} in {{"James","Hi"},{"Ben","Hey"},{"David", "Hello"}}]]
    <p>[[$name]] says [[$greeting]]</p>
[[end]]
            

Result:

[foreach {$name, $greeting} in {{"James","Hi"},{"Ben","Hey"},{"David", "Hello"}}]

[$name] says [$greeting]

[end] [include("contents")]

List Functions

StoryTeller has a few built-in functions for working with lists:

In the following example $inventory is a list of object names:

[[$sublist=exceptLast($inventory)]]
[[if(none($inventory))]]
    You are not carrying anything.
[[else if(none($sublist))]]
    You are carrying [[first($inventory)]].
[[else]]    
    You are carrying 
    [[foreach $item in $sublist]]
        [[$item]],
    [[end]]
    and [[last($inventory)]].    
[[end]]
            

If $inventory is empty then the reader will see "You are not carrying anything." If $inventory is {"an apple","five oranges","some grapes"} then the reader will see "You are carrying an apple, five oranges, and some grapes."

peel, peelFirst and peelLast

peel({list}) returns a list with three values {First, Middle, Last}. First and Last are (obviously) the first and last items in {list}. Middle is a list containing all of the remaining items. For example [[{$f,$m,$l}=peel({1,2,3,4,5})]] will set $f to 1, $m to {2,3,4} and $l to 5.

peelFirst({list}) returns a list with two values {First, Remaining}. First is the first item in {list}. Remaining is a list containing all of the remaining items. For example [[{$f,$r}=peelFirst({1,2,3,4,5})]] will set $f to 1 and $r to {2,3,4,5}. Similarly, peelLast({list}) returns the list {Remaining, Last} where Last is the last item in {list} and Remaining is all of the remaining items. [[{$r,$l}=peelLast({1,2,3,4,5})]] will set $r to {1,2,3,4} and $l to 5.

Note: in these functions, if First or Last do not exist (if the list is too short) they will be set to null.

The following is an alternative way to display the $inventory list:

[[{$fst,$mid,$lst}=peel($inventory)]]
[[if($fst)]]
    You are carrying [[$fst]]
[[else]]
    You are not carrying anything.
[[end]]
[[foreach $item in $mid]]
    , [[$item]]
[[end]]
[[if($lst)]]
	and [[$lst]].
[[end]]
			

Note: this example has been broken up onto multiple lines for readability. If copied as-is it will result in spaces appearing before each comma. To avoid this, remove the line breaks.

index and mark

index({list}) returns a list, in which each item in {list} is replaced by a list with three values: {Item, I, N}. Item is the original item, I is the index within {list} and N is the negative index.

For example, index({"A","B","C"}) gives the following list: (line breaks and indentation are just for readability)

{
    {"A",0,-3},
    {"B",1,-2},
    {"C",2,-1}
}
            

Similarly, mark({list}) returns a list in which each item is replaced by a list: {Item, IsFirst, IsLast}. Once again, Item is the original Item. IsFirst is a boolean value which is true for the first item and false for all other. IsLast is also a boolean value, true for the last and false for all other.

For example, mark({"A","B","C"}) gives the following list:

{
    {"A",true,false},
    {"B",false,false},
    {"C",false,true}
}
            

Here is yet another way to display the contents of $inventory:

You are 
[[foreach {$item,$f,$l} in mark($inventory)]]    
    [[if($f)]]carrying
    [[else]][[if($l)]] and [[else]], [[end]][[end]]
    [[$item]]
[[end]]
[[if(none($inventory))]]
    not carrying anything
[[end]].
            
[include("contents")]

Sets

A set is a group of things. For example the set of characters in your story. In StoryTeller, sets are constant. You define them once and then items can be neither added nor removed.

You define sets in the <script> block at the top of the HTML file (along with your variables). The "Set" constructor takes a list of items in curly brackets ('{' and '}'). Each item will need a name to identify it.

Example:

var people = new Set(
    {name: "Brian"},
    {name: "James"},
    {name: "Ben"},
    {name: "Declan"},
    {name: "David"}    
);
			

To make the set available in your story to need to define the "sets" array. This is similar to the variables array:

sets = [[
    {name: "people", set = people}
]];
			

Once you have defined your set you can use it in a foreach loop:

<ol>
    [[foreach $name in people()]]
        <li>[$name]%lt;/li>
    [[end]]
</ol>
			

This will list all of the names in the set "people".

Each member of a set can also be represented by its index. This allows you to store a reference to a particular member of a set in a variable. To get the index, pass the name of the item to the set. For example: [[personId=people("James")]] will store the index of "James" in the variable called personId. To retrieve the name, just pass the index to the set. For example: [[people(personId)]] will display James

Indices start from 0 and the maximum value is 1 less than the size of the set.

[include("contents")]

Sets As Lookup Tables

You can attach extra information to the members of a set and retrieve these values in your stories. You can store any "{key}":{value} pairs you like alongside the name.

Example

var people = new Set(
    {name: "Brian", "height": 170, "hair colour": "brown"},
    {name: "James", "height": 180, "hair colour": "dark brown"},
    {name: "Ben", "height": 160},
    {name: "Declan", "height": 180, "hair colour": "black"},
    {name: "David", "height": 150, "hair colour": "grey"}    
);
			

To access the values pass the item name and the key to the set. For example: [[people("Declan","height")]] will display 180. You cannot look up a key which does not exist. For example: [[people("Ben","hair colour")]] will result in an error.

You can also look up values with the index of the item in place of the name. For example: [[people(0,"hair colour")]] will display "brown".

[include("contents")]

Relations

A relation describes some fact for all of the members of one or more sets. One example is a relation between the sets of characters and objects in a story which describes whether or not a character is holding a particular item. The "isHolding" relation holds either true or false for every combination of character and object

You define relations in the <script> block at the top of the HTML file (along with your variables and sets). The "Relation" constructor is called with the list of sets it refers to.

Example:

var characters = new Set(
    {name = "Dorothy"},
    {name = "Scarecrow"},
    {name = "Lion"},
    {name = "Tin Man"},
    {name = "Wizard"}
);

var objects = new Set(
    {name = "ruby slippers"},
    {name = "diploma"},
    {name = "medal"},
    {name = "pocket watch"}
);

var isHolding = new Relation(characters, objects);
            

This is just one example. Relations can have any number of sets, including one. You could have a relation which records which characters are still alive: var isAlive(characters);.You also might have a relation which records all of the places any two characters have met: var metAt = new Relation(characters, characters, places);

To make the relation available in your story to need to define the "relations" array. This is similar to the variables and sets arrays:

relations = [[
    {name: "isHolding", relation = isHolding}
]];
			

You can now refer to entries in the relation by passing the names of items to the relation. For example: isHolding("Wizard","ruby slippers"). This can be used to both set and retrieve the entry. For example: [[isHolding("Dorothy","ruby slippers")=true]] will set the isHolding relation to true for the pair "Dorothy" and "ruby slippers" (all entries start off as false). If you now use this fact in an if block [[if(isHolding("Dorothy","ruby slippers"))]]...[[end]] it will resolve to true and the block will be processed.

It is also possible to ask a relation to search for combinations which are true. You do this by passing in null in place of the unknown items. The result will be a list of the combinations of unknowns which give a true result. You can use this result in a foreach loop.

When there is a single unknown then the result is a list of names:

<p>Dorothy is holding:</p>
<ul>
    [[foreach $object in isHolding("Dorothy", null)]]
        <li>[[$object]]</li>
    [[end]]
</ul>
            

When there are multiple unknowns then the result is a list of lists:

<ul>
    [[foreach {$name, $object} in isHolding(null, null)]]
        <li>[[$name]] is holding the [[$object]]</li>
    [[end]]
</ul>  
            
[if(detail)]

The following might get slightly technical. It's not that important and you can skip it if you like. [link("relations")][detail=false]Click here to hide the technical stuff.[end]

Like variables, relations are stored in the address bar. They need 1 bit for every possible combination of elements. To find out the total size of a relation, multiply the sizes of all of its sets together.

For example: If the characters set has 7 elements and the places set has 40 then the metAt relation described above will take up 7*7*40=1960 bits. As each character can represent 6 bits, this will take up 327 characters in the address bar. Clearly large relations are to be used sparingly.

[else]

There are some slightly technical details hidden here. You don't really need to read it. [link("relations")][detail=true]Click here to show the technical stuff.[end]

[end] [include("contents")]

Custom Functions

If you have complex logic to include in your story you can define your own functions using JavaScript. Functions are defined in a similar way to variables, sets and relations, in the script block at the top of the HTML file.

Example:

function integerToOrdinal(n) {
  if(Math.floor(n/10)%10!=1) {
    switch(n%10) {
	  case 1:
	    return n + "st";
	  case 2:
	    return n + "nd";
	  case 3:
	    return n + "rd"
    }
  }
  return n + "th"
}	
			
functions = [[
  { name : "ordinal", operation: integerToOrdinal}
]];           
            

This will define a function called "ordinal" which converts an integer (1, 2, 3) to an ordinal string (1st, 2nd, 3rd).

You can then use this function in your pages:

<div data-page-id="finishLine">
  "Congratualtions." an official says as you cross the finish line. "You're [[ordinal(place)]]"
</div>
			

Note: When lists are passed in as parameters to a custom function they become arrays. Similarly, if a custom function returns an array it will be treated as a list.

When you are defining your own functions it is important to remember only variables which are defined in the "variables" array will be managed by StroyTeller. If you use global variables which are not managed by StoryTeller then your story will have unexpected behaviour when if the reader uses the browser's back button or attempts to return to a bookmarked location.

To access your variables, keep a reference the object in the variable's "value" property and use its .get() and .set({value}) methods in your function.

Example:

var globalNumber = new NumberVariable(0);

var variables = [[
  { name : "myNumber", value : globalNumber }
]]; 

function multiplyGlobalNumberBy(x) {
	var temp = globalNumber.get() * x;
	globalNumber.set(temp);
}

var functions = [[
  { name : "multiplyMyNumber", operation: multiplyGlobalNumberBy}
]];           
			

If you need to maintain a value for use in your functions but have no use for it in the pages of your story you can simply not give it a name.

Example:

var globalNumber = new NumberVariable(0);

var variables = [[
  { value : globalNumber }
]];     
			

StoryTeller uses its own random number generator which ensures the random events in a page are the same when the reader returns to some point in the story either via the back button or a bookmark. This means that you should not use JavaScript's built-in random number generator (Math.random()). Instead, use the methods provided by StoryTeller's "random" object.

[include("contents")]

Sets and Relations in Custom Functions

Sets can be used within your custom functions. Each set exposes the following methods:

Relations can also be accessed. They expose these methods:

[include("contents")]

Block Functions

For more complex custom behaviors you can use block functions. These have two important differences from normal custom functions.

Firstly, these functions take a block of HTML and StoryTeller statements as one of their parameters. When they are used, the start of this block is indicated by the function name and the end is indicated by [[end]].

Example:

[[myBlockFunction($myVariable)]]
	<p>This is the block</p>
	[[if(showDetail)]]<p>The block can include StoryTeller statements</p>[[end]]
[[end]]
			

Secondly, the parameters are not evaluated before passing them to your function. You decide if, when and even how many times the block is executed and the parameters are evaluated. To execute the block, call its .execute() function. The text it produces will be returned. To evaluate the other parameters, use their .evaluate() method.

The block is passed to your function as its first parameters, then the normal parameters follow.

To tell StoryTeller that a function is a block function include "blockFunction: true" in the definition.

The following example defines a block function which will act like a for loop:

function forLoop(block, init, condition, update) {
	init.evaluate();
	
	var body = "";
	
	while(condition.evaluate()) {
		body += block.execute();
		update.execute();
	}
	
	return body;
}

var functions = [[
  { name : "for", operation: forLoop, blockFunction: true}
]];   
			

You can then use it in your pages like this:

<p>The coundown started.</p>
[[for($i=10,$i greaterThanOrEqual 0,$i=$i-1)]]
	<p>"[[$i]]"</p>
[[end]]
<p>"Blast Off!"</p>
			

You can also assign values to the parameters with their .assign({value}) method.

Example:

function forEachChar(block, valueParam, textParam) {	
	var body = "";
		
    var text = textParam.evaluate();
        
	for(var i=0;i<text.length;i++) {
		valueParam.assign(text[[i]]);
		body += block.execute();
	}
	
	return body;
}

var functions = [[
  { name : "forEachChar", operation: forEachChar, blockFunction: true}
]];

...

<div data-page-id="start">
  <p>
  The letters ran vertically down the side of the building:
  [[forEachChar($char, "THEATRE")]]
	<br/>[[$char]]
  [[end]]
  </p>
</div>
			
[include("contents")]

Custom Variable Types

If the need arises you can define new types of variables. The only requirement is that they have a constant size. That is, every value which the variable can hold can be represented by the same number of bits.

A variable must provide 3 methods in order to be stored in the address bar:

Note: The length of the unsigned 16bit integer arrays should be size/16 rounded up to the nearest whole number. For example, if .size() is 40 then the arrays should have length 3 (40/16=2.5). When size is not a multiple of 16 only the low size%16 bits of the last integer will be used.

Two more methods are required if you want to refer to your variable in your pages. These are unnecessary if you will only be using the variable in your own custom functions.

The following is an example of a custom variable to hold coordinates in a 1024 by 1024 grid:

var readerCoordinates = {
  x : 0,
  y : 0,
  
  size : function() {return 20;},
  
  toInt16Array : function() {
   var ints = [[]];
   ints[[0]] = (this.x & 0x03FF) | ((this.y & 0x003F) << 10);
   ints[[1]] = (this.y & 0x03FF) >> 6;
   return ints;
  },
  
  fromInt16Array : function(ints) {
    this.x = ints[[0]] & 0x03FF;
    this.y = (ints[[0]] >> 10) | (ints[[1]] << 6);
  }
}

var variables = [[
  { value : readerCoordinates }
]]; 
            
[include("contents")]