惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
V
V2EX
PCI Perspectives
PCI Perspectives
Latest news
Latest news
博客园 - 三生石上(FineUI控件)
C
CERT Recently Published Vulnerability Notes
W
WeLiveSecurity
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
H
Heimdal Security Blog
Attack and Defense Labs
Attack and Defense Labs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 叶小钗
V
Visual Studio Blog
Jina AI
Jina AI
P
Proofpoint News Feed
罗磊的独立博客
SecWiki News
SecWiki News
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
The Hacker News
The Hacker News
Hugging Face - Blog
Hugging Face - Blog
N
News and Events Feed by Topic
NISL@THU
NISL@THU
T
Tailwind CSS Blog
T
Tenable Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recent Announcements
Recent Announcements
H
Hacker News: Front Page
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Tor Project blog
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
S
Security Affairs
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
G
GRAHAM CLULEY

Whitebeard's Realm

Unequal, and hungry for it There's a STAR man, waiting in the sky Introducing Uruk, a language for tabletop games The diving watch I never knew I wanted Make your own interactive fiction client in Elm Introducing Planedrift > ASK THE VM WHERE IS THE PLAYER Playing Zork with a gen alpha AI Building a better crossword page for my daily cryptic hit What AI is doing for me, in a difficult situation It's Your Turn — a prompt deck for new roleplayers Seven reasons that Trump is a bad GM. Very bad You've Been Pawned — Now With a Snazzy New Look Six months on startplaying.games An online tool to make playable paper pawns Campaign report: Dragon of Icespire Peak: session #6 Campaign report: Rime of the Frostmaiden: session #4 Campaign Report: Dragon of Icespire Peak: Session #5 A few months on StartPlaying.Games Campaign report: Rime of the Frostmaiden: session #3 Character Quirks for easier roleplay How I use Avrae An Owlbear/Discord/DndBeyond checklist for new campaigns About me Projects Talks Videos
Building a Z-Machine in the worst possible language
whitebeard · 2026-04-12 · via Whitebeard's Realm

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.

elm-zmachine running Zork in a terminal — status line showing “West of House | Score: 0 Turns: 2”, the welcome text and a few opening moves into Forest Path

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

There are no infinite loops in elm so instead we run the machine for a maximum number of instructions and expect it to tell us if it hasn’t finished. We get back an updated machine and a StepResult:

ZMachine.runSteps : Int -> ZMachine -> StepResult

A StepResult gets you the result of a step/steps, a list of output events and a new machine:

type StepResult
    = Continue (List OutputEvent) ZMachine
        -- we ran the steps, but there are more to run
    | NeedInput InputRequest (List OutputEvent) ZMachine
        -- we need to get some input from the user
        -- once we have it call ZMachine.provideInput
    | NeedSave Snapshot (List OutputEvent) ZMachine
        -- the user's asked to save this snapshot
        -- once we've done it call ZMachine.provideSaveResult
    | NeedRestore (List OutputEvent) ZMachine
        -- the user wants to load a snapshot
        -- once we've done it call ZMachine.provideRestoreResult
    | Halted (List OutputEvent) ZMachine
        -- we're done
    | Error ZMachineError (List OutputEvent) ZMachine
        -- oops

An OutputEvent can be one of the following - the main ones to handle are PrintText, NewLine and ShowStatusLine:

type OutputEvent
    = PrintText String
    | NewLine
    | ShowStatusLine StatusLine
    | SplitWindow Int
    | SetWindow Window
    | EraseWindow Int
    | SetCursor Int Int
    | SetBufferMode Bool
    | PlaySound Int

I think that makes for a very simple surface with enough functionality to build a good client.

There are more details on the github page and in the repository there’s an example node.js/elm app that shows an example of using it with a copy of Zork1. If you’ve ever wanted to build your own infocom client - and really, who hasn’t? - this should get you a long way there.