โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โโโโโโโโ โโโโ โโโโ โโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโโ โ
โ โโโโโโโโ โโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโ โ
โ โโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโ โโโ โโโ โโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโโโโโโโโโ โ
โ โโโโโโโโ โโโ โโโโโโ โโโ โโโโโโโโโโ โโโโโโโโโ โโโโโโโโโโโโโ โ
โ โ
โ โก A Modern TypeScript Z-Machine Emulator โก โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Documentation ยท ๐ Quick Start ยท ๐ฎ Live Demo ยท ๐ฆ npm
- โจ Features
- ๐ฆ Installation
- ๐ Quick Start
- ๐ฎ Web Demo
- ๐ Supported Versions
- ๐ง I/O Adapter Interface
- ๐ API Reference
- ๐๏ธ Building
- ๐งช Testing
- ๐ Resources
- ๐ License
|
Run legendary Infocom titles: Zork, Planetfall, Hitchhiker's Guide, Enchanter, and 100+ more games from the golden age of interactive fiction. Pure TypeScript with no runtime dependencies in the core engine. Just clean, modern JavaScript. Works seamlessly in Node.js and all modern browsers. One codebase, everywhere. |
Import only what you need. The core engine is modular and optimized for minimal bundle size. Implement your own 898+ unit tests with 99.79% code coverage. Quetzal-compatible save format with full undo support. |
npm install zmachine๐ Also available via yarn, pnpm, or bun
yarn add zmachine
pnpm add zmachine
bun add zmachineimport { 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();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();๐น๏ธ Play classic text adventures directly in your browser โ no installation required!
Or run locally:
npm run dev:webThen 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)
| 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 | 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
| 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 |
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
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;
}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']);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| 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 checksrc/
โโโ 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
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.
|
๐ Specifications
|
๐ฎ Interactive Fiction
|
Contributions are welcome! Please read the Contributing Guide for guidelines.
git clone https://github.com/dlockard/zmachine.git
cd zmachine
npm install
npm test