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

Hello Interactive Fiction Community,

I’ve been secretly working on an interactive fiction engine called IFECS (maybe pronounced effects). It still needs a lot of work and documentation is pretty much non-existent but felt it was time to start sharing. Obviously I’ve been testing other development systems and many are really great (Inform 7 is genius). But as a developer I wanted to try something different (I know, I know… queue XKCD). Anyway, here are the key points that I think make IFECS somewhat unique.

  • At its core IFECS uses a fairly traditional and simple ECS (i.e. Entity, Component, System) engine. On top of the ECS I’ve layered an interface that makes the engine feel like object-oriented programming when it comes to the actual game development. One top of this is a DSL (Domain Specific Language) for building interactive fiction games.

  • The entire system (engine, interface, standard lib, games, etc) is written in TypeScript. My hope is this eliminates barriers between the engine and game development and makes the system a lot more open and developer friendly. No black boxes and a single programming language.

  • I’m using Deno as the runtime. I think Deno adds a lot of interesting options for publishing and sharing and provides some useful common tools for linting and testing.

Here is a Cloak of Darkness playable on Replit: https://replit.com/@Hypercubed/Cloak-of-Darkness?v=1

Here is the source code: GitHub - Hypercubed/ifecs

Constructive criticisms, suggestions, and pull requests are appreciated!

7 Likes

Do you have any info about the DSL for people to look at yet? Looks like so far all your examples are written in TypeScript.

Congratulations on developing your IF platform! I wish you the best of luck.

How do you imagine players would normally play IFECS games? In Replit, as you’re demonstrating here? Would there be a web UI runner for ordinary folks? Would players be expected to install deno locally to run an IFECS game? Would deno generate standard Z-code or Glulx files?

(I notice that when I attempt to run Cloak of Darkness in Replit, it takes 30+ seconds to launch, because Replit makes no attempt to cache downloaded deno dependencies; it downloads and redownloads (and rechecks) all the deno.land dependencies every time I refresh the page and click Run. That seems like a non-starter for regular folks who just want play a game. I literally thought it had crashed at first!)

I suggest that your next step be to write an “admirable” game in IFECS. (I say this to all new IF platforms that launch with just a tech demo.) Most people choose an IF platform by playing a great game and saying, “I really like this game, and I would like to make another game just like it. How did the author(s) make it?”

So, when IF platforms successfully take off, they require an admirable story (not just a technology demo) to attract new authors. Historically, the first “admirable” story for each now-successful IF platform was typically either written by the platform authors themselves, or directly funded by them. (Twine’s first admirable story by Anna Anthropy is the only exception I’m aware of.) Admirers don’t seem to directly care about any of the details of the system, except that if it’s too hard for them to learn the system and finish a game, that’s a major factor in achieving true popularity.

I think you’ll either need to write something great or hire a great writer (preferably paid in advance) to launch your platform effectively.

2 Likes

Hi @JoshGrams , maybe I’m using DSL too loosely here. I consider the action system a embedded domain-specific language, so yes. Written in TypeScript.

Hi @dfabulich,

I’ve tried to separate the interface from the engine itself so it is very possible to run a game within a web UI. For most of what I’ve been doing so far I’m running right off the terminal. The terminal interface starts up much faster than Replit (which is supposed to be caching too :unamused:). With Deno it’s also possible to compile to an executable that that could be a way to distribute. Generating Z-code or Glulx is pretty far out of scope for me at the moment.

I hear you load-and-clear regarding an “admirable” story. It’s going to be a difficulty for me… I’m not much of a story teller. Also, I don’t have any expectation that my approach will be very popular for non-programmers. Hoping some people find it useful; or at least as interesting as I do.

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