This is an experimental fork of CheapGlk to explore how large language models can enhance interactive fiction gameplay through intelligent command interpretation.
This is CheapGlk (the simplest Glk I/O library) updated with transparent LLM-based natural language processing. It allows players to use conversational language instead of precise parser commands when playing interactive fiction games.
Interactive fiction traditionally requires players to learn specific command syntax. "North" or "Take key" work fine, but "let's check out the building" or "Take it" (referring to the key) don't, unless the game specifically has code to allow that.
But the game doesn't actually care what words the player uses to communicate their intent. It just needs a normalized command like "inventory" or "wear coat". The interpretation of what someone actually meant is just an I/O problem. It's not a game logic problem.
This fork implements a prototype of putting the LLM at the Glk layer. A player types "go to the bedroom", the I/O layer grabs that, asks the LLM what they probably meant given the scene context, gets back "north", and passes that to the game. This way the game never needs to know that any of this happened. It just receives "north" like it always does.
So when you type:
- "what do I have?" it gets converted to "inventory"
- "let's go to the bedroom" it gets converted to "north" (or any appropriate direction)
- "wear my winter coat" it gets converted to "wear coat"
The intention is to keep the game unaware of any of this and thus keep all existing games playable.
One fun/interesting aspect is that because of the nature of LLMs, it's possible to to use a totally different language (or even transliteration) and the input will be converted correctly:
- "öppna dörren" gets converted to "open door"
- "efta7 el bab" gets converted to "open door"
Player Input: "go to the bedroom"
↓
[Glk I/O Layer]
- Captures input
- Sends to LLM with scene context
- Gets back: "north"
↓
Game VM receives: "north"
- Processes normally
↓
Output rendered to player
The LLM integration lives entirely in the Glk library (I/O layer), never touching the VM. This way we can leave Glk to handle input and output, including natural language interpretation, and the VM to execute the game logic, completely unaware of how input was obtained.
The LLM reads scene descriptions to understand room layouts and connect player intent to actual commands:
Living Room
A door to the north leads to the bedroom.
> go to the bedroom
[LLM: "go to the bedroom" -> "north"]
The system understands the current position to avoid nonsensical interpretations. If you're already in a location, asking to go there gets caught:
Back Alley
> go to the alley
[LLM: "go to the alley" -> ""]
{Default game response}
Some games ask the user to input something that's supposed to be treated like a raw string, such as their name. In these cases, the player can surround their input with brackets [] to skip the LLM interpretation layer.
What is your name, brave Knight? [Beshr]
"Wake up, Beshr, wake up!" Someone is shaking your shoulder.
Some other interesting stuff:
...
You can see the box (closed), a mirror and a metal door here.
> öppna lådan
[LLM: "öppna lådan" -> "open box"]
You open the box, revealing a pistol.
> take it
[LLM: "take it" -> "take pistol"]
Taken.
> efta7 el bab
[LLM: "efta7 el bab" -> "open door"]
You open the metal door.
Create ~/.glk_llm.conf:
# Enable LLM processing (0=disabled, 1=enabled)
enabled=1
# API endpoint (OpenAI-compatible)
api_endpoint=https://openrouter.ai/api/v1/chat/completions
# API key from your provider
api_key=sk-your-api-key-here
# Model to use
model=gpt-4
# Number of recent game output lines to include as context (0-20)
context_lines=10
# Request timeout in milliseconds
timeout_ms=5000
# Show interpretation in brackets (0=silent, 1=show)
echo_interpretation=1OpenAI:
api_endpoint=https://api.openai.com/v1/chat/completions
api_key=sk-your-openai-key
model=gpt-4OpenRouter:
api_endpoint=https://openrouter.ai/api/v1/chat/completions
api_key=sk-or-v1-your-key
model=anthropic/claude-3-5-sonnetLocal Ollama:
api_endpoint=http://localhost:11434/v1/chat/completions
api_key=dummy
model=llama2Requires OpenSSL for HTTPS API calls:
# macOS
brew install openssl
# Ubuntu/Debian
apt-get install libssl-dev
# Build
makeThen rebuild any IF interpreters (e.g., glulxe) that link against this library.
Requires Emscripten SDK:
# Install Emscripten
cd ..
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
# Build
cd ../cheapglk
make wasmThe WASM build creates libcheapglk.wasm.a which can be linked by WASM-compiled interpreters like glulxe.
For browser usage, see the glulxe repository for the complete web interface.
make clean # Clean native build
make clean-wasm # Clean WASM build onlyThere are obviously some issues that might make the experience not that great for now:
- Interpretation isn't always perfect or consistent, depending on the model you use and game you're playing
- LLM calls add latency (typically 500ms-2s)
- API costs accumulate with usage
- Depends on external service availability
- Model quality varies significantly, I've had some good results with
google/gemini-2.5-flashbut smaller models might require a lot of finetuning to be useful (but this might be the best direction for a totally offline experience).
MIT License, same as the original CheapGlk.
- CheapGlk: Andrew Plotkin
- LLM-based Interpretation Experiment: Beshr Kayali Reinholdsson