Working with SimpleQBN: Part 2: Working with Node.js Projects

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 2: Working with Node.js Projects

As covered in the previous part of this series, SimpleQBN is a JavaScript library using concepts and metaphors borrowed from the history of storylets and quality-based narrative (QBN) works. Each major class such as Expression or QualitySet can be used separated. In the cases of letting SimpleQBN control everything about the QBN process, classes such as Card or Deck can also be used.

SimpleQBN is available on the NPM registry and can be installed using the following command:

npm install simple-qbn

Export Subpaths with SimpleQBN

It is strongly recommended to use ES6 Modules with SimpleQBN because of its built-in export subpaths. Each individual class in the package can be used separately through specifying only its name.

For example, a project using only the Deck class might look like the following:

import Deck from 'simple-qbn/Deck';

const d = new Deck();

Creating Cards from JSON

One of the most useful aspects of using SimpleQBN in a Node.js-based project is the ability to store cards in a data format such as JSON and then generating them based on only a few lines of code.

Consider the following JSON example with the possible contents of a file named cards.json:

{
    "cards": [
        {
            "content": "This is an example!",
            "qualities": ["test-eq-1"]
        },
        {
            "content": "This is another example!",
            "qualities": ["test-eq-1"]
        },
        {
            "content": "This is not available at first!",
            "qualities": ["test-eq-1"]
        }
    ]
}

In the above example, three cards are represented as sets of properties, content and qualities. Using Node.js, this file can be read and parsed. Code to parse the above example of cards.json might look like the following:

// Import SimpleQBN from SimpleQBN.
import SimpleQBN from 'simple-qbn';
// Import readFile, a Promise-based file-reading function.
import { readFile } from 'fs/promises';

// Create a new Deck.
const d = new SimpleQBN.Deck();

// Add a value to the internal State in the created Deck
d.state.add('test', 1);

// Wait for the JSON file to be loaded and parsed before proceeding.
// (JSON loading is experimental in Node 15.8.0.
//   See https://nodejs.org/api/esm.html#esm_no_json_module_loading. )
const Stories = JSON.parse(await readFile(new URL('./cards.json', import.meta.url)));

// For each card within the Stories array, add new cards to the created deck
Stories.cards.forEach((card) => {
    d.addCard(card.content, card.requirements);
});

Extending Card

In the cases where additional properties or methods are needed from the Card class, it is recommended to extend the class and create a new, expanded one.

import Card from 'simple-qbn/Card';

class AugmentedCard extends Card {
    constructor(content, qualities, anotherProperty) {
        super(content, qualities);
        
        this.additionalProperty = anotherProperty;
    }
}

export default AugmentedCard;

Note: The class Card needs access to a State in order to test its values. Usually, it uses this from access to Deck (as it will create its own State). When extending Card, be sure to use or override its isAvailable() method.

Extending Deck

Like with Card, it is possible to extend Deck as well. However, as the internal properties (cards and state) of Deck are private class fields, this can cause complications.

For example, because cards cannot be accessed directly, other methods like size() and getCard(position) can be used to augment the base class of Deck to perform new actions.

import Deck from 'simple-qbn/Deck';

class AugmentedDeck extends Deck {
    constructor(anotherProperty) {
        super();
        this.additionalProperty = anotherProperty;
    }
    
    showCards() {
        // The 'cards' property is a private class field in Deck.
        // However, it exposes the size() and getCard(position) methods.
        for(let i = 0; i < this.size(); i++) {
            console.log(this.getCard(i).content);
        }
    }
}

export default AugmentedDeck;

The above augmented method of showCards() could then be used on an instance of the extended Deck class, using the base methods and the augmented one.

import AugmentedDeck from './AugmentedDeck.js';

const d = new AugmentedDeck();
d.addCard("Example", ['test-eq-1']);
// Shows "Example"
d.showCards();