Intro

This document will guide you in the creation of your own interactive story using the gamebook engine.

While progressing through your story, a reader will see limited portions of text (or other HTML content) at a time; these are called scenes. As the writer, you decide how to break your story into scenes.

Your story should also include special links which generally perform one of the following actions when clicked (these are called click actions):

  • Change to a new scene
  • Add new content to the current scene
  • Remove certain content from the current scene
  • Replace certain content in the current scene with new content
  • Many other actions including saving/loading, setting variables, and running custom JavaScript if desired

Most actions can also be assigned to run automatically as soon as certain content is loaded into a scene (these are called load actions). Additionally, content within a scene can conditionally show or hide itself depending on variable conditions (these are logic blocks).

The Story File

A story file is an HTML file (such as the example file sample-story.html) which contains all the parts necessary to generate your story.

The head element of your HTML file must include references to the following components:

jquery.min.js
helper library on which the gamebook engine depends (example: <script src="jquery.min.js"></script>)
engine.js
the gamebook engine itself (example: <script src="engine.js"></script>; must be placed after jQuery reference above)
style.css
stylesheet which selectively hides certain story elements (example: <link rel="stylesheet" href="style.css">)
Story ID

Additionally, you should give your story a unique story ID so the save/load data in your story doesn’t overlap with other story files (assuming you will offer save/load functionality in your story). To assign a story ID, assign a data-storyid attribute to the head element of your HTML file (example: <head data-storyid="jsmith001">). You may use any sequence of numbers and letters in your story ID (spaces and symbols are allowed), but avoid repeating the same ID between your stories (if your first story was jsmith001, for instance, increment your second story to jsmith002).

Body

The body element of your HTML file must include two non-overlapping block elements (such as div elements) using the following ID tags:

scene
container where the story’s contents will generate and evolve as the story progresses (example: <div id="scene">This content will be overwritten</div>)
canon
hidden source material from which the story will be generated (example: <div id="canon">Your canon material goes here</div>)

Writing Canon

Canon (that is, all content placed within the aforementioned canon element) should be written in HTML format and broken up into various container elements (div, p, span, etc.) which will serve as passages for the gamebook engine to load in response to certain actions. Passages may be written out of chronological order, and they may even be nested as desired.

Marking Passages

There are a few ways canon passages can be located by gamebook actions (see Selecting Passages), but an easy way is to mark your passages with class attributes (example: <div class="library">). It’s helpful for class names to be unique, but this isn’t required (depending on how you intend to select them).

Multiple class names can be assigned to a single element if desired (separate with spaces). Click here for more information about valid class names (also see Class Capitalization).

Avoid using the id attribute (ID uniqueness standards would be violated as the gamebook engine copied passages from canon into the scene container).

Start

When your HTML file is opened in a browser, it will load the first canon element it finds marked with the class name start. If no such element is found, the game will load the first element in the canon (and any child elements nested within it).

Styles

The stylesheet style.css is freely customizable (although modifying or removing styles in its “Gamebook Mechanics” section may render the gamebook unplayable).

Scene Headers

Scene headers (such as to show a location title at the top of a scene) are expected to be formatted as h3 headers (example: <h3>Library</h3>), but this isn’t mandatory.

Comma Series

Among the styles provided in the default stylesheet, the class comma-series can be applied to a container element to render its child elements as an inline series (example: <ul class="comma-series"><li>A<li>B<li>C</ul> produces A, B, and C). Use class="comma-series or" to use the conjunction “or” instead.

Note that even when they use the comma-series class, ul elements will always be ejected from paragraph (<p>) elements due to HTML standards. To sidestep this behavior, you may substitute <div class="p"> in place of <p> to ensure your comma series list stays inline with other text. Alternately, you may simply use span elements, which will not be ejected from standard paragraphs (example: <span class="comma-series"><span>A</span><span>B</span><span>C</span></span> produces A, B, and C with no risk of ejection from a paragraph).

Click Actions

Links which navigate and perform other actions within your story won’t be links to other webpages, but they will still use the <a> element. Instead of using the traditional href attribute in your link, use one or more of the following attributes to write click actions into your story (use semicolons to separate multiple parameters within an attribute).

data-click

Parameter(s): canon passage for scene change
Load Actions: new scene’s load actions run
Example: <a data-click="forest-entrance">Enter the forest</a>
Use: When the reader clicks the link, the scene will change to the specified canon passage (see Selecting Passages). If multiple passages are specified (separated with semicolons), the gamebook engine will choose one randomly. If the target passage isn’t found, the reader will be notified that that part of the story hasn’t been written yet.

data-click-switch

Parameter 1: JavaScript expression
Parameter 2: target if expression is true
Parameter 3: target if expression is false
Load Actions: new scene’s load actions run
Example: <a data-click-switch="Variables.isDrunk; morgue; grocery-store">Drive away</a>
Use: When the reader clicks the link, the scene will change to one of two passages, depending on the truth state of the provided expression (see Variables). If the target passage isn’t found, the reader will be notified that that part of the story hasn’t been written yet. Defers to data-click if that action exists in the same link.

data-click-back

Parameter (optional): fallback canon passage
Load Actions: new scene’s load actions run
Example: <a data-click-back>Exit inventory</a>
Use: When the reader clicks the link, the scene will change to the most recent bookmark. If the reader has no available bookmark history, the optional fallback scene will be used; failing that, the reader will be notified he/she can’t go back any further. This action does not revert story variables (it isn’t an undo command). Defers to data-click or data-click-switch if either action exists in the same link.

data-click-refresh

Parameter(s) (optional): scene passage(s) to refresh*
Load Actions: load actions rerun in refreshed passage(s)
Example: <a data-click-refresh="lake-description">Look again</a>
Use: When the reader clicks the link, the specified scene passage will re-evaluate all of its logic blocks and load actions (and those of its child items). Refreshes the entire scene if no passage is specified; does nothing if a passage is specified but isn’t found in the scene. Refreshes multiples if the passage is found more than once in the scene.

data-click-remove

Parameter(s) (optional): scene passage(s) to remove*
Load Actions: none
Example: <a data-click-refresh="lake-description" data-click-remove>Look once more</a>
Use: When a reader clicks the link, the specified scene passage will be removed from the scene. If no passage is specified, the element containing this attribute (the link itself) is removed. Does nothing if a passage is specified but isn’t found in the scene. Removes multiples if the passage is found more than once in the scene.

data-click-setvar

Parameter 1: variable name
Parameter 2: JavaScript expression
Parameter 3 (optional): scene passage to refresh*
Load Actions: none, unless a scene passage is refreshed
Example: <a data-click-setvar="Variables.boyName; 'Timmy'; boy-paragraph" data-click-remove>Ask his name</a>
Use: When the reader clicks the link, the specified variable will be set to the result of the specified expression. Optionally refreshes a scene passage after changing the variable. Does nothing if no variable name is specified.

data-click-setrandom

Parameter 1: variable name
Parameter 2 (optional): number of choices
Parameter 3 (optional): scene passage to refresh*
Load Actions: none, unless a scene passage is refreshed
Example: <a data-click-setrandom="Variables.signDirection; 4; the-sign">Spin the sign</a>
Use: When the reader clicks the link, the specified variable will be set to a random integer from 0 to N-1, where N is the specified number of choices (example: using 4 as the number of choices could result in 0, 1, 2, or 3). If no number of choices is specified, the variable will be set randomly to 0 or 1. Optionally refreshes a scene passage after changing the variable. Does nothing if no variable name is specified.

data-click-addtovar

Parameter 1: variable name
Parameter 2 (optional): amount to add
Parameter 3 (optional): scene passage to refresh*
Load Actions: none, unless a scene passage is refreshed
Example: <a data-click="forest-entrance" data-click-addtovar="Variables.timesInForest; 1">Enter the forest</a>
Use: When the reader clicks the link, the specified variable will increase by the specified amount (or decrease, if the amount is negative). If the variable was previously unassigned, it will be given the amount as its new value. If no amount is specified, the variable will be increased by 1. Optionally refreshes a scene passage after changing the variable. Does nothing if no variable name is specified.

data-click-macro

Parameter(s): canon passage(s)
Load Actions: canon passage’s load actions run in current scene
Example: <a data-click-macro="eat-magic-apple">Take a bite</a>
Use: When the reader clicks the link, all load actions in the specified canon passage(s) (and in child elements therein) will be performed on the current scene. If multiple passages are specified (separated with semicolons), the gamebook engine will perform load actions from all of them (in the specified order).

data-click-run

Parameter(s): JavaScript expression(s)
Load Actions: can vary depending on expressions (but normally none)
Example: <a data-click-run="alert('That\'s actually a horrible idea.')">Poke the bear</a>
Use: When the reader clicks the link, the expression(s) will be run. If multiple expressions are used, each will be run in sequence; if an evaluation error occurs, evaluation will stop at the point the error occurred.

data-click-prepend

Parameter 1: canon passage to prepend
Parameter 2 (optional): scene passage to prepend to*
Parameter 3 (optional): outside or inside (default: inside)
Load Actions: new passage’s load actions run
Example: <a data-click-prepend="the-word-apparently; the-word-dead; inside" data-click-remove>But could he really be...?</a>
Use: When the reader clicks the link, the specified canon passage will be prepended to the specified scene passage (or to the scene as a whole if no scene passage is specified). When the third parameter is specified as outside, the prepend will occur outside the targeted scene passage rather than inside it (inside could add a new sentence within a paragraph, while outside could add a new paragraph before it). Ignores outside if the scene passage is unspecified (you can’t prepend outside the scene container itself). Prepends multiple times if the scene passage (if specified) is found more than once in the scene. Does nothing if the specified canon passage isn’t found.

data-click-append

Parameter 1: canon passage to append
Parameter 2 (optional): scene passage to append to*
Parameter 3 (optional): outside or inside (default: inside)
Load Actions: new passage’s load actions run
Use: Like data-click-prepend, but appends (adds to the end) instead.

data-click-replace

Parameter 1: scene passage to replace*
Parameter 2: canon passage to replace with
Load Actions: new passage’s load actions run
Example: <a data-click-replace="window-unbroken; window-broken" data-click-remove>Throw rock</a>
Use: When the reader clicks the link, the specified scene passage will be replaced with the specified canon passage. Does nothing if the specified scene passage isn’t found; if the canon passage isn’t found, the scene passage will be removed. If multiple scene passages are found, all are replaced with their own copies of the canon passage.

data-click-become

Parameter(s): canon passage to replace with
Load Actions: new passage’s load actions run
Example: <a data-click-become="ordinary-rock">somewhat suspicious rock</a>
Use: When the reader clicks the link, the link itself will be replaced with the specified canon passage. If multiple passages are specified (separated with semicolons), the gamebook engine will choose one randomly. If the canon passage isn’t found, the link will be removed.

data-click-deactivate

Parameter(s) (optional): scene passage(s) containing link(s) to deactivate*
Load Actions: none
Example: a badly-worn <a data-click-append="rug-key" data-click-deactivate>rug</a> in the center of the room
Use: When the reader clicks the link, any links (or elements marked with the class gamebook-click-action) in the specified scene passage will be transformed into unclickable text spans; the links will also lose their load actions (if applicable). If no passage is specified, the element containing this attribute (the link itself) is deactivated. Does nothing if a passage is specified but isn’t found in the scene, or if the specified passage contains no links. Deactivation strips out all attributes from their respective links except class name(s).

data-click-resetstory

Parameter 1 (optional): alternate starting canon scene
Parameter 2 (optional): quiet
Load Actions: new scene’s load actions run after reset
Example: <a data-click-resetstory="start2; quiet">Try again</a>
Use: When the reader clicks the link, the story will be reset to its original state: bookmarks are cleared, all variables in the Variables namespace are erased, and the reader returns to the starting passage (or an alternate scene if specified). If the second parameter is the word quiet, the reader will not receive a confirmation dialog asking whether he/she wishes to reset.

data-click-resetbookmarks

Parameters: none
Load Actions: none
Example: <a data-click-resetbookmarks>Forget where you've been</a>
Use: When the reader clicks the link, his/her bookmark history will be cleared.

data-click-savestory

Parameters: none
Load Actions: none
Example: <a data-click-savestory>Save your progress</a>
Use: When the reader clicks the link, his/her progress in the story will be saved to the browser’s local storage, to be re-loaded later (see below). The current scene, story variables, and bookmark history will all be saved. If this story uses the same story ID as another story, this action will overwrite that story’s save data (with peculiar results if that story then tries to load this data).

data-click-loadstory

Parameters: none
Load Actions: none
Example: <a data-click-loadstory>Restore your progress</a>
Use: When the reader clicks the link, his/her progress in the story will be loaded from the browser’s local storage (if anything was previously saved). Load actions in the scene being restored will not be re-run (scene contents will remain as they appeared when the story was last saved). If the story ID has been changed since the reader last saved, the old save data will not load.

* Parameters above marked with an asterisk may use ^ parent selectors.

If the same link item contains multiple click attributes, they will be executed in the following order:

data-click-resetstory
data-click-loadstory
data-click-setvar
data-click-setrandom
data-click-addtovar
data-click-macro
data-click-run
data-click-become
data-click / data-click-switch / data-click-back
data-click-prepend
data-click-append
data-click-replace
data-click-deactivate
data-click-refresh
data-click-remove
data-click-resetbookmarks
data-click-savestory

Click-action links may be placed in locations outside the canon, such as in a sidebar. Note, however, that no actions allow modification of content outside the scene container (except if you do so with custom JavaScript in a data-click-run action).

Links in your page will be treated as normal links if they contain an href attribute (the gamebook engine usually only checks for click actions on links lacking href). These normal links are affected by the data-click-deactivate and data-load-deactivate actions.

Other Clickable Elements

To force the engine to check other types of elements for click actions (as it would for links without the href attribute), use the class name gamebook-click-action (example: <button class="gamebook-click-action" data-click-back>Back</button>). Any such elements are affected by the data-click-deactivate and data-load-deactivate actions.

Logic Blocks

When an HTML element containing one of the following logic attributes is loaded, the truth state of the element is evaluated to determine whether the element and its contents should be visible in the scene (and whether load actions therein should run).

data-logic-if

Parameter: JavaScript expression
Example: <p data-logic-if="Variables.timesInHouse > 1">Welcome back!</p>
Use: When the element is loaded, the expression is evaluated (see Variables); if its result is false, the element and any elements it contains are hidden, and any load actions therein are not performed.

data-logic-elseif

Parameter: JavaScript expression
Example: <span data-logic-if="Variables.defeatedBoss">It's difficult to make your way through the crowd of adoring fans.</span><span data-logic-elseif="Variables.defeatedMiniBoss">The village now seems to have an air of cautious optimism.</span>
Use: Selectively appears similarly to data-logic-if, except it will be false if a prior sibling if or elseif evaluated true.

data-logic-else

Parameters: none
Example: You're feeling <span data-logic-if="Variables.health >= 100">great</span><span data-logic-elseif="Variables.health >= 50">okay</span><span data-logic-else>lousy</span>.
Use: Selectively appears only if no prior sibling if or elseif evaluated true.

Attribute: data-logic-result

Parameter: true or false
Use: Used internally by the gamebook engine to store the results of if, elseif, and else processing in the current scene. Will be automatically cleared out if you write it into your canon.

Dead Ends

Avoid using logic blocks in a canon passage which receives direct links from data-click or data-load-goto, as the reader could be stranded if the new scene’s container element evaluates as false (the scene would be blank with no links to escape).

Hidden Sections

You may find it useful to embed hidden containers into your canon passages to store elements which will be brought into the scene. For example, suppose your canon contained a living-room div which contained starting text and some initial links which could bring in more content when clicked. For organizational purposes, you might choose to place a div with a data-logic-if="false" attribute (or just data-logic-if with no expression) inside the living-room div, and then place other resource elements inside the hidden div. This would allow links in living-room to pull in the necessary resources without the resources appearing in the passage prematurely.

Of course, you can also place the resource elements outside of the living-room div, but the hidden div method allows you a different way to organize your canon.

Load Actions

When an HTML element is loaded into the current scene, any load actions it contains are performed (provided it isn’t inside a false logic block). The following attributes are used to assign load actions (use semicolons to separate multiple parameters within an attribute):

data-load

Parameter(s): canon passage to load
Additional Load Actions: new passage’s load actions run
Example: <div data-load="sky-description">(these contents will be replaced)</div>
Use: When loaded, this element will appear with its contents replaced with the specified canon passage (that passage will be nested within this element). If multiple passages are specified (separated with semicolons), the gamebook engine will choose one randomly. The loaded canon passage will perform its load actions at this point (beware an infinite loop wherein two passages load each other). This element will appear empty if the specified canon passage isn’t found.

data-load-var

Parameter 1: variable name
Parameter 2 (optional): fallback text
Additional Load Actions: none
Example: Your health is at <span data-load-var="Variables.health; ??"></span>%.
Use: When loaded, this element will replace its contents with the current value of the named variable. If the specified variable is undefined or empty, uses the fallback text if available (otherwise the element will be empty).

data-load-array

Parameter 1: array variable name
Parameter 2 (optional): fallback text
Additional Load Actions: none
Example: On the wall, you see <span class="inline" data-load-array="Variables.paintings; nothing of interest"></span>.
Use: When loaded, this element will replace its contents with list items (li items if this element is ul or ol, otherwise span items) corresponding to the contents of a JavaScript array. Can be used with CSS which renders elements as a comma-separated series. If the specified variable is undefined or empty, uses the fallback text as a list item if available (otherwise the element will be empty). If the variable isn’t an array, the variable’s contents will become a single list item.

data-load-switch

Parameter 1: JavaScript expression
Parameter 2: text to use if expression is true
Parameter 3: text to use if expression is false
Additional Load Actions: none
Example: <span data-load-switch="Variables.motorIsOn; whirs busily; sits quietly"></span>
Use: When loaded, this element will appear with its contents replaced with one of two text strings, depending on the truth state of the provided expression. For more complex logical constructs, use logic blocks.

data-load-expression

Parameter(s): JavaScript expression(s)
Additional Load Actions: can vary depending on expressions (but normally none)
Example: The sign reads, "GO <span data-load-expression="Variables.signDirectionName.toUpperCase()"></span>!"
Use: When loaded, this element will appear with its contents replaced with the expression’s result (or if multiple expressions are entered, the result of the last expression executed). This element will appear empty if an expression fails to evaluate.

data-load-run

Parameter(s): JavaScript expression(s)
Additional Load Actions: can vary depending on expressions (but normally none)
Example: <div data-load-run="alert('This next part gets a little scary.')"></div>
Use: Like data-click-run, but triggered when this element is loaded.

data-load-macro

Parameter(s): canon passage(s)
Additional Load Actions: canon passage’s load actions run in current scene
Example: <div data-load-macro="beach-actions"></div>
Use: Like data-click-macro, but triggered when this element is loaded.

data-load-setvar

Parameter 1: variable name
Parameter 2: JavaScript expression
Parameter 3 (optional): scene passage to refresh
Additional Load Actions: none, unless a scene passage is refreshed
Example: <span data-load-setvar="Variables.hasBeenInForest; true">So... this is the forest.</span>
Use: Like data-click-setvar, but triggered when this element is loaded.

data-load-setrandom

Parameter 1: variable name
Parameter 2 (optional): number of choices
Parameter 3 (optional): scene passage to refresh
Additional Load Actions: none, unless a scene passage is refreshed
Example: <div data-load-setrandom="Variables.numberOfBeans; 3"></div>
Use: Like data-click-setrandom, but triggered when this element is loaded. (Remember the result is a random integer from 0 to N-1, where N is the specified number of choices.)

data-load-addtovar

Parameter 1: variable name
Parameter 2 (optional): amount to add
Parameter 3 (optional): scene passage to refresh
Additional Load Actions: none, unless a scene passage is refreshed
Use: Like data-click-addtovar, but triggered when this element is loaded.

data-load-replace

Parameter 1: scene passage to replace*
Parameter 2: canon passage to replace with
Additional Load Actions: new passage’s load actions run
Use: Like data-click-replace, but triggered as soon as the element is loaded.

data-load-become

Parameter(s): canon passage to replace with
Additional Load Actions: new passage’s load actions run
Use: Like data-click-become, but triggered as soon as the element is loaded.

data-load-deactivate

Parameter(s): scene passage(s) to deactivate*
Additional Load Actions: none
Use: Like data-click-deactivate, but triggered as soon as the element is loaded. Does nothing if no passage is specified.

data-load-prepend

Parameter 1: canon passage to prepend
Parameter 2 (optional): scene passage to prepend to*
Parameter 3 (optional): outside or inside (default: inside)
Additional Load Actions: new passage’s load actions run
Use: Like data-click-prepend, but triggered as soon as the element is loaded.

data-load-append

Parameter 1: canon passage to append
Parameter 2 (optional): scene passage to append to*
Parameter 3 (optional): outside or inside (default: inside)
Additional Load Actions: new passage’s load actions run
Use: Like data-click-append, but triggered as soon as the element is loaded.

data-load-refresh

Parameter(s) (optional): scene passage(s) to refresh
Additional Load Actions: load actions rerun in refreshed passage(s)
Use: Like data-click-refresh, but triggered as soon as the element is loaded. If no passage is specified, the element will refresh the entire scene but lose its ability to perform further refreshes in order to avoid an infinite refresh loop. (Caution: You may still trigger an infinite refresh loop if you specify a scene passage which contains this element.)

data-load-remove

Parameter(s) (optional): scene passage(s) to remove*
Additional Load Actions: none
Use: Like data-click-remove, but triggered as soon as the element is loaded.

data-load-goto

Parameter(s): canon passage for scene change
Additional Load Actions: new scene’s load actions run
Example: <span data-logic-if="!Variables.powerOn" data-load-goto="livingRoomDark"></span>
Use: Like data-click, but triggered as soon as the element is loaded (essentially a redirect). If multiple passages are specified (separated with semicolons), the gamebook engine will choose one randomly. Once this element is loaded and the scene change is triggered, subsequent load actions in the scene being departed will not run. Bookmark history will retain both the redirecting scene and the redirect target if neither uses data-load-nobookmark. Does nothing if the specified passage isn’t found.

data-load-nobookmark / data-nobookmark

Parameters: none
Additional Load Actions: none
Example: <div class="inventory-menu" data-nobookmark>...</div>
Use: When loaded directly via data-click or data-load-goto, this scene will not log itself as a bookmark. You may use data-load-nobookmark and data-nobookmark interchangeably. Does nothing if used in a passage which is never loaded directly for a scene change.

data-load-resetbookmarks

Parameters: none
Additional Load Actions: none
Example: <span data-load-resetbookmarks></span>
Use: Like data-click-resetbookmarks, but triggered as soon as the element is loaded.

data-load-savestory

Parameters: none
Additional Load Actions: none
Example: <span data-load-savestory></span>
Use: Like data-click-savestory, but triggered as soon as the element is loaded. Unlike data-click-savestory, this action is performed without any notification dialog.

data-load-skip

Parameters: none
Additional Load Actions: none
Example: <span data-load-macro="the-explosion" data-load-skip>And then it happened...</span><--skip the explosion for now-->
Use: Used internally by the gamebook engine to record whether actions have been halted in the current scene (such as when data-load-goto is encountered). You may also use it to turn off an element’s load actions (for instance, while debugging your story). Does not skip logic block evaluations.

* Parameters above marked with an asterisk may use ^ parent selectors.

Within a loaded or refreshed passage, load actions are executed in the order in which their respective elements appear in the passage. If the same element contains multiple load actions, the order of events is as follows:

data-logic-if / data-logic-elseif / data-logic-else
data-load-skip
data-load-resetbookmarks
data-load-setvar
data-load-setrandom
data-load-addtovar
data-load-macro
data-load-run
data-load
data-load-var
data-load-array
data-load-expression
data-load-switch
data-load-become
data-load-goto
data-load-prepend
data-load-append
data-load-replace
data-load-deactivate
data-load-refresh
data-load-remove
data-load-savestory

A link element may have both load and click actions assigned; its load actions are executed when the link is first loaded into the scene, and its click actions are executed if/when the link is subsequently clicked.

Load actions will not run in elements outside the scene container, such as elements in a sidebar.

Bookmarks

When the reader arrives at a new scene, the scene is usually stored in a hidden “bookmark” history (unrelated to browser bookmarks/favorites); the data-click-back command is then able to return the reader to the most recently bookmarked scene when clicked. A scene will be be omitted from bookmark history if its primary element (the element which was targeted by data-click or data-load-goto to change to that scene) uses the attribute data-load-nobookmark (or data-nobookmark).

So, for example, if an inventory menu spanned multiple “scenes” and you wished to let the reader return to their prior non-inventory location from any of those inventory scenes, you could mark each inventory scene as data-load-nobookmark; this way, a data-click-back action on any link therein would exit the inventory menus to return to the reader’s prior location.

If your story doesn’t use the data-click-back action (or if your menus and other asides are never more than one “scene” deep), there’s no need to use the data-load-nobookmark attribute.

Selecting Passages

Any click or load action parameter which is intended to refer to a scene or canon passage will accept any of the following inputs:

The parameter is evaluated by the following rules:

  1. If the input begins with the Variables namespace (such as Variables.destination), the variable’s current value is evaluated per the below rules. (This evaluation isn’t recursive: you can’t reference a variable which references another variable.)
  2. If the input consists entirely of one or more caret (^) characters, a parent selection is performed, and no further rules are evaluated for this input.
  3. If the input returns one or more matches using the input as a simple class name (example: ocean-cliff finds elements marked with class="ocean-cliff"), those matches are returned, and no further rules are evaluated for this input.
  4. Finally, the input is evaluated as if it were a CSS selector string (example: .reasons p:nth-of-type(3) finds the third paragraph element nested within an element marked with the class reasons).

Some click and load actions which aren’t using additional parameters for other purposes will accept multiple selectors separated with semicolons. In this event, each selector is evaluated independently per the above rules, and all the matches of those selectors are returned as a result.

If no matches are found, the gamebook engine’s reaction varies according to the action being performed.

Multiple Matches

If the selector(s) find multiple matches, scene and canon searches behave differently:

  • If the scene is being searched, all results will be affected.
  • If the canon is being searched, only the first match is used.

Class Capitalization

When applying and referencing class names, you should assume the reader’s browser will require case-sensitive class name matches (don’t search for library if your canon instead uses the class name Library). Since browsers may vary in this behavior, however, you should also avoid class-insensitive duplicates in your canon (don’t use both library and Library in your canon and expect all browsers to acknowledge the difference).

Parent Selection

Certain click and load action parameters (indicated in their documentation with an asterisk) accept one or more caret (^) characters as a selector to find the parent element containing the scene element being clicked or loaded; a selector of two carets (^^) finds the element’s grandparent element, three (^^^) finds its great-grandparent element, and so on. Using carets as a selector in a parameter which doesn’t support parent selection will return no matches.

If parent selection runs into the main scene container itself at any level of searching, all contents in the current scene will be selected.

Altering the Scene

A number of click and load actions are able to alter the contents of the current scene. Note, however, that none of these changes are made to the canon of your story (the canon is immutable while the story is in play). This means that if, for example, the reader clicks a link which appends a new paragraph to the current scene, this paragraph will be absent (barring special measures on your part) if the reader is able to revisit this scene later.

For scenes to reflect actions the reader has previously performed, your actions must be written to set variables when performed, and your scenes must be written with logic blocks which refer to these variables. Here is an example:

<div class="start">
<h3>Living Room</h3>
<p>
<span data-logic-if="Variables.fanOn">The fan is on. <a data-click-setvar="Variables.fanOn; 0; ^^">Turn it off?</a></span>
<span data-logic-else>The fan is off. <a data-click-setvar="Variables.fanOn; 1; ^^">Turn it on?</a></span>
</p>
<p><a data-click="kitchen">Go to the kitchen</a></p>
</div>

<div class="kitchen">
<h3>Kitchen</h3>
<p>Now try <a data-click-back>going back</a> and making sure the fan is still <span data-load-switch="Variables.fanOn; on; off"></span>.</p>
</div>

In the above, the living room passage contains a paragraph which contains two spans: one which displays when Variables.fanOn is true (1) and the other when it’s false (0 or undefined). Clicking a link in either span changes the variable and also (with the ^^ selector) refreshes its grandparent element (the paragraph containing the span containing the link). When the living room passage is exited and re-entered, it continues to display the proper span by virtue of the variable, which retains its value until the story is reset.

Variables

Variables assigned using various click and load actions will be placed in the Variables namespace; this means that attempting to set the variable hasEatenCake will actually set a variable named Variables.hasEatenCake. Any expression which subsequently needs to reference the variable will need the Variables prefix (Variables.hasEatenCake). Variables and namespaces are case-sensitive (note the capital V in Variables).

Equality

If you intend to compare a variable’s value to some other value rather than simply using it as a boolean truth value, you must use proper JavaScript comparison syntax. Notably, if you’re checking for equality, you must use == (loose equality, ignoring data types) or === (strict equality, including data types).

Text Values

When you use data-click-setvar or data-load-setvar to set the value of a variable, the gamebook engine checks whether your provided expression (the second parameter in your setvar attribute) evaluates as a valid JavaScript expression; if it doesn’t (and if your input doesn’t begin with the Variables namespace), your input is regarded as plain text (for example, this technically works: data-click-setvar="Variables.boyName; Timmy").

The above assumption can be risky, however, if your text values overlap with existing JavaScript global variable names (for instance, you could not assign the text value “Math” using this technique because Math is a native JavaScript object, and its name constitutes a valid JavaScript expression). To ensure your text value is always interpreted as text, use one of the following methods:

  • data-click-setvar="Variables.boyName; &quot;Timmy&quot;" (HTML-coded quotation marks passed as quotation marks to gamebook engine)
  • data-click-setvar="Variables.boyName; 'Timmy'" (single-quotes don’t conflict with attribute’s double-quotes)
  • data-click-setvar="Variables.boyName; 'Timmy O\'Toole'" (apostrophe escaped to avoid conflict with single-quotes)
  • data-click-setvar="Variables.boyName; 'Timmy O&rsquo;Toole'" (fancy apostrophe avoids conflict with single-quotes)
  • data-click-setvar="Variables.boyName; 'Timmy &quot;T.J.&quot; O\'Toole'" (using quotation marks and an apostrophe in the text value)

Arrays

Variables are able to store lists of data in the form of JavaScript arrays. The data-click-run and data-load-run actions are able to run any JavaScript and jQuery functions to manipulate data (including arrays), but the gamebook engine also provides the following convenience functions specifically for dealing with arrays:

Util.isArray()

Parameter: array variable
Example: Util.isArray( Variables.inventory )
Use: Returns true if the variable contains an array (regardless of whether the array contains any elements), otherwise false. Can be used to test whether an array variable was ever initialized.

Util.arrayLength()

Parameter: array variable
Example: Util.arrayLength( Variables.inventory )
Use: Returns the number of items in the array. Returns zero if the array is empty or if the variable doesn’t contain an array.

Util.arrayContains()

Parameter 1: array variable
Parameter 2: value to find
Example: Util.arrayContains( Variables.inventory, "key5" )
Use: Returns boolean true if at least one of the elements in the array is equal to the specified value, otherwise false. Returns false if the array is empty or if the variable doesn’t contain an array.

Util.chooseTextFromArray()

Parameter: array variable
Example: Util.chooseTextFromArray( Variables.inventory )
Use: Returns a text value chosen randomly from an array’s elements. Returns an empty string if the array is empty or if the variable doesn’t contain an array.

Util.listArray()

Parameter 1: array variable
Parameter 2 (optional): separator
Example: Util.listArray( Variables.inventory, " / " )
Use: Returns all elements of the array joined together as a single text string (separated by a separator of your choice; otherwise all will be joined without a separator). Returns an empty string if the array is empty or if the variable doesn’t contain an array. (You can also use data-load-array to display an array as list items.)

Util.addToArray()

Parameter 1: array variable
Parameter 2: value to add
Example: Util.addToArray( Variables.inventory, "mushroom" )
Use: Modifies the array to append an element containing the specified value; the updated array is also returned. If the array already contained the specified value, no change will be made, and the array will be returned as-is. If the variable didn’t contain an array, its value won’t be modified, but this function will still return a new array containing the specified value.

Util.removeFromArray()

Parameter 1: array variable
Parameter 2: value to remove
Example: Util.removeFromArray( Variables.inventory, "mushroom" )
Use: Modifies the array to remove any elements containing the specified value; the updated array is also returned. If the variable didn’t contain an array, its value won’t be modified, and this function will return an empty array.

Util.emptyArray()

Parameter: array variable
Example: Util.emptyArray( Variables.inventory )
Use: Modifies the array to remove all of its elements; an empty array is also returned. If the variable didn’t contain an array, its value won’t be modified, but this function will still return an empty array.

Note that the above functions (being JavaScript) use commas to separate their parameters, as opposed to click and load actions, which use semicolons.

Undefined Arrays

The add, remove, and empty functions listed above are only able to modify a variable’s contents if the variable already contained an array; notably, Util.addToArray() could fail if it’s trying to add a value to a variable which was previously undefined. To avoid this problem, you may use one of three workarounds:

  • Always initialize array variables before a part of your story may need to access those variables, such as at the start of your story. Use two square brackets with nothing (or a space) inbetween to indicate an empty array; for example: data-load-setvar="Variables.myArray; [ ]"
  • Before working with an array, test to make sure its variable contains an array (using Util.isArray); if it doesn’t, initialize it as described above.
  • Always set the variable with the result of your array function (example: data-click-setvar="Variables.myArray; Util.addToArray( Variables.inventory, 'taco' )").

Objects and Properties

Variables can also store JavaScript objects, which could simply consist of pairings of property labels and their respective values. In JavaScript, you may use curly brackets to indicate an object, colons to separate property labels from their values, and commas to separate these property pairings. For example, you could set a monster variable with a name of “Cyclops” (text) and strength of 5 (number): data-load-setvar="monster; { name: 'Cyclops', strength: 5 }" To later reference one of these variables, you could use dot notation: Variables.monster.name or Variables.monster.strength The gamebook engine also provides the following convenience functions for dealing with object properties:

Util.hasProperty()

Parameter 1: object
Parameter 2: property label
Example: Util.hasProperty( Variables.monster, 'name' )
Use: Returns boolean true if the object has been assigned the specified property, otherwise false. Also returns false if the object is undefined.

Util.getPropertyAsText()

Parameter 1: object
Parameter 2: property label
Example: Util.getPropertyAsText( Variables.monster, 'name' )
Use: Returns the value of the object’s specified property. Similar to referencing the property using dot notation, but returns an empty string (rather than undefined) if the object is undefined or if the object doesn’t contain the specified property.

Note that the above functions (being JavaScript) use commas to separate their parameters, as opposed to click and load actions, which use semicolons.

Debugging

As you write and debug your story, you can start the story at an arbitrary point by temporarily including the class name start in a canon scene of your choosing (and remove the class name if it was present in an earlier canon scene). When you’re ready to return the story to its normal sequence, remove the start class name and and either add it back to where it belongs, or simply leave it out entirely (if the gamebook engine finds no element marked as start, it begins the story with the first element in the canon).

Q&A

Can my story contain images?

Certainly! Nearly any HTML element can be added to your canon. You may also use unique styles from one scene to the next.

Are spaces required between multiple parameters in a click or load action?

No, spaces are optional; any spaces on either side of a semicolon (or at the beginning or end of your parameter string) will be trimmed out before the parameters are evaluated.

How do I make one link perform many actions from just one click?

If your desired actions are all different actions (for instance, if you don’t need to use data-click-setvar more than once in a single click event), and if your desired execution order matches the sequence listed here, you may simply assign multiple click actions to the same link.

Otherwise, you could define a canon passage which contains numerous elements with your desired actions in sequence as load actions, and then use the click action data-click-macro to execute that canon passage’s load actions at once. You can also use data-click-macro with other click actions (though note the macro’s position in the order-of-events list here).

Can my story be played on mobile devices?

Yes, the gamebook engine has been tested on the mobile versions of Chrome and Safari. The default stylesheet includes formatting which hides the sidebar element (if present in your story) when the window size is too small to fit it. If your sidebar commands are essential for your story, consider placing additional copies of these commands (or some of them) in a header element with the ID header-commands, which the default stylesheet displays only when the sidebar is hidden.

What if I change the canon in a story somebody has previously saved in?

If the reader loads save data from a previous version of your story, he/she may or may not encounter problems depending on the extent of your changes. Note:

  • The save data includes variable names and values (these would not automatically change if you’ve modified the way variables function in your story).
  • The save data also includes the full text of the scene the reader was viewing at the time the story was saved (if you’ve changed this particular scene in your canon, those changes wouldn’t be immediately reflected when he/she loads). This may also result in the scene referring to outdated passage names depending on the extent of your changes.
  • Because bookmark history is written to local storage in the form of the original selector strings used to find canon passages, the data-click-back action may behave unexpectedly after a load if previously-used selectors now find different matches in your modified canon.

If you wish to prevent the reader from using old save data in a new version of your story, assign a new story ID to the new version.

Can I use your gamebook engine on my own site? Can I modify it?

You’re free to do what you like with the gamebook engine and any of its components (it’s licensed under the GNU General Public License). If you’d like to credit me or let me know how you’re using the engine, my name is Joe Rovang, and you can message me here. You don’t have to, though.

Also, as long as your story doesn’t have large media requirements, I’m willing to host stories on my site if you’d like.

I found a bug, and/or I have a feature request.

Feel free to message me here, and I’ll try to pretend like I know what I’m doing.