First time sharing a TypeScript based IF development system (IFECS)

Hi and thanks for your excellent reply.

I understand what you’re saying about the advantages of using an established programming language. I have had the same thoughts, but here is what i concluded:

  1. Tools like VSCode do make it a lot easier, but writers don’t use them.
  2. You can combine a DSL with an established language.
  3. You are at the portability mercy of any existing language (mobile version etc?)

Regarding point (2), my original idea was to implement a DSL according to, what i call, the 90% rule. this was:

Have the DSL able to easily implement 90%+ of the game and leave the 10% to a “proper” programming language.

The thoughts behind this was to endeavour to make the DSL as simple and non-technical as possible so that nearly all the game could be spun in it very quickly. And indeed, some (simpler) games would be possible entirely in the DSL.

Could swallowing a few extra characters of ‘verbose’ syntax be a reasonable price to pay even for regular IF authors? If by authoring in Typescript they are on a continuum of expressivity that can achieve almost anything they want if they put the learning in, perhaps it’s worth starting there and staying there.

I thought this too! But it turns out that expressivity doesn’t necessarily re
quire extra syntax. Example: natural language needs extra words but not extra syntax to say more.

Importantly, the core engine and Reader, has no concept of rooms or world state at all

Yes, i agree. I have the same thing, and this should also be true of your DSL. You don’t need it to internalise concepts like “rooms”. Even basic verbs like “get” and “inventory” should be written in the DSL itself and not part of the implementation. Neither should be the dictionary.

Confession: i currently have some hard-coded prepositions i need to eliminate (when i get around to it)

Turns out the concept of “in” needs to be part of the implementation. Most probably also “on”. This turned out necessary in order to compute the “interactive scope” ie the set of things the player can interact with.

Rather than centralising these capabilities into a single monolithic engine

Absolutely. But you shouldn’t need any special engine syntax for this. It’s essential to separate the UI from the engine. I have a thing called IFI, which stands for “interactive fiction interface”, which is a messaging system between the “front-end” and the “back-end”. The engine (back-end), knows nothing about how the UI will present the information or get input.

same is true for “save game”. The actual save is always done by the UI (front-end). You don’t want your game engine having to mess with files for example. Otherwise it suddenly becomes horribly unportable.

YAGNI

I started writing some sample games in my DSL, but soon i began to factor bunches of this into reusable chunks. So this is now a “drop in” file to any game which provides all sorts of basic and standard stuff, “inventory” is one of them. And what you’re wearing etc.

the Reader and the core definition of Story wouldn’t have to be extended

I wound up implementing the “main loop” by the DSL. Which means it’s totally changeable by any given game. Not that many games need to change the main loop, but it’s there.

while retaining a simple engine. rather than defining a rich engine

Oh yes. I took a simple pattern as a building block and found i could use it in different ways to build the whole system.

I call this thing the “stave”:

NAME TOP
HEAD
* SELECTOR1
* SELECTOR2
  TAIL

Sorry, this is probably meaningless. But that is the template (and syntax) for the whole DSL. It’s basically a branching builder and with different forms it can be a generator, an object or a choice.

Because;

  • a generator is a bunch of alternatives chosen by the system
  • a choice is a bunch of alternatives chosen by the player
  • an object is a bunch of alternatives that semantically matches inputs.

All fascinating stuff!

Edit: I forgot to add. It turned out the DSL actually covered 99%+ of any game functionality. All the usual IF things wound up written by the DSL. An example of the 1% would be, say you wanted a black-jack implementation or chess or something. You’d do that in the companion “proper” programming language.

1 Like

I agree if the tooling stack can reach the level of quality and e.g. source-mapping that exists for transpiler tools already then finding the right stave structure could be what’s needed to boost this area of technology and make composable flow-based stories more editable, without using actual developer tools.

However, I would be suspicious until I can see an ‘unsupervised’ mapping between a stave and transpiled code, (probably generator code), though. If a stave is a ‘philosophical’ structure with various similar runtime mappings for different cases maintained by the tenacity of a clever parser engineer rather than a strictly defined transpilation I don’t think it will have the required power.

As a reference example, JSX is under the hood just a convention and tooling for spitting out a h() function full of values. The h() is an explicit constructor for XML-style elements.

For reference, Vue exports an h function you can see in use in manually authored code - it’s this kind of shape that JSX actually transpiles to, before other tooling operates on it…

So when JSX reads like this, which is useful for familiarity with people who write HTML and expect it to look like HTML…

module.exports = <div id="foo" className="bar"/>;

…it is actually transpiled to something like this…

import { h } from 'vue'

module.exports = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)

It is therefore javascript but presents h() constructor composition in a way that people can more easily deal with in a text editor.

This has meant that all kinds of tooling can ‘go both ways’ doing e.g. autocompletion within JSX when populating it and checking the resulting constructs are valid when consuming it without a lot of extra work (probably just an AST transform and pass on the structure to the normal javascript tools).

1 Like

For reference in Retire title. Add minimal story and menu by cefn · Pull Request #2 · cefn/cloak-of-darkness · GitHub i just removed the third ‘verb’ in my flow. I was exploring this as an exercise in the compositional power of composing generators.

Turned out that title could be better implemented just as a decorator of an existing generated sequence of tell and prompt passages. So the engine is even simpler now - only handles two actions.

The title behaviour intercepts the passages in the actions and shoves a header on them depending on what room you are in. As you can see if revisiting Cloak of Darkness it looks exactly the same.

1 Like

@jkj_yuio is the source or documentation of your stave-based approach online somewhere that I can find out more? It sounds very aligned and I’d be keen to learn.

Originally, the idea was to transpile to a lower level language. I built a language called KL which was to be the underlying implementation.

However what actually happened is that i needed to build a parser for the DSL and load it so as to transpile it. And once loaded, i thought it would be useful to interpret it directly as part of debugging.

I was so pleased with the interpreter that, in the end, i didn’t bother with transpiling, but kept the underlying KL as the programming companion language. ie for the 1% of things not possible in the DSL and also for supporting the IFI connection.

The DSL interpreter can load, parse and prepare a full size game in well under a second, even on a mediocre PC or a mobile device. So i kept it!

I think it’s still possible to transpile, but i wanted to build some games, and inevitably the DSL was further developed. So that’s a bit of moving target.

The stave is the pattern for DSL terms, and these can have state. But any state is nevertheless stored within the world. It is just a convenience to assume the state is within the terms. The sort of state is remembering if terms have been visited or whether selectors have been used.

All my work is open source. You can get the sdk and read the manual. Unfortunately, it isn’t finished. No one was interested in getting their head round it anyway. The source code to the DSL and KL is also available. I plan to separate the repo from all the other code so anyone interested can get and build just the Strand system.

Regarding transpilation, the bit that might make the stave approach difficult is that terms can be decorated with, what i call, indicators.

Example of a simple term;

DICE
* one
* two
* three
* four
* five
* six

This is just a random generator with no state. That would work fine. But you can have things like;

EXCUSE<
* it wasn't me
* i didn't do it.
* nothing to do with me.

The < marks a sequence. This term will emit the different lines in order. Without < it would just be random.

Or you can have EXCUSE&, where the indicator & means shuffle. So you get the lines in a random order. This state might be problematic for translation.

Any section of apparent text is actually a flow and this might diverge from your idea. As well as having terms, this is flow based system. You can do things like:

XBAG
It's a leather bag. Inside you find,
> what is in bag
.

Where > means run a parsed operation.

Or;

GOHALL
You go into the great hall.
> put the player in the great hall

So although the stave is the abstact pattern;

NAME TOP
HEAD
* selectors
  TAIL

Every part of it is a actually a flow that can contain these composite parts. Also, all parts of the term except NAME are optional. Indeed;

FOO

is a valid term definition, with no top, head nor body. Sounds useless, but i use this form all the tine.

So a flow is a sequence of elements that can be:

  • text
  • term names
  • parser
  • code

But in any case, the overall structure of the stave is still there.

Examples;

CAFEORDER?
The waiter arrives, "are you ready to order Sir?"
* tea
CAFTEA
* coffee
CAFCOFFEE
* chocolate
"Sorry we're out of chocolate."

  The waiter leaves.

and;

CAGE@ THING 
> put it in CABIN
* name
the bird old battered cage
* x it
The bird cage is old and battered, but inside there is a red and blue [parrot]. XCAGE1

XCAGE1 > is SAUCER in CAGE
* yes
SEESAUCER

SEESAUCER<
* Wait! Also inside the cage is Hector's fancy [saucer]!
* The fancy [saucer] is also inside the cage.

You can see more examples here, here and here.

1 Like