Skip to content

daniellockard/zmachine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

92 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                                                                           โ•‘
โ•‘   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—      โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ•‘
โ•‘   โ•šโ•โ•โ–ˆโ–ˆโ–ˆโ•”โ•      โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•  โ•‘
โ•‘     โ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—    โ•‘
โ•‘    โ–ˆโ–ˆโ–ˆโ•”โ•  โ•šโ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•    โ•‘
โ•‘   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—      โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ•‘
โ•‘   โ•šโ•โ•โ•โ•โ•โ•โ•      โ•šโ•โ•     โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ•  โ•šโ•โ•โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•  โ•‘
โ•‘                                                                           โ•‘
โ•‘          โšก A Modern TypeScript Z-Machine Emulator โšก                    โ•‘
โ•‘                                                                           โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Run classic Infocom text adventures in Node.js or the browser


npm version License: MIT TypeScript Zero Dependencies

Tests Coverage Node.js Browser


๐Ÿ“– Documentation ยท ๐Ÿš€ Quick Start ยท ๐ŸŽฎ Live Demo ยท ๐Ÿ“ฆ npm


๐Ÿ“‹ Table of Contents


โœจ Features

๐ŸŽฎ Classic Gaming

Run legendary Infocom titles: Zork, Planetfall, Hitchhiker's Guide, Enchanter, and 100+ more games from the golden age of interactive fiction.

๐Ÿš€ Zero Dependencies

Pure TypeScript with no runtime dependencies in the core engine. Just clean, modern JavaScript.

๐ŸŒ Universal Platform

Works seamlessly in Node.js and all modern browsers. One codebase, everywhere.

๐Ÿ“ฆ Tree-Shakeable

Import only what you need. The core engine is modular and optimized for minimal bundle size.

๐Ÿ”ง Extensible Architecture

Implement your own IOAdapter to connect the Z-machine to any platformโ€”terminals, GUIs, bots, or embedded systems.

โœ… Battle-Tested

898+ unit tests with 99.79% code coverage. Quetzal-compatible save format with full undo support.


๐Ÿ“ฆ Installation

npm install zmachine
๐Ÿ“‹ Also available via yarn, pnpm, or bun
yarn add zmachine
pnpm add zmachine
bun add zmachine

๐Ÿš€ Quick Start

๐Ÿ’ป Node.js

import { ZMachine, IOAdapter } from 'zmachine';
import { readFileSync } from 'fs';

// Implement your I/O adapter
class ConsoleIO implements IOAdapter {
  print(text: string) { process.stdout.write(text); }
  newLine() { console.log(); }
  // ... implement other methods
}

// Load and run a story
const storyData = readFileSync('zork1.z3');
const io = new ConsoleIO();
const zm = ZMachine.load(storyData, io);

await zm.run();

๐ŸŒ Browser

import { ZMachine } from 'zmachine';
import { WebIOAdapter } from 'zmachine/web';

// Set up DOM elements
const output = document.getElementById('output');
const input = document.getElementById('input');
const status = document.getElementById('status');

// Create I/O adapter
const io = new WebIOAdapter({
  outputElement: output,
  inputElement: input,
  statusElement: status,
});

// Load story file
const response = await fetch('zork1.z3');
const storyData = await response.arrayBuffer();

// Create and run machine
const zm = ZMachine.load(storyData, io);
io.initialize(zm.version);
await zm.run();

๐ŸŽฎ Web Demo

๐Ÿ•น๏ธ Play classic text adventures directly in your browser โ€” no installation required!

Or run locally:

npm run dev:web

Then open http://localhost:8080 and drag-and-drop a story file to start playing!

๐Ÿ–ผ๏ธ Screenshot Preview

The web demo features a retro terminal aesthetic with:

  • ๐Ÿ“Ÿ Classic green-on-black CRT styling
  • โŒจ๏ธ Full keyboard input support
  • ๐Ÿ’พ Save/restore game state
  • ๐Ÿ“œ Transcript recording
  • ๐ŸŽจ Z-machine text styling (bold, italic, colors)

๐Ÿ“Š Supported Versions

Version Status Era Notable Games
V1 โœ… Full 1980 Early Zork prototypes
V2 โœ… Full 1981 Early Infocom games
V3 โœ… Full 1982-1987 Zork I-III, Planetfall, Hitchhiker's, Enchanter trilogy
V4 โœ… Full 1985-1988 A Mind Forever Voyaging, Trinity, Bureaucracy
V5 โœ… Full 1987+ Beyond Zork, Sherlock, most Inform games
V6 โš ๏ธ Partial 1988+ Graphics games (Shogun, Zork Zero, Arthur)
V7 โœ… Full โ€” Large V5 variant
V8 โœ… Full โ€” Large V5 variant, modern Inform games

Note: V6 games require graphics/mouse support which is not implemented. Text-only features work.

๐Ÿ“œ Full Feature Support Matrix

โœ… Fully Implemented

Category Features
Text Output print, print_ret, new_line, print_char, print_num, print_addr, print_paddr, print_obj, print_unicode
Text Input read (sread/aread), read_char with timeout support, tokenization
Arithmetic add, sub, mul, div, mod, random, log_shift, art_shift
Logic and, or, not, test, test_attr
Comparison je, jl, jg, jz, jin
Control Flow call, call_1n/2n/vn/vs, ret, ret_popped, rtrue, rfalse, jump, piracy
Variables load, store, inc, dec, inc_chk, dec_chk, pull, push, loadw, loadb, storew, storeb
Objects get_parent, get_child, get_sibling, get_prop, get_prop_addr, get_prop_len, get_next_prop, put_prop, insert_obj, remove_obj, set_attr, clear_attr, test_attr, print_obj
Screen split_window, set_window, erase_window, erase_line, set_cursor, get_cursor, set_text_style, set_colour, set_font, buffer_mode
Sound sound_effect (beeps only, sounds 1 & 2)
Streams output_stream (screen, transcript, memory table), input_stream
Save/Restore save, restore (Quetzal format), save_undo, restore_undo
Tables copy_table, scan_table, print_table
Misc verify, quit, restart, show_status, nop, check_arg_count, catch, throw, tokenise, encode_text, check_unicode

โŒ Not Implemented (V6 Graphics)

These features are exclusive to V6 games and require a graphics layer:

  • Picture opcodes: draw_picture, picture_data, erase_picture, set_margins, picture_table
  • Mouse support: mouse_window, read_mouse
  • Graphics windows: move_window, window_size, window_style, scroll_window, set_true_colour (extended)
  • Font metrics: get_wind_prop, put_wind_prop, make_menu

๐Ÿ”ง I/O Adapter Interface

Implement the IOAdapter interface to connect the Z-machine to your platform:

๐Ÿ“‹ View Full Interface Definition
interface IOAdapter {
  // Lifecycle
  initialize?(version: number): void;
  
  // Text output
  print(text: string): void;
  printLine?(text: string): void;
  newLine(): void;
  
  // Input (async)
  readLine(maxLength: number, timeout?: number): Promise<ReadLineResult>;
  readChar(timeout?: number): Promise<number>;
  
  // Screen management
  showStatusLine(location: string, score: number, turns: number, isTime: boolean): void;
  splitWindow(lines: number): void;
  setWindow(window: number): void;
  eraseWindow(window: number): void;
  eraseLine?(): void;
  setCursor?(line: number, column: number): void;
  getCursor?(): { line: number; column: number };
  setTextStyle?(style: number): void;
  setForegroundColor?(color: number): void;
  setBackgroundColor?(color: number): void;
  
  // Sound
  soundEffect?(number: number, effect: number, volume: number): void;
  
  // Streams
  setOutputStream?(stream: number, enabled: boolean, table?: number): void;
  
  // Save/restore
  save(data: Uint8Array): Promise<boolean>;
  restore(): Promise<Uint8Array | null>;
  
  // Game control
  quit(): void;
  restart(): void;
}

๐Ÿ“– API Reference

๐Ÿ–ฅ๏ธ ZMachine

The main class for running Z-machine games.

// Load a story file
const zm = ZMachine.load(storyData: ArrayBuffer, io: IOAdapter);

// Or use constructor directly
const zm = new ZMachine(storyData, io);

// Run until halted or waiting for input
const state = await zm.run();  // Returns RunState

// Access game state
zm.version      // Z-machine version (1-8)
zm.state        // RunState: Stopped, Running, WaitingForInput, Halted
zm.memory       // Direct memory access
zm.header       // Header fields

// Utilities
zm.getObjectName(objectNum)   // Get object's short name
zm.printText(address)         // Decode text at address
zm.lookupWord(word)           // Look up word in dictionary
zm.restart()                  // Restart the game
๐Ÿ’พ Memory Access

Low-level memory access for tools and debugging:

const value = zm.memory.readWord(address);   // Read 16-bit word (big-endian)
const byte = zm.memory.readByte(address);    // Read 8-bit byte
zm.memory.writeWord(address, value);         // Write 16-bit word
zm.memory.writeByte(address, value);         // Write 8-bit byte
๐Ÿ“ Text Encoding/Decoding
import { ZCharDecoder, ZCharEncoder, ZSCII } from 'zmachine';

// Decode Z-characters to string
const decoder = new ZCharDecoder(memory, version, abbreviationsAddr);
const { text, bytesRead } = decoder.decode(address);

// Encode string to Z-characters (for dictionary lookup)
const encoder = new ZCharEncoder(version);
const encoded = encoder.encode('zork');

// ZSCII character conversion
const unicode = ZSCII.toUnicode(zsciiCode);
const zscii = ZSCII.fromUnicode(unicodeChar);
๐Ÿ’ฝ GameState (Save/Restore)
import { GameState, Quetzal } from 'zmachine';

// Create save state
const state = GameState.capture(zmachine);
const quetzalData = Quetzal.write(state, originalStoryData);

// Restore from Quetzal file
const state = Quetzal.read(quetzalData, originalStoryData);
GameState.restore(zmachine, state);
๐ŸŒ WebIOAdapter Features

The built-in WebIOAdapter includes:

  • Text styling: Bold, italic, fixed-width, reverse video
  • Colors: All 8 standard Z-machine colors
  • Status line: Score/moves or time display
  • Sound: Beep effects via Web Audio API
  • Save/Restore: File download/upload with localStorage backup
  • Transcript: Downloadable game transcript
  • Recording: Record and playback input sessions
const io = new WebIOAdapter({
  outputElement: document.getElementById('output'),
  inputElement: document.getElementById('input'),
  statusElement: document.getElementById('status'),
  onQuit: () => console.log('Game ended'),
  onRestart: () => location.reload(),
});

// Enable transcript
io.setOutputStream(2, true);
io.downloadTranscript();

// Record inputs
io.startRecording();
// ... play game ...
io.stopRecording();
io.downloadRecording();

// Playback recorded inputs
io.loadPlayback(['north', 'take lamp', 'light lamp']);

๐Ÿ—๏ธ Building

npm install
npm run build          # Compile TypeScript
npm run build:web      # Build web player
npm run build:lib      # Build npm library
npm run dev:web        # Start web dev server

๐Ÿงช Testing

Command Description Tests
npm test Core tests 817
npm run test:web Web tests 81
npm run test:all All tests 898+
npm run test:coverage With coverage 99.79%
npm test               # Run core tests
npm run test:watch     # Watch mode
npm run test:coverage  # Generate coverage report
npm run lint           # ESLint check

๐Ÿ“ Project Structure

src/
โ”œโ”€โ”€ core/              # ๐ŸŽฏ Zero-dependency core
โ”‚   โ”œโ”€โ”€ cpu/           #    Stack and call frames
โ”‚   โ”œโ”€โ”€ dictionary/    #    Word lookup and tokenization
โ”‚   โ”œโ”€โ”€ execution/     #    Opcode execution engine (102 handlers)
โ”‚   โ”œโ”€โ”€ instructions/  #    Opcode definitions and decoder
โ”‚   โ”œโ”€โ”€ memory/        #    Memory and header access
โ”‚   โ”œโ”€โ”€ objects/       #    Object tree and properties
โ”‚   โ”œโ”€โ”€ state/         #    Save/restore and Quetzal format
โ”‚   โ”œโ”€โ”€ text/          #    ZSCII and Z-character encoding
โ”‚   โ”œโ”€โ”€ variables/     #    Variable access (locals, globals, stack)
โ”‚   โ””โ”€โ”€ ZMachine.ts    #    Main VM class
โ”œโ”€โ”€ io/                # ๐Ÿ”Œ I/O adapter interfaces
โ”œโ”€โ”€ types/             # ๐Ÿ“ TypeScript type definitions
โ”œโ”€โ”€ web/               # ๐ŸŒ Browser-based player
โ”‚   โ”œโ”€โ”€ WebIOAdapter.ts
โ”‚   โ””โ”€โ”€ main.ts
โ””โ”€โ”€ index.ts           # ๐Ÿ“ฆ Public API exports

๐ŸŽฒ Finding Story Files

Story files (.z3, .z5, .z8, etc.) are copyrighted. Here's where to get them legally:

Source Type Description
๐Ÿ›’ GOG.com Commercial Infocom collections for sale
๐Ÿ“š IF Archive Free Thousands of free Inform games
โœ๏ธ Inform 7 Create Write your own Z-machine games
๐Ÿ”ง ZILF Create Write games in ZIL (original Infocom language)

Tip: Place story files in a roms/ folder (gitignored) for integration testing.


๐Ÿ“š Resources

๐Ÿ“– Specifications

๐ŸŽฎ Interactive Fiction


๐Ÿค Contributing

Contributions are welcome! Please read the Contributing Guide for guidelines.

git clone https://github.com/dlockard/zmachine.git
cd zmachine
npm install
npm test

๐Ÿ“„ License

MIT ยฉ Daniel Lockard


Made with โ˜• and a love for classic interactive fiction.

โฌ† Back to top

About

Resources

License

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages