
Wolfram-Twine Bridge
The goal of the project was to build an Import/Export system between Twine Projects and the Wolfram Language. Twine is browser-based software used to make interactive stories and text-based games. One can think of a Twine story as a website, where each webpage is analogous to a passage in the story. Connections between passages can be as sparse or dense as you like. Essentially one has a network of passages. A nice way to represent a network is with a graph, this is one of the forms I use to represent a Twine Story in Mathematica. (There is a link to my code at the end of this post)
Twine Structure
The workspace for Twine projects is the Twine Editor, available online and desktop. A published Twine file is in html format. This is the file we want to parse for import.
Sample Twine file:

The HTML structure is simple:
<tw-storydata>
<tw-passagedata>
"PASSAGE CONTENT ..."
</tw-passagedata>
<tw-passagedata>
"PASSAGE CONTENT ..."
</tw-passagedata>
...
</tw-storydata>
Some published Twine Projects have other HTML information, though this is not necessary for Import or Export. All we need is whatever is within the <tw-storydata>
tags.
Import Procedure
Initially, there were issues with importing the Twine files I had on hand. I presume it's because these files were not valid HTML (no doctype). The XML
import did not work either, presumably for the same reason. Importing as XMLObject
generated something that looked reasonable, but introduced an extraneous body tag in the middle of the structure. Riccardo Di Virgilio, an instructor at the summer school wrote a parser that takes in a string version of the file and outputs a properly nested XML structure.
With the story data in XML form, I could start generating the Graph and Association representation. The first thing I did was look for the particular Cases
of XMLElement
patterns to extract the story title and passages. From there I used that data to construct a graph. Harlowe uses a pair of Double Square brackets to denote a link. To create the edges I defined two functions, generateEdges
, and extractLinks
. generateEdges
takes in the passage name and a list of links and creates a directed edge between the passage name and those links. The list of links is generated from extractLinks
. extractLinks
takes in a passage's content and extracts all string content within Double Square Brackets and puts them in a list.
It's nice to have the graph, but there should be a quick way to see the content of each passage as well. I wrapped all the vertices of the generated graph with Tooltips so one can hover over them and see the passage content. To do this I made an association of the form <|"Story Title" -> "Start Here", "First Passage" -> "First Passage Content", ..., "Last Passage" -> "Last Passage Content" |>
. Then I generate the output Association. The Import function outputs a list of the Tooltip Graph and the output Association.
Example output:

Export Procedure
To export a Twine project I wrote twineExport
. It takes an Association form of a story and exports it as an HTML fragment. One can then import it into the Twine Editor. Twine passages also have a position attribute which I'm trying to make use of to place them nicely in the Twine editor. The current thinking is to mimic the layout of vertices from the graph representation. (I use twineSummary
function for this, I'll share that in the next section). I can use the GraphEmbedding
function on the graph. GraphEmbedding
returns the coordinates of the vertices, then I just need to scale the points to orient nicely in Twine.
The Left Image shows the exported file. In the Right image, I moved the passages around to show what one would get with using GraphEmbedding
.

Summary Procedure
The Twine Editor has a playthrough interface so one can test their stories. I haven't implemented something like this yet, but there is a simpler story explorer that I've made. The twineSummary
function creates it. The function takes an association as its first argument and an optional string as its second argument. The default output without any options is a GraphicsRow
where the first item is a dynamic passage viewer and the second item is a Button Graph. Each Button is a passage name; when clicked it will update the dynamic viewer with its respective content (An Image of this output is at the top of this post). "Graph" and "Export" are the possible options for twineSummary
. "Graph" returns the Button Graph by itself. "Export" generates a Twine HTML file and saves it to a folder called TwineStories
in the current notebook directory.
Macros
The default markup language for Twine, called Harlowe, has syntax for macros: commands for changing story elements and states. I have yet to support these features but have been ideating a way to work with them. Let's look at the set macro as an example. To set the value of the variable myNumber
to the value 10
we can write the following: (set: $myNumber to 10)
. What I've been thinking is to use an association where the keys are command
and data
. For set it would look like this:
<|"command" -> "set", "data" -> <|"Variable" -> Symbol[$myNumber], "Value" -> 10 |>|>
I defined a function called macroGrammar to handle this command and some others. In the set
case I generate a string representation of setting, for example, "var = val". Then convert that to an expression.
macroGrammar[macroAssoc_Association] :=
Module[{command = macroAssoc["command"], data = macroAssoc["data"]},
Switch[command,
"set",ToExpression[ToString@data["Variable"] <> "=" <> ToString@data["Value"]] ,
"if", If[data["ConditionExpression"], data["T"], data["E"]],
"print", Print[data],
"either", RandomChoice[data]
]
]
A list of Harlow macros can be found here
Future Directions
I'd like to implement more macros. Among other types, there are conditional, printing, data and output macros. Some macros include Links to other passages, which I can't support yet as they are effectively hidden from my parser. In addition to this, I need to come up with a good Passage Positioning scheme for Import to Twine. The other thing to do on the Wolfram Language side is better placement of the Vertex Labels. So for large graphs, the labels don't overlap. A Twine workspace editor in Mathematica would be useful as well. As of now, one has to write the Association by hand. (of course one can always just use the Twine editor).
The next step is to gradually implement these macros and continually increase the number of stories I am testing on. For each parsing or macro error in a story, I can implement a fix.
My TWINE CODE is on Github!