Skip to content
Tech News
← Back to articles

Building a Z-Machine in the worst possible language – Whitebeard's Realm

read original get Z-Machine Emulator Software → more articles
Why This Matters

This article highlights the challenge and ingenuity involved in building a Z-machine emulator using Elm, a purely functional language not naturally suited for low-level memory manipulation. It underscores the importance of creative problem-solving in software development, especially when working within constraints, and demonstrates how modern data structures can enable unconventional projects. For consumers and the tech industry, it showcases the potential for functional programming languages to tackle complex, performance-sensitive tasks in innovative ways.

Key Takeaways

The Z-machine is an early 1980s virtual machine made by Infocom to allow them to compile their text adventure games once and have them run on multiple architectures. It’s a good trick: if you have 10 games that need to run on 10 different computer architectures (ah, the good old days!) you can reduce the work from 10 x 10 compilation nightmares to 10 Z-machines + 10 compilation nightmares. And that maths compounds as you add more architectures and more games. There are many modern Z-machines that are part software archeology and part love letters to this age of text adventures.

I’ve always wanted to make one of my own and now I have!

My language of choice is Elm. But… the Z-machine is a direct memory access machine with a separate stack, designed to run on a very modest machine. The worst language to implement this in would be one in which all data structures are immutable and no functions have side-effects, a pure language. Like Elm. But there we are.

As an example of how silly an idea this is: imagine that you’re storing the memory as an Array of bytes. To write a bit to the memory in a non-pure language you might have a function that takes the memory, an address and a value. It would then write the value into the memory, perhaps returning a success or error code. We can’t do that in Elm.

Instead you’ll have a function that takes the same parameters but then returns a new memory with the byte changed. The original parameter remains unaffected. For a Z-machine emulator that looks like a lot of data copying and a performance/memory disaster - just to set one byte. Initially I thought it wouldn’t be practical without a lot of clever data structures. But actually, it turns out that Elm was already there. Arrays are backed by a persistent data structure - a RRB trie variant - that allows much better performance when slicing and appending. An initial test seemed to prove this and so I pushed on telling myself I wasn’t as crazy as I suspected.

A couple of weeks later - writing tests, wrestling with the Z-machine spec (the text encoding alone took a few days to understand), trying to find immutable patterns to support mutable operations - and I have a working Z-machine that can run a .z3 infocom game (version 3, the most common version) and that passes the Czech compliance test for Z-machines.

Some of it is not very pretty, but it works and it’s performant enough to be a viable way to build a good interactive fiction player - in the browser or elsewhere.

The main thing I want from this is to do some interesting client experiments (like if-pal). To make that easier I’ve done my best to give the library a clean interface for stepping and handling events. Time for a bit of elm code. I’m sorry.

It’s one line to load a .z3 file and get back a ZMachine:

ZMachine . load : Bytes -> Result String ZMachine

... continue reading