A turn-based text adventure with AI-driven adversary powered by PDDL + Pyperplan planning.
- Overview
- Quick Start
- Game Rules
- Technical Architecture
- The Warden AI — Planning-based Adversary
- Project Structure
- Team
Echoes of the Labyrinth is a turn-based dungeon escape game built as a university coursework project. You awaken in a seven-room underground labyrinth and must find your way out before the Warden — an AI guardian — catches you.
The game has multiple endings depending on your choices:
- Escape empty-handed — flee without taking the Soulstone
- Ritual ending — take the Soulstone, complete a stabilisation ritual at the shrine, then escape
- Death — caught by the Warden, health depleted, or turn limit exceeded
- Pure SWI-Prolog game engine — declarative logic for world state, rules, items, and win/lose conditions
- PDDL + Pyperplan powered AI adversary that dynamically plans paths instead of following hard-coded patrol routes
- Streamlit web interface with a dungeon-themed UI (optional)
- Turn-based combat with item-based defence mechanics
- Undo system that rewinds game state and resets cached AI plans
- SWI-Prolog
- Python 3.8+
- Optional: Pyperplan for AI planning
# Install optional dependencies
pip install pyperplan streamlit requestscd prolog
swipl?- [game].
?- start.1. Start the Prolog HTTP API (backend):
cd prolog
swipl?- [server].
?- server(8000).2. Start the Streamlit frontend (in a new terminal):
python -m streamlit run app.pyOpen http://localhost:8501 in your browser.
| Command | Description |
|---|---|
look. |
Observe your surroundings |
go(Direction). |
Move north, south, east, or west |
take(Item). |
Pick up an item |
drop(Item). |
Drop an item |
inventory. |
Check what you are carrying |
perform_ritual. |
Attempt the stabilisation ritual (at the shrine) |
undo. |
Rewind one turn |
- Ritual ending: Reach
exit_gatecarrying the Soulstone after completing the ritual at the shrine - Escape ending: Reach
exit_gatewithout taking the Soulstone
- Health drops to zero (Warden encounter without defence)
- Turn limit exceeded
- Attempting the ritual without the required items (corruption ending)
[armory]
|
north
|
[entrance_hall] --east-- [guard_post] --east-- [library]
(Warden start) |
south
|
[treasure_chamber] --west-- [shrine] --south-- [exit_gate]
(Soulstone)
adventure-game/
├── prolog/
│ ├── game.pl # Main loop, entry point
│ ├── world.pl # Static world facts (rooms, doors, item locations)
│ ├── rules.pl # Player actions, win/lose logic, undo
│ ├── adversary.pl # Warden AI: goal selection, plan execution
│ ├── pyperplan_runner.pl # PDDL problem generation + Pyperplan integration
│ ├── server.pl # HTTP API for Streamlit frontend
│ └── ui.pl # REST endpoint handlers
├── pddl/
│ ├── adversary_domain.pddl # STRIPS planning domain (warden actions)
│ └── adversary_problem_base.pddl # Problem template (dynamically instantiated)
└── app.py # Streamlit web interface
The game logic lives entirely in Prolog. The Streamlit frontend communicates with Prolog via a lightweight HTTP API.
This is the core technical contribution of the PDDL implementation.
Instead of a hard-coded patrol script, the Warden uses AI planning to decide where to move. Each turn it:
- Evaluates the current goal based on game state
- Checks whether an existing cached plan is still valid
- If not, calls Pyperplan (an external STRIPS planner) to compute a path
- Executes only the next step of the plan, then re-evaluates next turn
The Warden's behaviour adapts dynamically to player actions:
| Phase | Trigger | Goal | Behaviour |
|---|---|---|---|
| Phase 1 | Player has no Soulstone | Guard treasure chamber | Patrol between key rooms |
| Phase 2 | Player took Soulstone | Hunt the player | Chase player's current position |
| Phase 3 | Player near exit | Block exit gate | Intercept the exit corridor |
Prolog (adversary.pl)
│
├─ warden_goal/1 ← Select target based on game state
│
├─ build_warden_problem_file/3
│ └─ Reads adversary_problem_base.pddl
│ └─ Injects current (at-warden FROM) and (at-warden TO)
│ └─ Writes adversary_problem_tmp.pddl
│
├─ run_warden_planner/2
│ └─ Calls: pyperplan domain.pddl problem_tmp.pddl
│ └─ Parses .soln output → [move(a,b), move(b,c), ...]
│
└─ execute_first_action/2
└─ Validates next step against current world state
└─ Applies it (retract + assert enemy_at)
└─ Caches remaining plan for next turn
If Pyperplan fails (e.g., planner not installed), the Warden falls back to a deterministic patrol route: guard_post → library → shrine → guard_post. This means the game is always playable.
If a stored plan becomes invalid mid-execution (e.g., player used undo.), the plan is discarded and re-planning is triggered immediately.
When Pyperplan runs, it produces a plan like:
move(guard_post, library)
move(library, shrine)
move(shrine, treasure_chamber)
The Warden executes one step per turn, so the player experiences emergent, adaptive behaviour rather than a teleporting enemy.
| Name | Role | Contribution |
|---|---|---|
| LIUGUOHAO | PDDL + Pyperplan AI Planning | Adversary goal system, PDDL domain, planner integration |
| liuheng | Streamlit Web Interface | Web UI, Prolog HTTP API |
| wuzhengye | Presentation | Report and presentation |
This project is licensed under the MIT License. See LICENSE for details.