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

Is that possible today? Or do you mean it would be possible for someone to write a web UI, but nobody yet has, so it is not yet possible?

Distributing executables to ordinary users is surprisingly hard. Windows 10 out of the box won’t even run ordinary .exe files that aren’t sufficiently “popular.” (You can notarize and pay for signatures, etc. but ordinary IF authors won’t do that.)

As a result, it’s easier to teach users to download a runner (which can become popular enough that there’s no error message) and then open an IF game file in that runner.

If not Z-code or Glulx, I think you’d want to be able to generate an HTML file that someone could double-click on to start playing a game.

As for your TypeScript internal DSL, I (like Josh) didn’t realize that you meant for authors to write games in TypeScript, and I have some feedback about that.

It’s not uncommon for IF enthusiasts to present this forum with alternate IF authoring systems in which authors can develop IF with a “real” programming language, typically one that ranks highly on StackOverflow’s “most loved languages” list.

But these authoring systems never seem to gain traction. None of the most popular parser-IF systems require authors to use a “real” programming language. All of the most popular systems use an “external” DSL for IF.

Why is the IF community so enamored of DSLs? This isn’t like other parts of the game industry. In the game industry in general, even if people do decide to implement their own engine from time to time, the most popular game engines use popular general-purpose programming languages, the same languages everybody else uses. The IF community must be full of weirdos, right?

Well, yes, but we’re not the only weird ones. Consider querying a database. You could do it in C or in JS, and some people do, but the industry has discovered that using a declarative query language is better, and SQL is by far the most popular language for doing it, despite the fact that SQL doesn’t have much in the way of step-wise debugging support. If you’re querying a database, trust me, you should just use SQL. (At a minimum, you should learn SQL before attempting to query a database in another language.)

Text adventures aren’t like other programs that run step by step from beginning to end; they’re programs to cooperatively generate text, incorporating commands from the player while subtly guiding the player how to use the correct commands to win. Coding a parser-based text adventure is mostly about declaring data (what are the rooms and objects in the game) and a rules engine of events that fire when certain conditions occur.

And, of course, that’s what you see in IFECS. IFECS “Typescript” includes backtick template strings to parse action queries (written in strings), associated with event-handler functions.

The general industry wisdom when it comes to rules engines is that they all kinda suck. It’s easy to get started rolling your own rules engine, but it’s hard to write a good one, so everybody just rolls their own. And this is another area where DSLs often do thrive. (The article I linked above is by Martin Fowler, who concludes, “there’s a lot to be said for avoiding rules engine products.”)

Then, when you add in the fact that most IF authors see themselves as writers first and coders second (if at all), you start to see why a community has formed around DSLs for text-adventure rule engines, and why good programmers keep writing their own IF-rules engines rather than use anybody else’s.

Consider also that a lot of folks appreciate IF due to nostalgia for retro hardware. (I note that you’ve styled your demo to make the text appear slowly, like over a BBS from the 1990s.) A lot of people who enjoy that sort of thing tend to want their games to actually run on old hardware, or at least run on emulators for old hardware.

You’re not gonna run TypeScript/JavaScript on an Apple IIe. You’re not gonna run Python, there, either. But you absolutely can write Z-machine code today in Inform that works on Apple II out-of-the-box.

I suppose you could write JS that transpiles to Z-machine code, but probably only a subset of JS, at which point what you have is a JS-like DSL.

I’m telling you all this not to be discouraging. I think the world could be a better place with a popular IF engine oriented around a general-purpose programming language, and especially if someone were to implement a rules engine for JS that people love, not just for IF, but for all sorts of rules-engine shaped problems.

The path up this mountain is littered with the frozen corpses of others who have tried and failed to reach the top, but I hope somebody manages to climb it, and I wish you the best of luck.

P.S. One more parting thought. Rather than write your own admirable IF from scratch, you might find it productive to port an existing IF game into IFECS. Emily Short’s Bronze is available under Creative Commons. Porting it to IFECS would probably be a valuable exercise.

3 Likes

I very much appreciate your input. While discouraging it’s not unexpected. I’ve been rummaging through several “frozen corpses” of what look like really great projects. And indeed I’ve been trying to explore the rules engine path; but at the same time trying to leverage the power of ECS.

2 Likes

No… a web interface does not exist. I’d need to build out a web UI or work on interfacing with GlkOte.

It looks interesting. I think that Javascript or something that compiles to Javascript is the obvious choice for a basis for an IF development platform given how much interest there is in something being playable on the web. Or at least somewhat more obvious than, say, compiling one language to another language that compiles to machine code for a virtual machine that we then run in a browser in a javascript wrapper around a C VM emulator that’s been compiled to web assembly.

Given that I think the potential to run browser-native would be one of the advantages of a JS-based system, I hope you do pursue an integration with glkote. And that would open up the possibility of letting people play with the language in a browser, too. (I’m not suggesting it’d be the ideal way to develop, but letting people take it for a spin without having to install anything would be a nice feature.)

3 Likes

Of course, the trick is “what is admirable game?” And the answer is as varied as there are gamers.

I have been very impressed with Inkle lately. Of course, their “80 days” is quite famous, justifiably so. However, I was really impressed with their “Heaven’s Vault” game. Imagine my disappointment when I realized that it’s basically

  1. Choice based ( read low tech)
  2. Unity 3D Integration is really difficult to do.

I think Twine and Renpy have had their moment of glory. I’d watch inkle development closely, because I think their games are really popular and getting even more so.

I prefer choice-based IF than parser-based IF. And there’s seriously nothing low tech about it.

2 Likes

OK, I spent some time digging into the Cloak of Darkness example. Since IFECS hasn’t been around for 28 years, it doesn’t do as much for you as Inform7 does, nor would you expect it to. But it also feels like it breaks behaviors up oddly, in ways that I’m not sure are helpful?

The IFECS code is nearly 40% longer than the Inform7 code, and a fair chunk of this feels like boilerplate. It’s also missing some functionality. Some of these things are trivial: allowing the player to type “velvet”, “dark”, “black”, and “satin” for the velvet cloak. Others would probably be more work: allowing you to wear and remove the cloak, allowing one non-going action in the dark room before the message is trampled (you just have neat/trampled where the Inform7 version has neat/scuffed/trampled), having the hook description code be robust enough to show any future objects that might be placed on it if the game was expanded.

I think the weirdest and most worrisome thing to me is that entity types don’t seem to come with default rules and commands. Am I missing something here? I feel like that’s the whole point of having a library: if you declare something as a supporter you should be able to put things on and take them off of it, its contents should be listed in descriptions, etc., without any further work on the part of the author.

Here you’re defining a custom put this on that command. And that code calls the nouns cloak and hook even though this (mostly) seems like generic code that could apply to any supporter. And you’re using a whole bunch of if-statements inside the one function to check things like “does the actor have the object?” and “is the actor wearing the object?” This doesn’t feel very extendable: you’d have to copy the entire function. What if several different parts of your game want to add their own extensions to the put command? In a rule-based system I think these things should be separate rules that can be added and removed individually without knowing about any of the others. Maybe that’s an ideal that other systems don’t actually approach, but I think Inform7 gets closer than this?

And you define the hook as a supporter. And the Supporter class has a lookable component that seems to list all visible children. But then you ignore that and create a custom examinable component (how are lookable and examinable different?) that only checks to see if the hook contains a cloak? And ok, this is the same thing that you’d do in Inform7, you’re replacing the description. But there you can re-use the internal piece of the default code by saying with [a list of things on the hook] hanging on it instead of having to copy/paste the whole code with all the problems that code duplication brings.

So…yeah. Longevity concerns aside (will you still be able to run these games 30+ years from now?), I think it’d be neat to see a web-native IF engine, and this is a fun prototype, but I don’t think you’ve quite found the sweet spot yet…

3 Likes

This is something I’m still debating. In most IF systems actions are attached to entities while in an ECS the actions should be in a system. IFECS has an action system and a “eDSL” (using the term loosely) for defining actions. Many default actions are defined in the standard lib (You may have missed the put action in ifecs/lib/modules/verbs/putting.ts at main · Hypercubed/ifecs · GitHub) and then the game defines more specific rules to handle specific things like “put cloak on hook”. I’m still iterating on methods to let the specific actions be less verbose (something like before or after hooks).

Thank you for the comments. So far I’ve been focusing on running in a terminal but seams to be a lot of interest in web-native.

1 Like

5 posts were split to a new topic: Tech of IF

A few tweaks and IFECS works nicely in GlkOte:

CodePen: https://codepen.io/Hypercubed/full/poPjmyJ

6 Likes

Hi, Jayson. I’m a Typescript developer that’s exploring interactive fiction from a completely different direction. I am experimenting with co-routines for managing and debugging long-lived, stateful interactions on the web and was messing about with IF as a domain.

Code looks like this…

I’m sure there are better ways to structure what I am doing to make it understandable and accessible for IF authors, but I found that Interactive Fiction capabilities almost immediately drop out of the continuation capabilities of Typescript generators, (the things with * in the linked code). I didn’t have to do anything very much. Each story then ends up being essentially…

  • Define and initialise your state
  • Write typescript code which sequences passages and defines branches against that state, with incredibly simple typescript logic (like if value==matchingValue or if score > 5)

At present the only thing the ‘framework’ needs to help with, is defining what callbacks to the reader look like (e.g. in my example Tell is something which shows a passage on screen and waits for the reader to indicate ‘yes I’ve read it’, while Prompt is something which shows a passage on screen and waits for the reader to choose from options).

A whole lot of power drops out of this. Your state and logic can be anything at all, and doesn’t have to be anticipated by any framework. Also if you wanted to specialise the controls which are presented, you can use actual web components instead of just text fragments. If you want there to be some specialised view of the story state that indicates progress, this is straightforward to do (place a web component alongside the story view that renders the story state). At no point do you need to understand some story structure or system that I’ve introduced, because there isn’t one - there’s just generator code. Writing continuation/coroutine code is well-understood and there is excellent editor and debugger support for it already.

The speed at which it was possible to put together an interactive story engine made me wonder why people don’t just author stories in languages that support continuations. I’m not proposing that ‘my project’ should be how it’s done, but rather whether it’s worth populating some examples which are useful starting points and which open up this way of authoring for people.

Idle speculation at this point but I found this thread to be very very interesting given the context of my work and I thought I should say hi.

2 Likes

I’ve started writing a (choice-based) IF-Framework of my own in TypeScript twice over the last three years. Last time around I even got a little playable demo out of it but in the end I decided (again) that it did nothing for me I couldn’t do just as easily and nicely and frankly much more quickly getting from idea to implementation if I just continued using twine.

I must admit, I always seem to be drawn back to building choice-based systems, starting with Interactive Text Adventure Boxes - MicroPython Forum (Archive) and I’ve not been able to really walk away since.

This is the 3rd or 4th experiment of generator-based text gaming for me, initially in Python and more recently in Typescript with both languages having generator support.

So I thought I’d put together a Cloak of Darkness demo prompted by Cloak of Darkness - IFWiki

It’s playable at Cloak of Darkness

My take-away is that it’s super-nice to get the benefit of JSX editing (line wrapping), auto-completion and typesafety (choice ids, room ids).

The continuum between the code for running the story and the code for rendering the navigable UI means you can flex the whole experience very freely. This should facilitate specialisation of the UI to serve a particular story. I’ve used just JSX fragments (text) mostly but there’s no reason not to author a story which has a blend of full-blown UI components in the passages, and the Reader component can easily wire into story state and show e.g. inventory.

Although specialisation of UI components would need e.g. React skills, the type-safe authoring of stories using this syntax is within reach for regular people. There’s good editor support for Typescript with embedded JSX. Try checking out the story and editing with VSCode for example.

The source code ends up reading like this…

1 Like

Nice!

What you have with your continuations is what i call “flow”. And your function names are what i call “terms”. I have been working on a rapid development IF system using a flow-based paradigm for some years now, and i believe it to be the future of IF.

Unfortunately such ideas expressed in a general programming language appear rather clunky and syntax heavy. If you were to boil down what you really need, you could end up with a very compact way to express it. This is why i developed a low on syntax DSL.

Next you must develop your world and property states. For example hasCloak is awfully bogus, as it assumes the otherwise location of the cloak. Suppose you can also drop the cloak somewhere. How would that work?

You’ll need to articulate the relationship between objects and to store generic properties as well. This will need to go into the world state. “inventory” and things in a “room” will then drop out easily. As then will the concept of “scope”.

Great stuff!

3 Likes

Thanks for the suggestions @jkj_yuio it’s exactly the prompts I need to progress, and to try to express what I think is so powerful here.

Yes, it’s this tension between terseness and expressiveness that I’m interested in. The (wild) speculation is that the tradeoff may not be as bad as it seems when using a modern language with Generators and JSX.

Taking the fragment that I shared above, one reading might be that use of && is weird, what does that even mean? another might be what are all those asterisks for, why don't you just leave them out?.

To start with, it’s worth noting that in a Typescript editor like VSCode, a lot of this complexity goes away (it does auto-completion and parameter popups, it warns you with red lines if anything is unmatched, it reformats the code with exactly the right layout with Prettier the moment you save and so on - you’re not on your own).

However, a more fundamental answer is that it is a full-blown language. I crucially didn’t have to invent (or define syntax or a parser for) any of the mechanisms I have used, including those which happen to be demonstrated in that fragment, I just reached for them from the standard Typescript toolbox. There are more learning and authoring resources to support this language emerging every day meaning there is rich support and a wide community to understand this syntax.

Although the Cloak example illustrates how existing language syntax can already meet the requirements of conditional rendering, guarded choices or navigation there is a much bigger toolbox that I haven’t reached into, and that authors could benefit from.

Of course expressivity means extra syntax - the language has function and function* because they come with completely different behaviours, and you want to use them at different times.

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.

An example of this richness (which I wouldn’t want to have to reimplement in my own DSL) includes the fundamental expressive power of a delegated yield *, to a function *. In the cloak-of-darkness example this is used to introduce an implementation of worldState, rooms and navigation, which doesn’t exist at all in the core engine.

You can see for example (in a recent commit) that the main story in cloak-of-darkness.tsx actually delegates to yield* roomStory

Inside its ‘black box’ roomStory in turn delegates to one Room Generator at a time passing the worldState to perform its work with the completed Generator returning a new destination roomId. The roomStory Generator is parameterised by the provided rooms and state, but it handles the lookup mechanism only.

Importantly, the core engine and Reader, has no concept of rooms or world state at all. It just takes a story generator that will notify titles, pages and prompts to the user in a sequence, driven by the users’ choices. Rooms are an ad-hoc format undertaken just for this story, fulfilled by delegating yields and invisible to the core engine which just sees a Story.

Here’s a purer reference example of what the underlying engine consumes, that anticipates rooms, inventory etc being ad-hoc extensions on a per-story basis…

In the case of roomStory, the delegated yield allows the whole notion of rooms and world state to be introduced as an optional specialisation in just a few lines of code.

In another story, you might delegate e.g. to an inventory submenu, which ‘takes over’ the frontend while it looks after e.g. moving objects between rooms and reporting what’s in a room, before returning back to the story.

Rather than centralising these capabilities into a single monolithic engine, I’m curious if it can be radically decentralised into lots of innovative fragments that are brought together only when authoring an individual story, and whether a modern language will facilitate this.

Yes for any more complex story than the Cloak, an inventory would have to have richer logic, but if it’s accessible enough, why not have a dedicated inventory model designed to suit every story, on a YAGNI basis.

Any real IF experience will need quite a lot of these things. My speculation (to be challenged) is that for much of these cases the Reader and the core definition of Story wouldn’t have to be extended (for example just a Generator yielding e.g. title, tell and prompt in any sequence it likes). Then you could afford to invent or import ad-hoc navigation and inventory paradigms via delegating yields.

So at this point I’m still wondering if (in the right language) it’s possible to approach it compositionally ‘from the ground up’, layering in interactivity as needed by the story while retaining a simple engine. rather than defining a rich engine, with an all-encompassing set of features from which a story can draw, which drives us to bespoke-parsed-syntax and far away from the incredible tooling support of a modern language.

1 Like

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