░▒▓██████▓▒░░▒▓█▓▒░ ░▒▓██████▓▒░░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓███████▓▒░
░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
░▒▓██████▓▒░░▒▓████████▓▒░▒▓██████▓▒░░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░
A faithful Clojure port of Zork I: The Great Underground Empire.
Clork is winnable! You can play through the entire game, collect all 19 treasures, deposit them in the trophy case, and enter the Stone Barrow to see the Zork II teaser.
A complete 358-command playthrough is included in scripts/transcript.txt.
- All 110 rooms - Complete map including the maze, coal mine, and all special areas
- All 19 treasures - Egg, chalice, painting, coffin, diamond, etc.
- Combat system - Troll and thief fights with ZIL-accurate mechanics
- Major puzzles - Loud room, dam, rainbow, cyclops, canary, coal mine machine
- Thief AI - The wandering thief who steals treasures and opens the egg
- Light/dark mechanics - Lantern, candles, and the dreaded grue
- Full parser - Multi-object commands, pronouns, disambiguation
lein runZORK I: The Great Underground Empire
Infocom interactive fiction - a fantasy story
Copyright (c) 1981, 1982, 1983, 1984, 1985, 1986 Infocom, Inc. All rights reserved.
ZORK is a registered trademark of Infocom, Inc.
Release 1 / Serial number 1
Clojure port by Nathan Douglas
West of House
You are standing in an open field west of a white house, with a boarded front door.
There is a small mailbox here.
>
Clork includes a comprehensive debug system for development and testing. All commands start with $:
$debug state- High-level game state overview$debug here- Current room details$inspect <object>- Deep inspection with flag analysis$where <object>- Find where an object is located
$goto <room>- Teleport to any room$purloin <object>- Take any object (bypass checks)$flag <id> <flag>- Set a flag$scenario <name>- Load test scenarios (e.g.,$scenario equipped)
$path <from> <to>- Find shortest path between rooms$reachable <room>- Show all rooms reachable from a location$route <room1> <room2> ...- Plan optimal route through multiple rooms
$undo [n]- Undo last command(s)$redo [n]- Redo undone command(s)$history- Show command history
# Run all tests
lein test
# Run with pending tests (unimplemented features)
lein test :pendingThe test suite includes 474 tests covering rooms, objects, verbs, combat, and game mechanics.
Clork includes a structured API for training machine learning agents to play the game. Instead of generating free-form text, agents interact via structured actions and receive rich state observations.
# Run in ML mode (JSON lines on stdin/stdout)
lein run --ml
# With reward signals for RL training
lein run --ml --ml-rewardsA Python wrapper provides an OpenAI Gym-style interface:
from clork_env import ClorkEnv
env = ClorkEnv(use_rewards=True)
obs = env.reset()
while not obs['game_over']:
actions = env.valid_actions() # No text generation needed!
action = agent.select(actions) # Your agent here
obs, reward, done, info = env.step(action)
env.close()Actions are structured data, not free-form text:
{"verb": "go", "direction": "north"}
{"verb": "take", "direct-object": "lamp"}
{"verb": "put", "direct-object": "egg", "prep": "in", "indirect-object": "nest"}With --ml-rewards, the API provides shaped rewards for training:
| Signal | Default Weight | Description |
|---|---|---|
score_delta |
1.0 | Change in game score |
novel_room |
5.0 | First visit to a room |
novel_message |
0.5 | New unique game output |
object_taken |
2.0 | New object acquired |
container_opened |
1.5 | New container/door opened |
death |
-10.0 | Player died |
cd python
PYTHONPATH=. python example_random_agent.py --episodes 5 --steps 100
# Output:
# Episode 1: Reward=41.5, Rooms=8, Score=0
# Episode 2: Reward=35.0, Rooms=6, Score=0
# ...See python/clork_env.py for the full API documentation.
src/clork/
├── core.clj # Main game loop and initialization
├── parser.clj # Natural language parser
├── rooms.clj # All 110 room definitions
├── objects.clj # All object definitions
├── verbs.clj # Verb handlers
├── combat.clj # Combat mechanics
├── debug/ # Debug command system
│ ├── pathfind.clj # Pathfinding commands
│ ├── scenarios.clj # Test scenarios
│ └── ...
└── ml/ # Machine learning API
zork-i/ # Original ZIL source for reference
Zork had a huge impact on me. I was a poor kid without a computer, but I could "check out" a computer from the school district - an Apple //e, I think - which came with a number of games and other software, including the Zork trilogy.
I wasn't a terribly savvy gamer and not good at creating coherent maps (not that Zork makes this easy) or recording my experiments, so a lot of my experience with the games was to load Zork I, play it until I was ready to throw the computer out the window, then repeat that with Zork II, Zork III, and then I was ready to go back to Zork I. I spent a lot of time too frustrated to do anything but rest my head on my arms while the Apple hummed steadily in the background.
I couldn't make head nor tails of a lot of the puzzles (though I muddled through many of them), but that often mattered less than you might think. I spent a lot of time just wandering around. Being young and naïve about video game tropes (so many of which this game invented!), I spent a lot of time in places like FOREST-3 - "a dimly lit forest, with large trees all around" but with "no tree... suitable for climbing," where "the rank undergrowth prevents eastward movement" and "storm-tossed trees block your way" south. I thought, perhaps, there was some way to get beyond this and go to other parts of the world, find other parts of the Great Underground Empire, etc.
I had to return that borrowed //e eventually, but there were other ways I could interact with Zork. I read the CYOA-esque books. I wrote short stories that borrowed heavily from its magic system, its worldbuilding, and its unique blend of juvenile humor and wistful melancholia. And my earliest independent programming projects, once I finally got my own computer, were writing my own "interactive fiction" games in QBASIC, TADS, and Inform 5 or 6. Despite its age, substantial design issues, and various other limitations, Zork is still a cornerstone of how I think about game design, storytelling, etc.
My professional experience as an engineer has almost entirely been with object-oriented programming. I've been interested for a long time in purely functional programming and functional languages, but haven't had much opportunity to engage.
At some point a few years back, I found that the original MDL and ZIL sources for Zork had been released. So I did what I think any engineer with ties to Zork would do - I jumped into the source to look at the parser. And recoiled in horror. The parser would be an absolutely gnarly, hairy beast in any language, but it's almost completely unreadable to me in ZIL.
Trying to read it was like trying to read Latin - I can figure out an individual word here and there, and then use those as context to figure out the meaning of another word here, and then zoom out to try to understand a sentence... but then I've forgotten what I guessed one of the words to mean, and so forth. I can get trapped in an expression, zooming in and out and moving forward and backward. And that's before trying to keep track of the nested parentheses, angle brackets, and so forth.
So, I thought, perhaps I might port it to a modern functional language with a similar Lisp-ish syntax - Clojure - that would be more likely to conform with modern programming language conventions (and that has a working compiler, syntax highlighting, automated testing tools, books I can read, etc). And so I could decode the arcane ZIL syntax and get some idea of how to transform it into (hopefully) idiomatic Clojure.
Another way to think about this, the one I prefer, is that I'm still exploring Zork, but in a new level. I'm once again an adventurer/archaeologist/"cretin", muddling around through an incredibly cryptic, deeply esoteric, unfair and unforgiving world. I'm taking each treasure, puzzle, room, trap, joke, and adversary, carefully extracting it from the webs and rust and impacted dirt, blowing the dust off, and installing it into a new trophy case.
Fortunately, at this point in my life, I have an excellent array of tools in my inventory.
| Source | Lines |
|---|---|
| ZIL original | 12,041 |
| Clojure (with comments) | 28,109 |
| Clojure (code only) | 18,405 |
The Clojure version is about 1.5x larger than the ZIL original (comparing code-only), or 2.3x including all the ZIL reference comments.
Why the expansion?
- ZIL is brutally terse - designed for 8-bit machines with ~80KB of memory. Every byte mattered.
- Clork adds features ZIL doesn't have - debug commands (
$goto,$inspect,$path), ML training API, undo/redo, pathfinding, test scenarios. - Clojure is more explicit - ZIL uses implicit globals and terse macros; idiomatic Clojure threads state explicitly.
- ~900 lines of ZIL code preserved as comments - for reference and traceability.
This is a fan project for educational purposes. Zork is a trademark of Activision.