Existential Types in Typescript Through Continuations

(this post was originally from a comment I made on /r/typescript)

My favorite advanced” thing about Typescript is that while it does not currently support existential types, you can actually encode existential types through continuations.

For example, say you have the following interface and known implementations:

interface Property<T> {
    pget: () => T;
    pset: (value: T) => void;
}

class NumberProperty implements Property<number> {
    constructor(private value: number) {
    }

    pget() { return this.value }
    pset(value: number) { this.value = value }
}

class DateProperty implements Property<Date> {
    constructor(private value: Date) {
    }

    pget() { return this.value }
    pset(value: Date) { this.value = value }
}

Now you want to maintain a list of Properties. You don’t care what types each individual Property is, just that they are valid properties. How do you type this?

let properties: Property<???>[];

You could always put in any or unknown, but we know that leads to problems down the line–unknown would require casting to use, but we don’t know what type to cast to. And any would let us do bad things like having properties where the getters and setters have different types.

Ideally we’d be able to tell Typescript for each individual Property in my list, the Property is over a generic type T, and even though I don’t know what T is, it exists”, which is essentially what existential types are. There are GH issues suggesting syntax like Property<*> for this, but who knows when they’ll make progress.

For now, you can actually get this working in a roundabout way using continuations:

type PropertyCont = <R>(cont: <T>(prop: Property<T>) => R) => R;

function makePropCont<T>(property: Property<T>): PropertyCont {
    return <R>(cont: <T>(prop: Property<T>) => R) => cont(property);
}

let properties: PropertyCont[];

This abuses the cool inference Typescript has for functions to allow us to encode a generic type without ever having to say what that type is–existential types!

Now, we can use this list of Properties as intended without any casts or any:

properties.push(
    makePropCont(new NumberProperty(0)),
    makePropCont(new DateProperty(new Date)),

    // ERR won't pass as the generic type isn't internally consistent
    // which is correct behavior
    makePropCont({
        pget: () => 44,
        pset: (badValue: string) => { }
    })
);

properties.forEach(cont => cont(prop => {
    // val is of type `T`, which while we haven't defined anywhere
    // TS is able to infer
    const val = prop.pget();
    prop.pset(val);
}));
2020-02-11 · programming

Invertebrae: a Post-Mortem

Editor’s note: this post is about a game I created for Ludum Dare 44, and was originally lifted from there.

Ludum Dare 44 is over! It’s actually been over for a couple of days now, so let’s just get to it.

Results

Results!Results!

Overall

This is my third Ludum Dare where I actually completed a game, and as I continue to improve as a dev, my Overall score also continues to improve. Extremely happy to be in the top 50–next time, maybe the top 10?

Jokes aside, it makes me happy to see that a text-adventure game can do this well. Glad to see that the LD audience is diverse in their interests.

Theme

Oof. I know entries that stretch interpretations of LD themes typically don’t do too well, but I definitely was hit pretty hard. In case you’re wondering how I feel my game tackles the theme, I’m going to actually cite what my friend had to say, as he put it way better than I could:

Your life is the sum of your earned experiences. You have to manage how you spend each word because they are the currency you use the act.

In that regard, as we’ll talk about later, the game even punishes you for amassing too much currency ;)

Feedback

I had gotten a lot of great feedback on Invertebrae from friends, family, and of course the Ludum Dare community. While some of the feedback I had expected the moment I hit the publish button, there was also completely valid criticism that I hadn’t seem coming at all.

How Long Is Your Game Really?

Nearing the end of the compo period, I was running through Invertebrae nearly every half hour, making sure that grammar was sound, the UI never bugged out, and that I hadn’t inadvertently created any soft-locks with the puzzle mechanism. Each of these playthroughs would take me roughly 5-10 minutes, so in my head, I just pegged my game as a 20-minute game at most.

Imagine my surprise when the comments started coming in with playthrough times of 30 minutes, an hour, two hours, heck my friend even stopped playing to go to sleep and then continued it the next day.

First off, it makes me so happy that anyone would play a game of mine for more than like 20 minutes, much less two hours. The idea of creating something that could hold someone’s attention for that long really is some of the nicest feedback I can receive as a game developer. Plus, let’s be frank–most Ludum Dare games can be fully experienced in 5-10 minutes. In the time it takes to play a one-hour text-adventure, you could have rated like ten other great games.

However, not knowing that my game would have such a playtime means that I didn’t design the game for such a playtime, and that was definitely reflected in the criticism. As I addressed in a post-compo update, the core drag-and-drop UX is pretty, but also pretty inefficient. Dragging words back and forth leads to a lot of unnecessary movement, and this is only exacerbated by the large number of words you could amass.

The Moon Is Exponentially Far Away

Due to one of the core puzzles in the game, a lot of people ended up hoarding a lot of words. This was definitely not the intention–the multi-page feature of the word bank was actually only added in the last five hours after I realized it was even possible to collect enough words such that they went off the bottom of the screen. However, with puzzles like open computer that rely on you having to re-use words you played prior, and navigating between nodes of the story being a slow affair, it was silly of me to not expect players to store every word they saw in worry that it would be necessary later.

Instead of puzzles that are designed to be worked out from a pool of four to seven words, now even the simplest puzzles are obscene, with tons of accidentally reasonable combinations of words not satisfying the one or two answers I had in mind. Plus, the moment you learn that some puzzles have multiple answers is the moment you start thinking that all puzzles have multiple answers. I tried to poke fun at this with things like the stairwell ending, where you play pet cat after walking on a stairwell that squeaks like a dying cat”. But while that ending is quite funny (well, only 190 places of funny as the results showed), the hundreds of non-existent endings that players tried to get to certainly aren’t.

A lot of people had noted that a couple of the puzzles were really difficult. While I agree that a few were a bit obtuse, I do think it was primarily due to the excess of words that people could collect. Even sort of sketchy phrases like pretend security are way easier to reason if they’re only two of five words in your word bank.

If I were to work on Intevertebrae in the future, I would definitely design the puzzles to have fewer words overall and give each word more uses. Not only would this decrease the frustration of cycling through endless combinations, but it would also increase the number of actual word puzzles, which is the real difficulty (and source of fun) that I wanted present in my game.

OK People Said Good Things Too

I’ve gone on about the most popular critical feedback for a bit now, but people also plenty of nice things to say:

Issues aside, I’m really proud of how I did this Ludum Dare, and I’m looking forward to the next one. See y’all at LD45!

2019-05-23 · gamedev · ludum-dare

Invertebrae: a Pre-Mortem

Editor’s note: this post is about a game I created for Ludum Dare 44, and was originally lifted from there.

I want to save my post-mortem until after the judging period is done, so until then let’s pre-mortem! I think the majority call these dev-diaries” but pre-mortem is obviously a much cooler term.

Formulation

Getting the ball rolling.Getting the ball rolling.

Like all games, it started with a notebook and various existing chat threads. A lot of my IRL friends are at least vaguely interested in game development (some of them even participating in past Ludum Dares!) so it’s incredibly valuable to bounce ideas back and forth.

I’ve wanted to make a terminal-esque game since at least LD42, so I went into this expecting to force whatever theme won into my game, rather than the other way around. Your life is currency was a little tricky until I figured I could stretch the meaning of currency” to expand to currentness”, which is really just a long-winded way of saying now”. That’s what I’ll do: a narrative game set in a terminal that plays with the idea of now”. I’ll also put money in the story or something to make the connection a bit more concrete.

As to what the heck invertebrae” even means…it’s a cooler, less correct way of saying invertebrate”. And invertebrate”, while having a specific scientific meaning, could also be literally translated as spineless”, which is how I’m choosing to interpret it with regards to the game’s themes.

Inspiration

For story inspiration, I looked towards one of my favorite visual novel series, Zero Escape. In particular, I was most focused on the second in the series, Virtue’s Last Reward; without spoiling too much, it also has a focus on getting information between multiple in-game timelines. I didn’t really want the plot of my game to revolve directly around time-travel, but the idea of being able to use information gained from bad ends” stuck.

In terms of aesthetics, I can’t for the life of me find the original picture, but a while back on Twitter I saw someone post their custom Linux desktop setup and was absolutely enamored with it. As you can probably guess, their terminal was a deep blue-black with golden-yellow text.

Implementation

While making an actual terminal game would have been neato, it was imperative that my game could run in the browser this time around. And with my day job being a front-end React developer, it really only made sense to just build it with React. Plus, this gave me an occasion to make a full=fledged project with the recently released React Hooks, which if you haven’t heard are essentially a monadic-ish effect system for React components (hopefully that cleared things right up).

It also meant that I got to take advantage of some of the great tooling in the React ecosystem, such as react-dnd. This made the drag-and-drop feature mostly straightforward to implement, although funnily enough react-dnd has some problems cooperating with the React Hooks syetem, so there was some time spent trying to get the two to make peace.

Initial GIF of the engine.Initial GIF of the engine.

One cool thing I like about how the game is built is that the engine” is mostly separate from the story. As many people found out, the entirety of the story is actually within a singular JSON file that the game engine loads at runtime. Theoretically, if you like the mechanics in my game but were meh on the story content (don’t worry it’s fine), you could actually clone my repo, edit the story.json file with your marvelous creation, and boom you have Twine but with drag-and-drop!

Music

I learned from my last two Ludum Dare games that music honestly might as well be mandatory (and my first game even had music, albeit mixed too quietly for anyone to hear). As fun as the game is, playing something with absolute silence in the background is just never going to be an optimal experience. And even if you don’t have a musical bone in your ear, there’s so much free software these days that’s so immediately approachable. Plus, the circle has swung back around and minimalism is cool again, so you can totally get away with arpeggiating a chord into infinity.

Groovebox how I love thee.Groovebox how I love thee.

For the music here, I used Groovebox, a really fun synth-sequencer program for iOS. If you have an iWhatever, stop reading and download it right now–it’s free!! I spend so many bus rides now just making short little loops. And much like gamedev, while 99.9% of games don’t quite hit the mark, the 0.1% that do make it all worth the while.

Before I leave you…

Can’t unsee.Can’t unsee.

During the compo, my sister pointed out that the font I chose kind of looked like a cleaned-up Comic Sans. Whoops.

2019-05-09 · gamedev · ludum-dare

View the archives