quest-engine 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quest_engine-1.0.0/PKG-INFO +619 -0
- quest_engine-1.0.0/README.md +593 -0
- quest_engine-1.0.0/engine/__init__.py +4 -0
- quest_engine-1.0.0/engine/__main__.py +2 -0
- quest_engine-1.0.0/engine/campaign.py +806 -0
- quest_engine-1.0.0/engine/challenge.py +93 -0
- quest_engine-1.0.0/engine/challenges.py +390 -0
- quest_engine-1.0.0/engine/engine.py +666 -0
- quest_engine-1.0.0/engine/main.py +676 -0
- quest_engine-1.0.0/engine/skill_pack.py +118 -0
- quest_engine-1.0.0/engine/ui.py +1187 -0
- quest_engine-1.0.0/engine/updater.py +183 -0
- quest_engine-1.0.0/engine/zone.py +51 -0
- quest_engine-1.0.0/pyproject.toml +37 -0
- quest_engine-1.0.0/quest_engine.egg-info/PKG-INFO +619 -0
- quest_engine-1.0.0/quest_engine.egg-info/SOURCES.txt +18 -0
- quest_engine-1.0.0/quest_engine.egg-info/dependency_links.txt +1 -0
- quest_engine-1.0.0/quest_engine.egg-info/requires.txt +1 -0
- quest_engine-1.0.0/quest_engine.egg-info/top_level.txt +1 -0
- quest_engine-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quest-engine
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A pluggable terminal RPG framework for building skill-based learning games.
|
|
5
|
+
Author: thorski1
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/thorski1/quest-engine
|
|
8
|
+
Project-URL: Repository, https://github.com/thorski1/quest-engine
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/thorski1/quest-engine/issues
|
|
10
|
+
Keywords: terminal,game,rpg,learning,education,cli
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
21
|
+
Classifier: Topic :: Education
|
|
22
|
+
Classifier: Topic :: Terminals
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
|
|
27
|
+
# Quest Engine
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
██████╗ ██╗ ██╗███████╗███████╗████████╗
|
|
31
|
+
██╔═══██╗██║ ██║██╔════╝██╔════╝╚══██╔══╝
|
|
32
|
+
██║ ██║██║ ██║█████╗ ███████╗ ██║
|
|
33
|
+
██║▄▄ ██║██║ ██║██╔══╝ ╚════██║ ██║
|
|
34
|
+
╚██████╔╝╚██████╔╝███████╗███████║ ██║
|
|
35
|
+
╚══▀▀═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A pluggable terminal RPG framework for building skill-based learning games.
|
|
39
|
+
|
|
40
|
+
**Powers:**
|
|
41
|
+
- [NEXUS Quest](https://github.com/thorski1/nexus-quest) — cyberpunk hacker RPG (Bash · SSH · Vim · Git · Docker · Postgres · Python · Regex · Linux · Kubernetes · AWS)
|
|
42
|
+
- [The Young Lady's Illustrated Primer](https://github.com/thorski1/primer) — children's educational adventure (Letters · Numbers · Science · Kindness · Geography · Math · History · Art · Coding)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## What Is This?
|
|
47
|
+
|
|
48
|
+
Quest Engine is the pure game loop — XP, levels, saves, difficulty, daily challenges, star ratings, achievements, bookmarks, zone previews, completion certificates. It has zero game content of its own.
|
|
49
|
+
|
|
50
|
+
You bring the content (zones and challenges). The engine handles everything else.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/thorski1/quest-engine
|
|
58
|
+
pip install -e ./quest-engine
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Requirements:** Python 3.10+, `rich >= 13.0.0`
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Project Structure
|
|
66
|
+
|
|
67
|
+
Your game lives in its own repo alongside the engine:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
quest-engine/ ← this package (engine only, no game content)
|
|
71
|
+
engine/
|
|
72
|
+
engine.py ← XP, levels, saves, achievements, speed records
|
|
73
|
+
ui.py ← all Rich TUI components
|
|
74
|
+
challenges.py ← quiz / fill-blank / live / ordered / arrange runner
|
|
75
|
+
skill_pack.py ← SkillPack dataclass + load_skill_pack()
|
|
76
|
+
campaign.py ← Campaign dataclass + CampaignSession
|
|
77
|
+
zone.py ← Zone dict helper
|
|
78
|
+
challenge.py ← Challenge dict helper
|
|
79
|
+
main.py ← run() and run_campaign() entry points
|
|
80
|
+
|
|
81
|
+
your-game/ ← your repo (content only)
|
|
82
|
+
skill-packs/
|
|
83
|
+
bash/
|
|
84
|
+
__init__.py ← exports SKILL_PACK = SkillPack(...)
|
|
85
|
+
zones.py ← all zone and challenge data
|
|
86
|
+
story.py ← narrative text (intros, outros, story)
|
|
87
|
+
campaigns/
|
|
88
|
+
my_campaign/
|
|
89
|
+
__init__.py ← exports CAMPAIGN = Campaign(...)
|
|
90
|
+
story.py ← chapter bridges, entry summaries
|
|
91
|
+
your_game/
|
|
92
|
+
main.py ← sets env vars, calls run() or run_campaign()
|
|
93
|
+
setup.cfg / pyproject.toml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quick Start: Your First Pack
|
|
99
|
+
|
|
100
|
+
### 1. Install the engine
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install -e ./quest-engine
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Set the skill-packs directory
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# your_game/main.py
|
|
110
|
+
import os
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
from engine.main import run
|
|
113
|
+
|
|
114
|
+
_HERE = Path(__file__).parent.parent
|
|
115
|
+
os.environ.setdefault("QUEST_SKILL_PACKS_DIR", str(_HERE / "skill-packs"))
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
run("my_pack")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Create your skill pack
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
your-game/
|
|
125
|
+
skill-packs/
|
|
126
|
+
my_pack/
|
|
127
|
+
__init__.py
|
|
128
|
+
zones.py
|
|
129
|
+
story.py
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Write your zones
|
|
133
|
+
|
|
134
|
+
**`skill-packs/my_pack/zones.py`**
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from engine.zone import Zone
|
|
138
|
+
from engine.challenge import Challenge
|
|
139
|
+
|
|
140
|
+
ZONE_ORDER = ["intro_zone", "advanced_zone"]
|
|
141
|
+
|
|
142
|
+
ZONES = {
|
|
143
|
+
"intro_zone": Zone(
|
|
144
|
+
id="intro_zone",
|
|
145
|
+
title="Introduction",
|
|
146
|
+
description="The basics.",
|
|
147
|
+
challenges=[
|
|
148
|
+
Challenge(
|
|
149
|
+
id="q1",
|
|
150
|
+
type="quiz",
|
|
151
|
+
prompt="What is 2 + 2?",
|
|
152
|
+
options=["3", "4", "5", "6"],
|
|
153
|
+
answer="b",
|
|
154
|
+
explanation="2 + 2 = 4. The answer is B.",
|
|
155
|
+
hints=["Think about fingers on two hands.", "It's less than 5."],
|
|
156
|
+
xp=10,
|
|
157
|
+
),
|
|
158
|
+
Challenge(
|
|
159
|
+
id="q2",
|
|
160
|
+
type="fill_blank",
|
|
161
|
+
prompt="The capital of France is ___.",
|
|
162
|
+
answer="Paris",
|
|
163
|
+
explanation="Paris has been the capital of France since the 10th century.",
|
|
164
|
+
xp=10,
|
|
165
|
+
),
|
|
166
|
+
],
|
|
167
|
+
),
|
|
168
|
+
"advanced_zone": Zone(
|
|
169
|
+
id="advanced_zone",
|
|
170
|
+
title="Advanced Topics",
|
|
171
|
+
description="Going deeper.",
|
|
172
|
+
challenges=[
|
|
173
|
+
Challenge(
|
|
174
|
+
id="q3",
|
|
175
|
+
type="flag_quiz",
|
|
176
|
+
prompt="Which flag shows hidden files with ls?",
|
|
177
|
+
answers=["-a", "--all", "ls -a"],
|
|
178
|
+
explanation="`ls -a` includes dotfiles (hidden files beginning with `.`).",
|
|
179
|
+
hints=["The flag is a single letter.", "Think 'all'."],
|
|
180
|
+
xp=15,
|
|
181
|
+
),
|
|
182
|
+
],
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 5. Write your narrative
|
|
188
|
+
|
|
189
|
+
**`skill-packs/my_pack/story.py`**
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
INTRO_STORY = """
|
|
193
|
+
Your game's opening narrative. Shown once at the very beginning.
|
|
194
|
+
Can be multiple paragraphs. Supports Rich markup like [bold]bold[/bold].
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
ZONE_INTROS = {
|
|
198
|
+
"intro_zone": "Text shown when the player first enters this zone.",
|
|
199
|
+
"advanced_zone": "Text shown when the player first enters the advanced zone.",
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ZONE_COMPLETIONS = {
|
|
203
|
+
"intro_zone": "Text shown when the player finishes all challenges in this zone.",
|
|
204
|
+
"advanced_zone": "Well done — you've mastered the advanced zone.",
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
BOSS_INTROS = {
|
|
208
|
+
"intro_zone": "Text shown before the final challenge in the zone.",
|
|
209
|
+
"advanced_zone": "This is the hardest challenge yet.",
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 6. Define your SkillPack
|
|
214
|
+
|
|
215
|
+
**`skill-packs/my_pack/__init__.py`**
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from engine.skill_pack import SkillPack
|
|
219
|
+
from .story import INTRO_STORY, ZONE_INTROS, ZONE_COMPLETIONS, BOSS_INTROS
|
|
220
|
+
from .zones import ZONES, ZONE_ORDER
|
|
221
|
+
|
|
222
|
+
SKILL_PACK = SkillPack(
|
|
223
|
+
id="my_pack",
|
|
224
|
+
title="My Pack",
|
|
225
|
+
subtitle="◈ My Subtitle ◈",
|
|
226
|
+
save_file_name="my_pack",
|
|
227
|
+
intro_story=INTRO_STORY,
|
|
228
|
+
quit_message="Come back soon.",
|
|
229
|
+
zone_order=ZONE_ORDER,
|
|
230
|
+
zones=ZONES,
|
|
231
|
+
zone_intros=ZONE_INTROS,
|
|
232
|
+
zone_completions=ZONE_COMPLETIONS,
|
|
233
|
+
boss_intros=BOSS_INTROS,
|
|
234
|
+
zone_achievement_map={
|
|
235
|
+
"intro_zone": "intro_done",
|
|
236
|
+
"advanced_zone": "advanced_done",
|
|
237
|
+
},
|
|
238
|
+
achievements={
|
|
239
|
+
"intro_done": ("First Steps", "Completed the intro zone"),
|
|
240
|
+
"advanced_done": ("Going Deep", "Mastered the advanced zone"),
|
|
241
|
+
},
|
|
242
|
+
level_titles=[
|
|
243
|
+
(1, "Novice"),
|
|
244
|
+
(5, "Learner"),
|
|
245
|
+
(10, "Scholar"),
|
|
246
|
+
(15, "Expert"),
|
|
247
|
+
(20, "Master"),
|
|
248
|
+
],
|
|
249
|
+
banner_ascii=r"""
|
|
250
|
+
__ ____ __ ____ _ _ ___ _ _
|
|
251
|
+
\ \/ /\ \ / / | _ \| | | |/ _ \| \| |
|
|
252
|
+
> < \ V / | |_) | |_| | (_) | .` |
|
|
253
|
+
/_/\_\ \_/ |____/ \___/ \___/|_|\_|
|
|
254
|
+
""",
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 7. Wire up setup.cfg
|
|
259
|
+
|
|
260
|
+
**`setup.cfg`**
|
|
261
|
+
|
|
262
|
+
```ini
|
|
263
|
+
[metadata]
|
|
264
|
+
name = my-game
|
|
265
|
+
version = 0.1.0
|
|
266
|
+
|
|
267
|
+
[options]
|
|
268
|
+
install_requires =
|
|
269
|
+
quest-engine
|
|
270
|
+
|
|
271
|
+
[options.entry_points]
|
|
272
|
+
console_scripts =
|
|
273
|
+
my-game = your_game.main:main
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 8. Run it
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
pip install -e ./my-game
|
|
280
|
+
my-game
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Challenge Types — Full Reference
|
|
286
|
+
|
|
287
|
+
### `quiz` — Multiple Choice
|
|
288
|
+
|
|
289
|
+
The player types A / B / C / D (or the option text, or 1/2/3/4).
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
Challenge(
|
|
293
|
+
id="q1",
|
|
294
|
+
type="quiz",
|
|
295
|
+
prompt="Which command lists directory contents?",
|
|
296
|
+
options=["ls", "cd", "pwd", "cat"],
|
|
297
|
+
answer="a", # correct option letter (a/b/c/d)
|
|
298
|
+
explanation="ls lists files and directories in the current directory.",
|
|
299
|
+
hints=["Think 'list'.", "Two letters."],
|
|
300
|
+
xp=10,
|
|
301
|
+
difficulty="easy", # easy / medium / hard / boss
|
|
302
|
+
)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### `fill_blank` — Free Text Answer
|
|
306
|
+
|
|
307
|
+
The player types the exact answer (case-insensitive).
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
Challenge(
|
|
311
|
+
id="q2",
|
|
312
|
+
type="fill_blank",
|
|
313
|
+
prompt="The command ___ prints the current working directory.",
|
|
314
|
+
answer="pwd",
|
|
315
|
+
explanation="pwd stands for Print Working Directory.",
|
|
316
|
+
hints=["Three letters.", "P-W-D."],
|
|
317
|
+
xp=10,
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `flag_quiz` — Multiple Valid Answers
|
|
322
|
+
|
|
323
|
+
Accepts any entry from the `answers` list. Great for command flags where `ls -a` and `-a` are both valid.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
Challenge(
|
|
327
|
+
id="q3",
|
|
328
|
+
type="flag_quiz",
|
|
329
|
+
prompt="What flag shows hidden files with ls?",
|
|
330
|
+
answers=["-a", "--all", "ls -a", "ls --all"],
|
|
331
|
+
explanation="`-a` includes dotfiles. `ls -a` also accepted.",
|
|
332
|
+
hints=["Single letter flag.", "Think 'all'."],
|
|
333
|
+
xp=15,
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `live` — Real Command in Sandbox
|
|
338
|
+
|
|
339
|
+
The player runs an actual shell command. The engine creates a temp directory, runs the command in it, and validates the result.
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
Challenge(
|
|
343
|
+
id="q4",
|
|
344
|
+
type="live",
|
|
345
|
+
prompt="Create a directory named 'output' inside the sandbox.",
|
|
346
|
+
explanation="mkdir creates a new directory: `mkdir output`",
|
|
347
|
+
hints=["Use mkdir.", "mkdir output"],
|
|
348
|
+
xp=20,
|
|
349
|
+
setup={
|
|
350
|
+
"dirs": ["src"], # pre-created directories
|
|
351
|
+
"files": {
|
|
352
|
+
"src/hello.txt": "hello world\n", # pre-created files
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
validation={"type": "dir_exists", "target": "output"},
|
|
356
|
+
)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Available validators:**
|
|
360
|
+
|
|
361
|
+
| type | fields | passes when |
|
|
362
|
+
|------|--------|-------------|
|
|
363
|
+
| `dir_exists` | `target` | directory exists in sandbox |
|
|
364
|
+
| `file_exists` | `target` | file exists in sandbox |
|
|
365
|
+
| `file_missing` | `target` | file does NOT exist |
|
|
366
|
+
| `output_contains` | `expected` | stdout or stderr contains expected string |
|
|
367
|
+
| `file_contains` | `target`, `expected` | file content contains expected string |
|
|
368
|
+
| `file_executable` | `target` | file has execute bit set |
|
|
369
|
+
| `file_perms` | `target`, `expected_mode` | file permissions match (e.g. `"755"`) |
|
|
370
|
+
| `multi` | `checks: [...]` | all nested validators pass |
|
|
371
|
+
|
|
372
|
+
### `ordered` — Sequence Ordering
|
|
373
|
+
|
|
374
|
+
The player types the correct order of steps as a space-separated number sequence.
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
Challenge(
|
|
378
|
+
id="q5",
|
|
379
|
+
type="ordered",
|
|
380
|
+
prompt="Put these git steps in order: commit, add, push, init",
|
|
381
|
+
items=["git commit -m 'msg'", "git add .", "git push", "git init"],
|
|
382
|
+
# correct order is: init(3), add(1), commit(0), push(2) — 0-based indices
|
|
383
|
+
answer=[3, 1, 0, 2],
|
|
384
|
+
explanation="init → add → commit → push is the correct git workflow.",
|
|
385
|
+
xp=20,
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### `arrange` — Matching Pairs
|
|
390
|
+
|
|
391
|
+
The player matches left items to right items using letters.
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
Challenge(
|
|
395
|
+
id="q6",
|
|
396
|
+
type="arrange",
|
|
397
|
+
prompt="Match each command to its purpose.",
|
|
398
|
+
pairs=[
|
|
399
|
+
{"left": "ls", "right": "List files"},
|
|
400
|
+
{"left": "cd", "right": "Change directory"},
|
|
401
|
+
{"left": "pwd", "right": "Print working directory"},
|
|
402
|
+
],
|
|
403
|
+
# right items may be shuffled for display; answer reflects the correct mapping
|
|
404
|
+
answer="A B C",
|
|
405
|
+
explanation="ls=List, cd=Change, pwd=Print.",
|
|
406
|
+
xp=20,
|
|
407
|
+
)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## SkillPack — Full Field Reference
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
SkillPack(
|
|
416
|
+
# Required
|
|
417
|
+
id="my_pack", # unique string identifier
|
|
418
|
+
title="My Pack", # display title
|
|
419
|
+
zone_order=["zone_1", ...], # ordered list of zone IDs
|
|
420
|
+
zones={ # dict of zone_id → zone dict
|
|
421
|
+
"zone_1": {
|
|
422
|
+
"id": "zone_1",
|
|
423
|
+
"name": "Zone Display Name", # shown in UI
|
|
424
|
+
"description": "...", # shown in zone preview
|
|
425
|
+
"challenges": [...], # list of challenge dicts
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
# Narrative text
|
|
430
|
+
intro_story="...", # shown once at start
|
|
431
|
+
zone_intros={"zone_1": "..."}, # shown on first entry to each zone
|
|
432
|
+
zone_completions={"zone_1": "..."}, # shown on zone complete
|
|
433
|
+
boss_intros={"zone_1": "..."}, # shown before last challenge
|
|
434
|
+
|
|
435
|
+
# Save / display
|
|
436
|
+
save_file_name="my_pack", # filename (no spaces) for JSON save
|
|
437
|
+
subtitle="◈ Tagline ◈", # shown under banner
|
|
438
|
+
quit_message="See you later.", # shown on quit
|
|
439
|
+
default_player_name="Agent", # default name if player skips input
|
|
440
|
+
|
|
441
|
+
# Achievements
|
|
442
|
+
zone_achievement_map={"zone_1": "achievement_id"},
|
|
443
|
+
achievements={
|
|
444
|
+
"achievement_id": ("Short Title", "Longer description"),
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
# Progression
|
|
448
|
+
level_titles=[
|
|
449
|
+
(1, "Novice"), # (min_level, title)
|
|
450
|
+
(5, "Learner"),
|
|
451
|
+
(10, "Expert"),
|
|
452
|
+
],
|
|
453
|
+
|
|
454
|
+
# Display
|
|
455
|
+
banner_ascii=r"...", # raw string ASCII art for banner
|
|
456
|
+
kids_mode=False, # True → kid-friendly praise phrases
|
|
457
|
+
|
|
458
|
+
# Optional advanced
|
|
459
|
+
recommended_age="8+", # informational only
|
|
460
|
+
)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Campaign — Chaining Packs Into a Story
|
|
466
|
+
|
|
467
|
+
A Campaign sequences SkillPacks into chapters with connecting narrative.
|
|
468
|
+
|
|
469
|
+
**`campaigns/my_campaign/__init__.py`**
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
from engine.campaign import Campaign, ChapterDef
|
|
473
|
+
|
|
474
|
+
CAMPAIGN = Campaign(
|
|
475
|
+
id="my_campaign",
|
|
476
|
+
title="The Full Journey",
|
|
477
|
+
save_file_name="my_campaign",
|
|
478
|
+
intro_story="The opening of your campaign...",
|
|
479
|
+
final_story="The ending, shown after the last chapter.",
|
|
480
|
+
quit_message="Your journey continues...",
|
|
481
|
+
entry_summary_prefix="Last time, you...",
|
|
482
|
+
campaign_achievements={
|
|
483
|
+
"campaign_complete": ("Journey's End", "Finished all chapters"),
|
|
484
|
+
},
|
|
485
|
+
chapters=[
|
|
486
|
+
ChapterDef(
|
|
487
|
+
pack_name="intro_pack", # must match SkillPack.id
|
|
488
|
+
title="Chapter 1: Beginnings",
|
|
489
|
+
entry_summary="You are just starting out.",
|
|
490
|
+
intro_bridge="Transition text shown before this chapter starts.",
|
|
491
|
+
outro_bridge="Text shown after this chapter ends, before the next begins.",
|
|
492
|
+
recommended_age="8+", # optional
|
|
493
|
+
),
|
|
494
|
+
ChapterDef(
|
|
495
|
+
pack_name="advanced_pack",
|
|
496
|
+
title="Chapter 2: The Deep End",
|
|
497
|
+
entry_summary="Having mastered the basics, you push further.",
|
|
498
|
+
intro_bridge="You descend into the advanced section...",
|
|
499
|
+
outro_bridge="You have completed the journey.",
|
|
500
|
+
),
|
|
501
|
+
],
|
|
502
|
+
)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Entry point:**
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
from engine.main import run_campaign
|
|
509
|
+
|
|
510
|
+
def main():
|
|
511
|
+
run_campaign("my_campaign") # loads CAMPAIGN from campaigns/my_campaign/__init__.py
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Zone Helper — `engine.zone.Zone`
|
|
517
|
+
|
|
518
|
+
`Zone` is a `dict` subclass. Use it instead of plain dicts for cleaner zone definitions.
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
from engine.zone import Zone
|
|
522
|
+
|
|
523
|
+
Zone(
|
|
524
|
+
id="zone_1",
|
|
525
|
+
title="Zone One", # maps to dict key "name" (what the engine reads)
|
|
526
|
+
description="...",
|
|
527
|
+
challenges=[...],
|
|
528
|
+
# Optional display fields:
|
|
529
|
+
color="cyan",
|
|
530
|
+
icon="⚡",
|
|
531
|
+
subtitle="Subheader",
|
|
532
|
+
)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Challenge Helper — `engine.challenge.Challenge`
|
|
538
|
+
|
|
539
|
+
`Challenge` is a `dict` subclass. Use it for cleaner challenge definitions.
|
|
540
|
+
|
|
541
|
+
```python
|
|
542
|
+
from engine.challenge import Challenge
|
|
543
|
+
|
|
544
|
+
Challenge(
|
|
545
|
+
id="ch_1",
|
|
546
|
+
type="quiz", # quiz / fill_blank / flag_quiz / live / ordered / arrange
|
|
547
|
+
prompt="...", # maps to dict key "question" (what the engine reads)
|
|
548
|
+
explanation="...", # maps to dict key "lesson"
|
|
549
|
+
answer="b", # for quiz/fill_blank
|
|
550
|
+
answers=["-a", "--all"], # for flag_quiz (multiple valid answers)
|
|
551
|
+
options=["A", "B", "C", "D"],
|
|
552
|
+
hints=["Hint 1", "Hint 2"],
|
|
553
|
+
xp=10,
|
|
554
|
+
difficulty="medium", # easy / medium / hard / boss
|
|
555
|
+
# For live challenges:
|
|
556
|
+
setup={"dirs": [...], "files": {...}},
|
|
557
|
+
validation={"type": "output_contains", "expected": "hello"},
|
|
558
|
+
# For ordered challenges:
|
|
559
|
+
items=["step A", "step B", "step C"],
|
|
560
|
+
# For arrange challenges:
|
|
561
|
+
pairs=[{"left": "cmd", "right": "description"}],
|
|
562
|
+
)
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## Engine Features
|
|
568
|
+
|
|
569
|
+
| Feature | Description |
|
|
570
|
+
|---------|-------------|
|
|
571
|
+
| **XP & Levels** | Configurable `level_titles` per pack; XP scales with difficulty |
|
|
572
|
+
| **Star Ratings** | 1–3 stars per zone based on hints used and challenges skipped |
|
|
573
|
+
| **Achievements** | Auto-unlocked when zone is completed; displayed in stats |
|
|
574
|
+
| **Daily Challenge** | Deterministic pick per pack per day; 2× XP; streak tracking |
|
|
575
|
+
| **Difficulty Modes** | Easy (0.75× XP, free hints) / Normal / Hard (1.5× XP) |
|
|
576
|
+
| **Speed Records** | Per-challenge personal bests; new-record flash on screen |
|
|
577
|
+
| **Bookmarks** | Toggle with `[b]`; review from main menu |
|
|
578
|
+
| **Zone Preview** | Challenge list shown before entering zone |
|
|
579
|
+
| **Help Screen** | Full keybinding reference via `[?]` |
|
|
580
|
+
| **Completion Certificate** | ASCII grade art (S/A/B/C/D) on pack complete |
|
|
581
|
+
| **Campaign Stats** | Per-chapter star ratings, XP totals, overall grade |
|
|
582
|
+
| **Kids Mode** | `kids_mode=True` → kid-friendly praise pool and gentler UX |
|
|
583
|
+
| **Save/Resume** | JSON saves in `~/.quest_engine/<save_file_name>/` |
|
|
584
|
+
| **Placement Quiz** | Campaign can start with optional assessment to pick chapter |
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## In-Game Controls
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
[h] Hint Show next hint for current challenge
|
|
592
|
+
[b] Bookmark Toggle bookmark on current challenge
|
|
593
|
+
[d] Difficulty Switch difficulty (Easy / Normal / Hard)
|
|
594
|
+
[?] Help Full control reference
|
|
595
|
+
[s] Skip Skip current challenge (costs a star)
|
|
596
|
+
[q] Menu Return to main menu
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Games Built on Quest Engine
|
|
602
|
+
|
|
603
|
+
| Game | Audience | Chapters | What You Learn |
|
|
604
|
+
|------|----------|----------|----------------|
|
|
605
|
+
| [NEXUS Quest](https://github.com/thorski1/nexus-quest) | Adults / Developers | 11 | Bash · SSH · Vim · Git · Docker · Postgres · Python · Regex · Linux · Kubernetes · AWS |
|
|
606
|
+
| [The Primer](https://github.com/thorski1/primer) | Children (5–12) | 9 | Letters · Numbers · Science · Kindness · Geography · Math · History · Art · Coding |
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Requirements
|
|
611
|
+
|
|
612
|
+
- Python 3.10+
|
|
613
|
+
- `rich >= 13.0.0`
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## License
|
|
618
|
+
|
|
619
|
+
MIT
|