Author: Luca Bocaletto (bocaletto-luca)
Repository: https://github.com/bocaletto-luca/TextQuestEngine
License: GNU GPL v3.0
A Python engine for creating modular, extensible, and standalone text adventures. This repository includes:
- Core engine: parser, dispatcher, loader, plugin system
- A complete demo: “The Lost Cave”
- CLI tools to scaffold, run, test, lint, build and package your adventure
- Features
- Prerequisites
- Installation
- Usage
- Project Structure
- Demo “The Lost Cave”
- Packaging
- Contributing
- License
- Multilingual parser (Italian/English) with synonyms
- Event-driven dispatcher & plugin hooks (
pre_action
,command_<cmd>
,post_action
) - World loader from YAML + JSON (
world.yaml
+items.json
) - Built-in support for rooms, items, NPCs, missions, game time
- Intuitive CLI:
init
,run
,test
,lint
,build
,package
- Standalone packaging via PyInstaller
- Python 3.8+
- pip
- (optional) PyInstaller for standalone packaging
git clone https://github.com/bocaletto-luca/TextQuestEngine.git
cd TextQuestEngine
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
pip install pyinstaller # only if you intend to use `tqe package`
After installation, the tqe
command (alias for python3 -m engine.utils.cli
) provides:
tqe init <project_name> # Scaffold a new adventure project
tqe run --world <path/world.yaml> # Launch the interactive game
tqe test # Run unit & integration tests (pytest)
tqe lint # Check code style & quality
tqe build # Build sdist & wheel packages
tqe package # Create a standalone executable (PyInstaller)
The demo “The Lost Cave” is in demo_adventure/
. To try it:
cd demo_adventure
tqe run
Sample commands:
> look
> take torch
> move inside
> use torch
> look
> take gem
> missions
TextQuestEngine/
├── engine/ # Core engine code
│ ├── core/
│ │ ├── dispatcher.py
│ │ ├── game.py
│ │ ├── parser.py
│ │ └── cli_commands.py
│ ├── data/
│ │ ├── loader.py
│ │ └── models.py
│ ├── plugins/
│ │ └── base.py
│ └── utils/
│ └── cli.py
├── demo_adventure/ # “The Lost Cave” demo
│ ├── config/
│ │ ├── world.yaml
│ │ └── items.json
│ ├── plugins/
│ │ └── light_plugin.py
│ └── assets/
│ └── map.txt
├── src/ # PyInstaller entrypoint
│ └── main.py
├── setup.py
└── README.md
# demo_adventure/config/world.yaml
config:
start_room: house
start_time: 0
intro_text: >
You are an explorer seeking epic adventures… and epically bad jokes.
If you can keep a grin while risking life and limb, you’re in the right place!
initial_missions:
- mission_gem
- mission_beer
rooms:
house:
name: Abandoned House
desc: >
A dusty little house filled with creaky furniture and mysterious
oddities. A torch glints on the table like a miniature sun.
connections:
outside: forest
items:
- torch
forest:
name: Messy Forest
desc: >
Trees look like they’ve given each other bad haircuts. A winding path
leads deeper, where a mysterious cave yawns in darkness.
connections:
back: house
inside: cave
items: []
cave:
name: Dark (and Witty) Cave
desc: >
It’s so dark even your brightest ideas struggle to stand out.
Maybe you need something to light the way… or at least a comedian.
connections:
exit: forest
items: []
tavern:
name: Tavern of the Dozy Gnome
desc: >
A humble tavern where every patron snores into their pint.
The bar is lined with bottles—most as empty as your wallet.
connections:
outside: house
items:
- beer
npcs:
lazy_gnome:
name: Lazy Gnome
location: tavern
dialogues:
- text: "Oh, traveler! If you have a beer, we can call it ‘friendship’."
options:
- reply: "Here’s a beer"
text: "Ah, finally! *sneeze*"
next: end
- reply: "I have a gem"
text: "A gem? I prefer frothy mugs, but sure…"
next: end
- text: "end"
options: []
missions:
mission_gem:
title: "The Shiny Gem"
description: >
Legend speaks of a precious gem hidden in the dark depths of the cave.
Find it and bring it back… and maybe entertain a gnome!
requirements:
have_item: ["gem"]
steps:
- "Enter the cave"
- "Light the torch"
- "Take the gem"
- "Return to the Lazy Gnome"
rewards:
xp: 50
message: "You lit up the cave… and one gnome’s spirits!"
mission_beer:
title: "Bring the Beer"
description: >
The Lazy Gnome is parched. Fetch a cold beer and present it with flair.
requirements:
have_item: ["beer"]
steps:
- "Find a beer"
- "Go to the tavern"
- "Give the beer to the Lazy Gnome"
rewards:
xp: 20
message: "The gnome now sleeps soundly. Mission accomplished!"
{
"torch": {
"names": ["torch", "lantern"],
"description": "A battery-powered torch that shines brightly—perfect for caves.",
"weight": 1.0,
"usable_on": ["cave"]
},
"gem": {
"names": ["gem", "precious stone"],
"description": "A sparkling gem embedded in the cave wall—brighter than your wits.",
"weight": 0.2,
"usable_on": []
},
"beer": {
"names": ["beer", "pint", "bottle"],
"description": "A frothy pint of beer: liquid consolation for any gnome.",
"weight": 0.5,
"usable_on": ["lazy_gnome"]
}
}
# demo_adventure/plugins/light_plugin.py
from engine.plugins.base import PluginBase
from types import SimpleNamespace
class LightPlugin(PluginBase):
"""
Handles darkness in the cave and torch usage:
- Blocks vision until the torch is lit
- Spawns the gem once lit
"""
def on_pre_action(self, action: SimpleNamespace, state, world):
if action.command == "look" and state["current_room"] == "cave":
if not state.get("torch_lit", False):
return "It’s too dark to see. Maybe a torch would help!"
return None
def on_command_use(self, action: SimpleNamespace, state, world):
if action.target == "torch":
state["torch_lit"] = True
cave = world.rooms["cave"]
if "gem" not in cave.items:
cave.items.append("gem")
return "You light the torch! Warm glow reveals a shiny gem."
return None
To build a standalone executable:
pip install pyinstaller
tqe package
The binary (plus config/
and assets/
) will appear in dist/
. Rename it to tqe
and distribute!
- Fork the repository
- Create a feature branch (
feature/your-feature
) - Make changes, run
tqe test
&tqe lint
- Submit a Pull Request
This project is licensed under GNU GPL v3.0.
See LICENSE for details.