A z-machine interpreter for Infocom-era text adventure games which can run on embedded hardware.
embcrusted
is forked from encrusted
, though it's been heavily refactored and stripped-down to run in an embedded no_std
environment. It's missing all these features (and more!):
- No Save / Load functionality
- No fancy resource-intensive data structures (like hash-maps for dictionaries).
- Not a single
#[derive(Debug)]
in sight
Here's a video of embcrusted
playing Zork I
off a Planck EZ Keyboard (running a ARM Cortex-M4 MCU), intercepting keystrokes as "input", and faking keystrokes as "output". Note that the game is running entirely on the keyboard, and only sends data to the device it's plugged into.
Check out the zorkey
branch on my fork of the QMK firmware if you're interested in the code that makes this possible.
embcrusted
is completely no_std
, though it does depend on alloc
.
At the moment, embcrusted
has only been tested on the following system:
Platform | Flash ROM | RAM |
---|---|---|
STM32F303xC (inside the Planck EZ keyboard) |
256kb | 48kb |
Storage and Memory requirements will vary by game (stack usage, dynamic-memory size, binary size) and compilation target (code density).
That said, here are some benchmarks I've collected using encrusted-ui-no-std
(compiled and run on amd64
, linked with libc, with inlined Zork I game data):
# additional RUSTFLAGS to strip binary during compilation.
# for full list of compiler flags, see the `profile.release` section in Cargo.toml
RUSTFLAGS='-C link-arg=-s' cargo +nightly run -p encrusted-ui-no-std --release
- Storage Requirements
- Just
embcrusted
: 60.43 KiB - Just Zork game data: 82.88 KiB
- Just
encrusted-ui-no-std
(with Zork andembcrusted
commented out): 19.07 KiB - Total Additional Storage: 143.32 KiB
- Just
- Heap Usage
- Zork base z-machine dynamic memory requirements: 11.58 KiB
- z-machine interpreter (before first exec): 2.70 KiB
- z-machine interpreter (after a bit of gameplay): 3.71 KiB
- High Watermark Heap usage: 17.62 KiB
I did not benchmark stack usage during interpreter execution (though it probably isn't too much?).
In a nutshell:
- Implement the
embcrusted::Ui
trait - Construct a new
Zmachine
interpreter with a reference to the game's data, your UI implementation, and an initial RNG seed - Run the interpreter in a loop, handling input and exit requests as necessary.
See the encrusted-ui
and encrusted-ui-no-std
packages for some basic examples.
At the moment, embcrusted
meets the resource constrains of my target hardware, and as such, it's unlikely that I'll be improving it much further.
That said, if you're interested in helping out, there are a couple of places that could be improved / need some more work.
PRs are welcome!
- RAM usage could be improved by refactoring
String
operations into in-place buffer manipulations, and replacingVec
s with static buffers.- This would also remove the dependency on
alloc
, for very resource constrained systems.
- This would also remove the dependency on
- Binary size could be cut down further by adding feature-flags for specific z-machine version features.
- It might be possible to shrink the game file down somewhat with some low-overhead decompression at runtime (e.g:
heatshrink
). I've done some preliminary tests, and while it's somewhat promising, it's not something I'll be integrating intoembcrusted
just yet. If you're interested in experimenting, take a look atbuffer.rs
👀 - A quick grep for "FIXME" or "TODO" will probably turn up plenty of things which need fixing 😄.