Bots Writing Twine
Twee is a human-readable format of the “code” of Twine stories. Using a Twee compiler like Extwee, Twee can be converted into a Twine-compatible HTML file.
Generating Twee
In the previous part, the Twee compiler Extwee was used as a model for how to take Twee code, combine it with a story format, and create a Twine-compatible HTML file. The point was made that, as long as a story format is part of the process, the only real hurtle is the Twee code itself when creating a Twine 2-compatible HTML file.
Twee and Passages
Twee is made up of passages. These are marked with two colons and then the name of the passage. (Optionally, tags and additional metadata can be added.) Between one passage and another is at least one new line.
Passages are connected using opening and closing square brackets around the name of the destination passage. These connections can also be changed using the pipe character, |, or an arrow “->” or “<-” to point to a different destination.
Examples:
To create a link between the current passage and one named “Next passage”:
[[Next passage]]
To create a link named “Next” but point to another passage named “Next passage”:
[[Next|Next passage]]
Using an arrow to point at a new destination passage:
[[Next->Next passage]]
[[Next passage<-Next]]
What about macros and other code?
Twee supports anything a story format does. Additional code could be added through using macros or anything else supported by the output story format. However, for these examples, only passage connections will be used.
Examining TweeWriter
The TweeWriter object in Extwee gives a model for how to take a Story object, parse its passages, and write out a Twee file based on what it finds.
It’s main looping structure in the writeFile() function looks like this:
for(let passage of this.story.passages) {
// Write the name
this.outputContents += ":: " + passage.name;
// ...
// Add the text and two newlines
this.outputContents += "\n" + passage.text + "\n\n";
}
For each passage object in the passages array pf a Story, it gets written out as a textual passage in a Twee file.
Looking at Story
To use TweeWriter require a Story object.
Examining the constructor of Story shows the following:
constructor () {
this.name = "";
this.metadata = {
"ifid": "",
"format": "",
"formatVersion": "",
"zoom": "",
"start": ""
};
this.passages = null;
this.tagColors = "";
this.zoom = "";
this.creator = "";
this.creatorVersion = "";
}
It assigns default values and takes no arguments. A empty Story object can be created without any other dependencies, then.
Looking at Passage
The Story object has an array of passages. Using the Passage object, new passages could be created, added to an array, and then this passages property of Story could be set.
The constructor of Passage looks like this:
constructor (name = "", tags = "", metadata = {}, text = "", pid = 1) {
this.name = name;
this.tags = tags;
this.metadata = metadata;
this.text = text;
this.pid = pid;
}
It needs a name, tags, metadata, text, and a pid. As all of these parameters have default values, all that really matters is the name and text arguments. The rest can be set to their defaults.
Looping Passages
Adding lines to now require Story and Passage, code can begin to take shape to create a Story, build some passages, and then add them to the story. Using the existing code from the previous part, this created Twee file can then be read, parsed, and used to generate a HTML file.
const Story = require('extwee/Story.js');
const Passage = require('extwee/Passage.js');
let textfile = "test.twee";
let newStory = new Story();
let passageArray = [];
passageArray.push(new Passage("Start", "", {}, "[[Passage0]]") )
for(let i = 0; i < 10; i++) {
passageArray.push(new Passage("Passage" + i, "", {}, "[[Passage" + (i + 1) + "]]") );
}
newStory.passages = passageArray;
let tw = new TweeWriter(newStory, textfile);
The new code works now to create a new Twee file with 10 passages, each one linking toward the next. (Trying to use the link in the last passage will, of course, produce an error. There is no eleventh passage to link to!)
Before the loop, there is also new code to add a “Start” passage. (This is needed as no StoryData passage was added to potentially override the starting passage.)
Creating PassageWriter
When creating passages, the code can be made more generic and abstracted into its own class. That way, only an array of objects would need to be passed to it and a new story could be written.
const TweeWriter = require('extwee/TweeWriter.js');
const Story = require('extwee/Story.js');
const Passage = require('extwee/Passage.js');
/**
* @class PassageWriter
* @module PassageWriter
*/
class PassageWriter {
/**
* @method PassageWriter
* @constructor
*/
constructor (outputFile, contentArray = []) {
let newStory = new Story();
newStory.passages = [];
for(let passage of contentArray) {
newStory.passages.push(new Passage(passage.name, "", {}, passage.text) );
}
let tw = new TweeWriter(newStory, outputFile);
}
}
module.exports = PassageWriter;
Putting All the Code Together
Now, a simple array can be created. The PassageWriter object handles the code of creating a new Story, adding the passages, and working with TweeWriter directly.
The code is now ready to write Twee and have that code saved in an external file. The same file will then be read, parsed, and transformed into a Twine 2-compatible HTML file via the existing Extwee objects and processes.
Creating a bot or other process to create a Twine story is now a matter of creating an array of objects where each entry contains the name of the passage (with a passage named “Start” included somewhere) and its text.