Bots Writing Twine: Part 2: Generating Twee

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.

const FileReader = require('extwee/FileReader.js');
const TweeParser = require('extwee/TweeParser.js');
const StoryFormatParser = require('extwee/StoryFormatParser.js');
const HTMLWriter = require('extwee/HTMLWriter.js');
const PassageWriter = require('./PassageWriter.js');
let textfile = "test.twee";
let storyContent = [
{
name: "Start",
text: "[[Passage1]]"
},
{
name: "Passage1",
text: "[[Start]]"
}
];
let pw = new PassageWriter(textfile, storyContent);
let storyFormat = "format.js";
let outputFile = "example.html";
let fr = new FileReader(textfile);
let tp = new TweeParser(fr.contents);
let sfp = new StoryFormatParser(storyFormat);
let hw = new HTMLWriter(outputFile, tp.story, sfp.JSON);
view raw index.js hosted with ❤ by GitHub

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.