questmark 0.0.37 → 0.1.0

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.
@@ -0,0 +1,219 @@
1
+ # Write and play your first conversation
2
+
3
+ In this tutorial you'll write a small piece of interactive fiction from
4
+ scratch, and play it. Along the way you'll meet the three ideas everything in
5
+ Questmark is built on: **states**, **options**, and **context**.
6
+
7
+ You don't need any programming experience, but you do need to be comfortable
8
+ with Markdown — which is exactly the point. Everything you write here is
9
+ ordinary Markdown plus a few backticked additions.
10
+
11
+ This tutorial takes about ten minutes. If you follow it to the end, you'll
12
+ have a working, playable conversation.
13
+
14
+ ---
15
+
16
+ ## 1. Get set up
17
+
18
+ Clone the repository and install its dependencies:
19
+
20
+ ```bash
21
+ git clone https://github.com/jorisvddonk/questmark.git
22
+ cd questmark
23
+ npm install
24
+ ```
25
+
26
+ The Questmark command line is available from the project directory as
27
+ `npm start`, and the published CLI is also available via
28
+ `npx questmark` if you'd rather use that.
29
+
30
+ ## 2. Write your first document
31
+
32
+ Create a file called `tavern.md` with the following contents:
33
+
34
+ ```markdown
35
+ # QUESTMARK-OPTIONS-HEADER
36
+
37
+ {
38
+ "questmark-spec": "1.0",
39
+ "initial-state": "bar"
40
+ }
41
+
42
+ # bar
43
+
44
+ The barkeep eyes you from behind the counter.
45
+ * [Order a drink](#drink)
46
+ * [Leave](#leave)
47
+
48
+ # drink
49
+
50
+ A frothing mug of ale slides across the counter.
51
+ * [Back to the bar](#bar)
52
+
53
+ # leave
54
+
55
+ "Safe travels, stranger."
56
+ `exit`
57
+ ```
58
+
59
+ Let's take it apart.
60
+
61
+ Every Questmark document starts with a **`QUESTMARK-OPTIONS-HEADER`** section.
62
+ Its indented code block is JSON that tells the interpreter where to start
63
+ (`initial-state`) and, later, what variables to set up (`initial-context`).
64
+
65
+ Each `# heading` in the document defines a **state**. A state is one "room" of
66
+ your fiction — the thing the player sees at one moment in the story. The text
67
+ under a heading is shown to the player when they enter that state.
68
+
69
+ Each `*` list item is an **option**: one thing the player can choose to do.
70
+ The Markdown link in `[Order a drink](#drink)` means "when the player picks
71
+ this, jump to the `drink` state."
72
+
73
+ The last line, `` `exit` ``, is a piece of code that ends the conversation.
74
+
75
+ ## 3. Play it
76
+
77
+ Run the document:
78
+
79
+ ```bash
80
+ npm start -- --input tavern.md
81
+ ```
82
+
83
+ You should see the barkeep's text, and then a menu with two choices. Pick
84
+ "Order a drink", then "Back to the bar", then "Leave". Each time you make a
85
+ choice, the story moves to the state that choice pointed at.
86
+
87
+ Play it once more and make a different choice. The story branches — that's the
88
+ whole trick behind conversation trees.
89
+
90
+ ## 4. Remember something the player did
91
+
92
+ Right now the barkeep can't tell whether you've already had a drink. Let's add
93
+ some memory. Questmark tracks what the player has done in a bag of variables
94
+ called the **context**.
95
+
96
+ Update the header to initialize a variable, and set it when the player orders
97
+ a drink:
98
+
99
+ ```markdown
100
+ # QUESTMARK-OPTIONS-HEADER
101
+
102
+ {
103
+ "questmark-spec": "1.0",
104
+ "initial-state": "bar",
105
+ "initial-context": { "had_drink": 0 }
106
+ }
107
+
108
+ # bar
109
+
110
+ The barkeep eyes you from behind the counter.
111
+ * [Order a drink](#drink)
112
+ * [Leave](#leave)
113
+
114
+ # drink
115
+
116
+ A frothing mug of ale slides across the counter.
117
+ `1 "had_drink" setContext`
118
+ * [Back to the bar](#bar)
119
+
120
+ # leave
121
+
122
+ "Safe travels, stranger."
123
+ `exit`
124
+ ```
125
+
126
+ The new line `` `1 "had_drink" setContext` `` means "push the number `1`, then
127
+ store it into the context under the name `had_drink`." Code inside backticks is
128
+ Tzo bytecode, written in a reverse-Polish style: the values come first, then
129
+ the operation.
130
+
131
+ ## 5. Make an option conditional
132
+
133
+ Now let's use that memory. A new option should appear only once the player has
134
+ had a drink. Backticks *before* an option's text are a **precondition**: the
135
+ option is only offered when the code leaves a positive number on the stack.
136
+
137
+ Add the option to the `bar` state:
138
+
139
+ ```markdown
140
+ # bar
141
+
142
+ The barkeep eyes you from behind the counter.
143
+ * [Order a drink](#drink)
144
+ * `"had_drink" getContext` [Ask about the storm](#storm)
145
+ * [Leave](#leave)
146
+ ```
147
+
148
+ The precondition `` `"had_drink" getContext` `` means "look up `had_drink` and
149
+ push its value." On your first visit the value is `0`, so the option is hidden.
150
+ After you've ordered a drink the value is `1`, so the option appears.
151
+
152
+ Now add the `storm` state the option links to:
153
+
154
+ ```markdown
155
+ # storm
156
+
157
+ "Storm's coming. Mark my words."
158
+ `exit`
159
+ ```
160
+
161
+ ## 6. Try the finished conversation
162
+
163
+ Here is the complete document:
164
+
165
+ ```markdown
166
+ # QUESTMARK-OPTIONS-HEADER
167
+
168
+ {
169
+ "questmark-spec": "1.0",
170
+ "initial-state": "bar",
171
+ "initial-context": { "had_drink": 0 }
172
+ }
173
+
174
+ # bar
175
+
176
+ The barkeep eyes you from behind the counter.
177
+ * [Order a drink](#drink)
178
+ * `"had_drink" getContext` [Ask about the storm](#storm)
179
+ * [Leave](#leave)
180
+
181
+ # drink
182
+
183
+ A frothing mug of ale slides across the counter.
184
+ `1 "had_drink" setContext`
185
+ * [Back to the bar](#bar)
186
+
187
+ # storm
188
+
189
+ "Storm's coming. Mark my words."
190
+ `exit`
191
+
192
+ # leave
193
+
194
+ "Safe travels, stranger."
195
+ `exit`
196
+ ```
197
+
198
+ Play it and notice the difference:
199
+
200
+ 1. On your first visit to the bar, "Ask about the storm" is **not** in the
201
+ menu.
202
+ 2. Order a drink, return to the bar, and the option is **now** there.
203
+
204
+ ```bash
205
+ npm start -- --input tavern.md
206
+ ```
207
+
208
+ ## 7. Where to go next
209
+
210
+ You've used states, options, links, context, and a precondition. Those are the
211
+ core of the language.
212
+
213
+ - Read the [how-to guide on playing documents](../how-to/play-a-document.md)
214
+ for more ways to run a document.
215
+ - Read the [language reference](../reference/language.md) for the complete set
216
+ of language features, including the `@once` directive and effects.
217
+ - Browse the [examples](https://github.com/jorisvddonk/questmark/tree/master/examples)
218
+ in the repository — `space_alien.md` is a longer conversation tree that puts
219
+ everything together.
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "questmark",
3
- "version": "0.0.37",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
+ "type": "module",
5
6
  "main": "dist/node/index.js",
6
7
  "types": "dist/node/index.d.js",
7
8
  "exports": {
8
- ".": {
9
- "import": "./dist/node/index.js",
10
- "require": "./dist/browser/index.js"
11
- }
9
+ ".": "./dist/node/index.js"
12
10
  },
13
11
  "scripts": {
14
12
  "prepublishOnly": "npm run build",
15
13
  "build": "npx cross-env rm -rf dist/ && tsc -p tsconfig.json && npm run wp_build",
16
- "wp_build": "webpack build --config webpack.config.js",
17
- "test": "echo \"Error: no test specified\" && exit 1",
18
- "start": "ts-node ./src/cli"
14
+ "wp_build": "webpack build --config webpack.config.cjs",
15
+ "test": "npm run build && node --test \"test/**/*.test.cjs\"",
16
+ "start": "tsx ./src/cli.ts",
17
+ "docs:dev": "vitepress dev docs",
18
+ "docs:build": "vitepress build docs",
19
+ "docs:preview": "vitepress preview docs"
19
20
  },
20
21
  "bin": {
21
22
  "questmark": "./bin/questmark.mjs"
@@ -24,36 +25,32 @@
24
25
  "license": "MIT",
25
26
  "dependencies": {
26
27
  "array-flat-polyfill": "^1.0.1",
27
- "commander": "^6.1.0",
28
- "inquirer": "^6.2.0",
28
+ "commander": "^15.0.0",
29
+ "inquirer": "^14.2.2",
29
30
  "mdast": "^3.0.0",
30
- "mdast-util-from-markdown": "^0.1.1",
31
- "node-fetch": "^2.6.5",
31
+ "mdast-util-from-markdown": "^2.0.3",
32
32
  "tzo": "^1.0.19",
33
- "unist-builder": "^2.0.3",
34
- "unist-util-filter": "^2.0.2",
35
- "unist-util-find-after": "^3.0.0",
36
- "unist-util-find-all-after": "^3.0.1",
37
- "unist-util-find-all-before": "^3.0.0",
38
- "unist-util-find-all-between": "^2.1.0",
39
- "unist-util-flat-filter": "^1.0.0",
40
- "unist-util-map": "^2.0.1",
41
- "unist-util-remove-position": "^3.0.0",
42
- "unist-util-visit": "^2.0.3",
43
- "unist-util-visit-parents": "^3.1.0"
33
+ "unist-builder": "^4.0.0",
34
+ "unist-util-filter": "^5.0.1",
35
+ "unist-util-find-after": "^5.0.0",
36
+ "unist-util-find-all-after": "^5.0.0",
37
+ "unist-util-find-all-before": "^5.0.0",
38
+ "unist-util-flat-filter": "^2.0.0",
39
+ "unist-util-map": "^4.0.0",
40
+ "unist-util-remove-position": "^5.0.0",
41
+ "unist-util-visit": "^5.1.0",
42
+ "unist-util-visit-parents": "^6.0.2"
44
43
  },
45
44
  "devDependencies": {
46
- "@types/colors": "^1.2.1",
47
- "@types/commander": "^2.12.2",
48
- "@types/inquirer": "^7.3.1",
49
- "@types/markdown-it": "^10.0.2",
50
- "@types/mdast": "^3.0.3",
51
- "@types/node": "^14.6.0",
52
- "node-polyfill-webpack-plugin": "^1.1.4",
45
+ "@types/mdast": "^4.0.4",
46
+ "@types/node": "^26.6.3",
47
+ "@types/unist": "^3.0.3",
48
+ "node-polyfill-webpack-plugin": "^4.1.0",
53
49
  "ts-loader": "^9.2.6",
54
- "ts-node": "^9.1.1",
55
- "typescript": "^4.0.2",
50
+ "tsx": "^4.23.15",
51
+ "typescript": "^5.9.3",
52
+ "vitepress": "^1.6.4",
56
53
  "webpack": "^5.54.0",
57
- "webpack-cli": "^4.8.0"
54
+ "webpack-cli": "^7.2.3"
58
55
  }
59
56
  }
package/readme.md CHANGED
@@ -1,102 +1,77 @@
1
- # introduction
1
+ # Questmark
2
2
 
3
- Questmark is a [hypertext fiction](https://en.wikipedia.org/wiki/Hypertext_fiction) and [conversation tree](https://en.wikipedia.org/wiki/Dialogue_tree) language, compiler, interpreter, and TypeScript library. The Questmark language is designed to be easy to understand, and designed to support BOTH conversation trees (like in games such as Star Control 2) and quests / hypertext fiction / text adventures / visual novels (like in games such as Space Rangers 2). It's based heavily on Markdown, and compiles to [Tzo](https://github.com/jorisvddonk/tzo) bytecode.
3
+ Questmark is a [hypertext fiction](https://en.wikipedia.org/wiki/Hypertext_fiction)
4
+ and [conversation tree](https://en.wikipedia.org/wiki/Dialogue_tree) language,
5
+ compiler, interpreter, and TypeScript library. It supports both conversation
6
+ trees (like in games such as Star Control 2) and quests, hypertext fiction,
7
+ text adventures, and visual novels (like in games such as Space Rangers 2).
4
8
 
5
- # The problem that Questmark tries to solve
9
+ Questmark is based heavily on Markdown, and compiles to
10
+ [Tzo](https://github.com/jorisvddonk/tzo) bytecode. If you can write Markdown,
11
+ you can write Questmark: every properly-structured Markdown document with
12
+ headers and in-document links is already a Questmark document. Behaviour and
13
+ side-effects are added with small, backticked code snippets where needed.
6
14
 
7
- Many modern games feature complex dialog trees or dialog graphs, which allow a player to interact with NPCs in the game world through dialogue that is predefined by the game's creators. Modern games implementing such systems are Skyrim, The Witcher, Deus Ex: Mankind Divided, Mass Effect: Andromeda... The list really [goes on and on](https://www.giantbomb.com/dialogue-trees/3015-77/)...
15
+ ## Documentation
8
16
 
9
- Often, if a player chooses a certain option within such a dialog system, this has some kind of side-effect in the game world. Dialogue systems therefore need to be able to model conversation graphs as well as be able to trigger side-effects when necessary.
17
+ The docs follow the [Diátaxis](https://diataxis.fr/) framework and are also
18
+ rendered as a [hosted website](https://jorisvddonk.github.io/questmark/):
10
19
 
11
- This means that, the people that are responsible for writing dialog prose with side-effect for game should know how to *write prose* as well as how to *program side-effects*!
20
+ - **[Tutorials](docs/tutorials/)** — start here if you're new. Write and play
21
+ your first conversation in about ten minutes.
22
+ - **[How-to guides](docs/how-to/)** — play a document, compile it to bytecode,
23
+ and embed Questmark in your own application.
24
+ - **[Reference](docs/reference/)** — the language, the CLI, and the API,
25
+ described precisely.
26
+ - **[Explanation](docs/explanation/)** — why Questmark exists and how it works
27
+ under the hood.
12
28
 
13
- Unfortunately, the authors that write dialogue prose may not know how to program, and the programmers that write dialogue graph and side-effect code may not know how to write dialogue prose!
14
-
15
- This means that, if you come up with [an advanced scripting language with associated editor](https://github.com/jorisvddonk/p6014-dialogue-scripting-tool), you're likely to only attract programmers. Your dialogue prose authors will either be programmers (which may be terrible at writing dialogue prose!), or your dialogue prose authors will just continue to work inside Word documents...
16
-
17
- Maybe your dialogue prose authors are writing using a different, proprietary system instead. That might actually work out OK, or it might not, for instance if these proprietary systems use binary file formats that are difficult/impossible to merge.
18
-
19
- # The solution!
20
-
21
- Both authors and programmers, however, are likely to know Markdown or any of its modern dialects. This knowledge shared between authors and programmers is what Questmark makes use of.
22
-
23
- Questmark is a natural dialect of Markdown, allowing for the creation of dialogue graphs with in-state and state-transition side-effects.
24
-
25
- Every Markdown document with properly structured headers and in-document links is a Questmark document. Questmark-specific features and code can then be added to add further interactivity and side-effects to a dialogue system.
26
-
27
- Questmark aims to make the following workflow viable:
28
-
29
- 1. Authors write dialogue trees in Markdown, which can be demoed interactively, either by interpreting Markdown and compiling it as HTML, by interpreting Questmark in a simple Questmark runner, or by interpreting Questmark inside of a modern game engine. Note that the Markdown->HTML approach only works for simple documents without scripting.
30
- 2. Authors, project managers, and other reviewers can add comments to provide feedback or clarification on dialogue without affecting interpreted Questmark output. These comments will *not* be visible anywhere when the dialogue tree definition is interpreted as Questmark.
31
- 3. Gameplay programmers can add interactivity where needed.
32
- 4. Authors can modify dialogue prose easily without affecting dialogue interactivity, if needed.
33
- 5. Game can be shipped!
34
-
35
- Ideally, your dialogue tree Questmark files would be saved under revision control (e.g. git).
36
-
37
- # Current status of Questmark
38
-
39
- The spec is currently not written down properly, the supported featureset has not been decided on, and there is no formal test library (though [Tzo](https://github.com/jorisvddonk/tzo) *does* have a [testsuite](https://github.com/jorisvddonk/tzo/tree/master/src/tests)). This Github repository, however, contains a proof of concept implemented in TypeScript!
40
-
41
- You should look in the [examples](https://github.com/jorisvddonk/questmark/blob/master/examples) folder for examples. Particularly, [self-describing.md](https://github.com/jorisvddonk/questmark/blob/master/examples/self-describing.md) is a text adventure written in Questmark that explains how Questmark works to you!
42
-
43
- There are a few things that are still a bit "up in the air" and need to be thought about or implemented properly:
44
-
45
- * How should inline HTML be treated?
46
- * How can the Questmark compiler be modified to add custom directives and macros?
47
- * How can a Questmark document be translated to another language, whilst keeping the scripting logic intact and easy to modify?
48
-
49
- # Questmark cli usage
50
-
51
- Questmark contains a simple CLI to compile and optionally interpret Questmark documents that don't contain any foreign opcodes that aren't implemented by the standard reference inplementation.
52
-
53
- ## Compiling a Questmark document to Tzo bytecode
29
+ ## Quick start
54
30
 
55
31
  ```bash
56
- npx questmark --no-run --input path_to_questmark_document.md --output path_to_output.json
57
- ```
58
-
59
- ## Interpreting a Questmark document
60
-
61
- ```bash
62
- npx questmark --input path_to_questmark_document.md
63
- ```
64
-
65
- # Questmark library usage
32
+ npm install
66
33
 
67
- TODO: document this.
34
+ # Play an example conversation
35
+ npm start -- --input examples/space_alien.md
68
36
 
69
- See [cli.ts](https://github.com/jorisvddonk/questmark/blob/master/src/cli.ts) in the meantime.
37
+ # Compile a document to Tzo bytecode without running it
38
+ npm start -- --input examples/space_alien.md --output vmstate.json --no-run
70
39
 
71
- # Questmark cli usage
72
-
73
- If you have Node.js installed, you can use the Questmark cli via [`npx`](https://docs.npmjs.com/cli/v7/commands/npx).
40
+ # Run the test suite
41
+ npm test
42
+ ```
74
43
 
75
- To get cli usage instructions:
44
+ You can also use the published CLI with `npx questmark` (see the
45
+ [how-to guide](docs/how-to/play-a-document.md)).
76
46
 
77
- ```bash
78
- npx questmark --help
79
- ```
47
+ ## Examples
80
48
 
81
- To play through a Questmark document:
49
+ The [examples](examples) folder contains playable documents. Notably,
50
+ [`self-describing.md`](examples/self-describing.md) is a text adventure written
51
+ in Questmark that explains how the language works, and
52
+ [`space_alien.md`](examples/space_alien.md) demonstrates preconditions,
53
+ `@once` options, and context-driven branching.
82
54
 
83
- ```bash
84
- npx questmark --input <path_to_file.md>
85
- ```
55
+ ## Current status
86
56
 
87
- You can also play a Questmark document that's hosted online:
57
+ Questmark is a working proof of concept implemented in TypeScript, with a
58
+ compiler, an interpreter, a CLI, and a [test suite](test). The spec is still
59
+ informal and the supported feature set is still evolving.
88
60
 
89
- ```bash
90
- npx questmark --input https://ghcdn.rawgit.org/jorisvddonk/questmark/master/examples/self-describing.md
91
- ```
61
+ Open questions being worked on:
92
62
 
93
- To convert a Questmark .md file to Tzo bytecode (VMState), without interpreting the document:
63
+ - How should inline HTML be treated?
64
+ - How can the compiler be modified to add custom directives and macros?
65
+ - How can a document be translated to another language, whilst keeping the
66
+ scripting logic intact and easy to modify?
94
67
 
95
- ```bash
96
- npx questmark --input <path_to_file.md> --output <path_to_output_VMState.json> --no-run
97
- ```
68
+ ## Other tools / libraries
98
69
 
99
- # Other tools / libraries
70
+ - [Tzo](https://github.com/jorisvddonk/tzo) — the bytecode and VM Questmark
71
+ compiles to.
72
+ - [questmark-webrenderer](https://github.com/jorisvddonk/questmark-webrenderer) —
73
+ write and play Questmark documents in your web browser.
100
74
 
101
- Want to write a Questmark document and have the ability to play it easily from within your webbrowser? Consider trying out the [questmark-webrenderer](https://github.com/jorisvddonk/questmark-webrenderer).
75
+ ## License
102
76
 
77
+ MIT
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "moduleResolution": "node",
4
- "target": "es6",
5
- "module": "commonjs",
3
+ "moduleResolution": "nodenext",
4
+ "target": "es2022",
5
+ "module": "nodenext",
6
6
  "lib": [
7
7
  "esnext"
8
8
  ],
@@ -19,6 +19,10 @@
19
19
  "rootDir": "./src",
20
20
  "typeRoots": [
21
21
  "node_modules/@types"
22
+ ],
23
+ "types": [
24
+ "node",
25
+ "mdast"
22
26
  ]
23
27
  },
24
28
  "include": ["src/**/*.ts"],
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
3
  "compilerOptions": {
4
- "module": "commonjs",
5
4
  "declarationDir": "./_temp/",
6
5
  "outDir": "./_temp/",
7
6
  "rootDir": ".",
8
- "target": "es6",
9
7
  "allowUmdGlobalAccess": true,
10
8
  },
11
9
  }
@@ -1,8 +1,8 @@
1
1
  const path = require('path');
2
- const TerserPlugin = require('terser-webpack-plugin');
3
2
  const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
4
3
 
5
4
  module.exports = {
5
+ mode: "production",
6
6
  entry: './src/index.ts',
7
7
  devtool: "inline-source-map",
8
8
  output: {
@@ -29,20 +29,14 @@ module.exports = {
29
29
  },
30
30
  resolve: {
31
31
  extensions: [".ts", ".tsx", ".js"],
32
+ extensionAlias: {
33
+ ".js": [".ts", ".js"]
34
+ },
32
35
  fallback: { fs: false }
33
36
  },
34
37
  optimization: {
35
38
  minimize: false,
36
39
  mangleExports: false,
37
- minimizer: [
38
- new TerserPlugin({
39
- parallel: true,
40
- terserOptions: {
41
- keep_classnames: true,
42
- keep_fnames: true
43
- },
44
- }),
45
- ],
46
40
  },
47
41
  plugins: [
48
42
  new NodePolyfillPlugin()