Working with SimpleQBN: Part 1: Concepts

Working with SimpleQBN

SimpleQBN is a generic JavaScript library for creating quality-based narrative (QBN) works. It borrows heavily from other projects like TinyQBN for SugarCube in Twine 2, but is designed for more general Node.js and Web-based projects.

Part 1: Concepts

SimpleQBN is based on the concept of storylets and uses terms common to other quality-based narrative (QBN) works. The internal classes and their relationships are based on these terms and use them explicitly in reference to each other.

What is a storylet?

The term storylet was originally coined by Failbetter Games in 2010, but most people now use Emily Short’s refined version of three primary elements:

  • Content
  • Requirements
  • Effects on State

A storylet is some selection of content. It can only be accessed if its requirements are met. Inside or as a result of accessing the content, it may affect other stored values. (Storylets also draw from an older concept called the sculptural hypertext model coined in 2001, but very few people use this term.)

Instead of being connected via links as shown in a program like Twine, storylets exist as their own entities in a project. If their requirements are met, the can be used. Otherwise, they are not available.

Storylets: State

Any implementation of storylets has to also consider another term, state. In programming terminology, the use of “state” refers to a collection of values created, updated, or accessed as part of running a program. In this case, storylets exist in connection to a state as part of the same project.

The state contains values checked as part of the requirements of a storylet. The effects of a storylet can also change these values, affecting the availability of other storylets.

SimpleQBN explicitly uses a class called State. This has methods including add(), update(), has(), remove(), and size(). It works using key-value pairs where the first value is the key and the second is its value. For example, a key might be “rubies” and its value 5.

Working directly with the State class might look like the following:

const state = new State();
state.add('test', 1);
state.add('discard', 0);

In the above code, the String values ‘test’ and ‘discard’ are added and associated with their values of 1 and 0.

Storylets: Requirements

A storylet is available if its requirements are met. These requirements are expressed in the Grams’ Shorthand as developed in TinyQBN. These are conditional statements expressing some comparison between either a value contained in the State and an external one between two values contained within State. They are written in a format of “operator-operation-operator.”

For example, a storylet might have a requirement where the content is only available if the key ‘test’ is greater than 2. In SimpleQBN, these requirements are used as part of the class Expression, and such a requirement might be written as “test-gt-2”.

Working directly with the Expression class might look like the following:

const e = new Expression('test-gt-2');

An Expression has one public property valid (returns if the String value conforms to the Gram’s Shorthand) and a method check(), which returns if, according to the values in State, the comparison is considered true or not.

Storylets often have more than one requirement and SimpleQBN uses the class QualitySet to represent this. As a collection of values based on the class Expression, a QualitySet can more quickly determine if a storylet is available based on all of the Expressions contained within it.

Working directly with the QualitySet class might look like the following:

const qs = new QualitySet();

The QualitySet class has the public methods add(), has(), and size() for working with its internal Expressions. Its method check() iterates over its internal collection and calls check() method of each Expression. If all of them are true, the QualitySet itself is said to be true as well.

Storylets as Cards

It is often useful to think of storylets in the metaphor of an individual card and a collection of them as a deck. Selecting available cards from a deck uses the term drawing. If the card is available, it can be drawn.

SimpleQBN uses the terms card and deck explicitly as the names of its classes Card and Deck. A storylet is considered a Card. The collection of them is part of a Deck. To simplify working with State, each Deck has its own internal instance of the State class, too.

Most of the time, authors working with SimpleQBN start with creating a new instance of a Deck and then adjust its internal State values, add new Cards, or select them using the explicitly named draw() method. As storylets include content and requirements, these are passed to the addCard() method of a Deck.

Working directly with the Deck class might look like the following:

const d = new Deck();
d.state.add('test', 1);
d.addCard("This is a card!", ['test-eq-1']);

In the above code, a new Deck is created. Next, its internal State is changed via the method State.add() to add a new key-value pair of the key ‘test’ with the value of 1. Finally, on the third line, a new Card is added through passing the content of the Card as “This is a card!” and its requirements as an Array containing the single requirement of “test-eq-1”.

What about effects?

SimpleQBN does not provide a domain specific language (DSL). In a post on the design problems related to the quality-based narrative (QBN) space, Bruno Dias mentions one of the issues surrounding QBN projects as including a DSL or not. For example, TinyQBN, in working with the SugarCube story format in Twine 2, is able to provide the ability to work with values more directly and have passages in Twine 2 act as cards. This means passages, when shown, can produce effects on values within its own state through using the TwineScript built into the SugarCube story format.

As a more generic library, SimpleQBN provides only the objects and relationships to create a quality-based narrative (QBN) work. It takes as a base assumption the library will exist alongside or as part of a larger project. Through its methods and access to both the lower-level State and higher-level Deck, authors can arrange its classes in the way most useful to them, pulling from either its parts, such as using an Expression directly or using a collection as part of the QualitySet class, or letting classes such as Card and Deck handle tasks for them.