2012-12-19

How to use Twine/Tweecode Variables and Macros from Custom Macro

This tutorial is aimed at people wanting to use JavaScript in their Twine stories. This is advanced topic and is not necessary for writing most stories. Twine's ease in integrating JavaScript was a key factor when I was evaluating interaction fiction systems. Please read the tutorial on creating a custom macro in Twine first.

Twine/Tweecode has the ability to use macros and variables. In the Responsive, Sugarcane and Jonah StoryFormats macros and variables are stored in a place where they are accessible by custom macros for both reading and writing.

All custom macros are stored in a global object called "macros". They can be referred to by name. The actual macro code is in the "handler" property.

macros["custommacro"].handler(place, macroName, params, parser);
macros["display"].handler(place, "display", [ "SomePassage" ], parser);

In most cases it is sufficient to pass the place and parser arguments given to your custom macro into the macro you are calling.

Now, onto Tweecode variables. In tweecode variables are prefixed with a dollar sign so that the parser knows to lookup the variablename.

<<set $variablename = 1>><<print $variablename>>
<<if $variablename eq 1>>Success<<endif>>

Tweecode stores variables in the JavaScript object: state.history[0].variables. They are stored without the $ dollar sign "sigil".

state.history[0].variables["customvariables"] = 1;
if(state.history[0].variables["customvariables"] == 1) { 
  //... 
}

Knowing how to reuse Twine's macros and working with variables set in Tweecode from within JavaScript custom macros (and vice versa) opens many possibilities.

2012-12-14

How to use Parameters and Variables in Custom Twine Macros

The last article looked at basic macros in Twine. We'll expand on that article by looking at parameters and using an internal variable. First the code example:

try {
  version.extensions['recallMacro'] = { major:1, minor:0, revision:0 };
  macros['recall'] = {
    handler: function(place, macroName, params, parser) {
      if(params.length > 0) {
        this.m = params[0];
      }
      new Wikifier(place, this.m);
    },
    init: function() {
      this.m = "A blank slate";
    },
    m: null,
  };
} catch(e) {
  throwError(place,"recall Setup Error: "+e.message); 
}

Let's go through the code a piece at a time (not necessarily in written order).

try {
  version.extensions['recallMacro'] = { major:1, minor:0, revision:0 };
  macros['recall'] = {
    ...
  };
} catch(e) {
  throwError(place,"recall Setup Error: "+e.message); 
}

This is boilerplate code that is explained in the last article.

    init: function() {
      this.m = "A blank slate";
    },
    m: null,

This code declares a variable as part of the macro called "m". Throughout the macro we refer to this variable using this.m. The variable is declared within the macro to avoid polluting the global namespace and to avoid variable name clashes there. Note that m is declared with an initial value of null and the value of m is set within the init() method. This practice makes the macro work better if the story is restarted without refreshing the page.

    handler: function(place, macroName, params, parser) {
      if(params.length > 0) {
        this.m = params[0];
      }
      new Wikifier(place, this.m);
    },

This code checks the length of the params array. If there is something in the params array then the first parameter is saved into this.m. In all cases the contents of this.m are printed.

Once you have added this macro to your story then try adding the following tweecode to a passage in your story:

Initial state: <<recall>>
Store John: <<recall John>>
Store Ben: <<recall Ben>>
What is stored?: <<recall>>

Using parameters and variables in Twine macros is a great way to increase the power of the macro. Storing variables as properties inside the macro object avoids any name clashes with other macros.

How to Create Custom Macros in Twine

This post is a technical article for the Twine hypertext authoring system. I will assume a passing knowledge of Twine/Tweecode and an intermediate knowledge of Javascript. This tutorial applies to version 1.3.5 of Twine - and even further still I assume the latest betas.

There is a bug in the current v1.3.5 Sugarcane and Jonah story formats; custom macros will not work during the display of the "Start" passage. The Responsive story format does not have this problem.

I'll start with a simple macro example then explain it line by line. Put this code into it's own passage. The passage title can be anything. Give the passage the tag "script"

try {
  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };
  macros['macrodemo'] = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },
    init: function() { },
  };
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

Let's go through the code a piece at a time (not necessarily in written order).

try {
  ...
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

This code sets up basic Javascript exception handling to let you know via a popup dialog when there is a problem in the macro code. Custom macros are inserted into the page at runtime so they can be difficult to debug. This exception handler makes things a bit easier. Technically this is optional, but trust me, it's worth adding. Just change the "macrodemo" text to the name of your macro so you can tell multiple custom macros apart.

  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };

This statement registers the macro with an extensions array. This is totally optional and appears to currently have no effect. If you are making a macro for re-use in many stories then it's a good idea to store version information so why not do it here. Maybe one day it'll be useful. I've taken the macro name and appended "Macro" just to be consistent with the way Twine's in built macros are registered.

  macros['macrodemo'] = {
  ...
  };

This creates the javascript object that holds the custom macro. The macro name in this case is "macrodemo" and you should change this to suit. The title of a macro should not contain spaces, double quotes or any characters that might confuse twine. There are two required functions and you can also add any other macro related variables inside the object.

    init: function() { },

This function is called when Twine is setting up the macro object when the story is started. This is where you should also initialise any variables because init() is called whenever the story is restarted. In many cases an empty function (as above) works just fine. There is no guarantee which order init() functions will be called, except that generally the inbuilt macros have their init() method called first.

    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },

This is the actual code that is executed when your macro is called. A macro is passed four parameters, the most important of which are "place" (an HTMLContainer where text is currently being output to) and "params" (any parameters passed to the macro). Parameters are split into an array on space characters except within "double quotes". The macroName parameter is self-explanatory. The parser gives a reference to the current Tiddlywiki parser object - which is useful only for some advanced cases; e.g. if you were writing a macro that has a corresponding end macro: see the inbuilt if, else and endif macros or my while loop macro for examples.

The example code here shows how to do simple text output which is added in place of the macro call. Most Twine macros will want to do something similar to this.

Once the macro is in place then somewhere in your Twine story go:

Custom macro: <<macrodemo>> 

Rebuild the story and you should see Hello World! Congratulations you have written your first macro.

BUG: Backslash characters do not encode / decode correctly from the passage store. Use a workaround such as: String.fromCharCode(92).

TIP: Since the latest betas support multiple source files via StoryIncludes, I like to put macros into separate text (.twee) files just to make organisation and reuse easier.

Macros are a powerful way of adding functionality to your Twine stories. The full power of Javascript is available to you. If you make a good macro then please share.