@jutge.org/toolkit 4.4.17 → 4.4.20

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.
Files changed (28) hide show
  1. package/assets/problems/quizzes/demo-quiz.pbm/README.md +1 -0
  2. package/assets/problems/quizzes/demo-quiz.pbm/ca/award.png +0 -0
  3. package/assets/problems/quizzes/demo-quiz.pbm/ca/quiz.yml +1 -1
  4. package/assets/problems/quizzes/demo-quiz.pbm/ca/single-choice.yml +9 -1
  5. package/assets/problems/quizzes/demo-quiz.pbm/ca/some-drawing.svg +150 -0
  6. package/assets/problems/quizzes/demo-quiz.pbm/ca/some-image.png +0 -0
  7. package/assets/problems/quizzes/demo-quiz.pbm/en/quiz.yml +1 -1
  8. package/assets/problems/quizzes/demo-quiz.pbm/en/single-choice.yml +9 -1
  9. package/assets/problems/quizzes/demo-quiz.pbm/en/some-drawing.svg +150 -0
  10. package/assets/problems/quizzes/demo-quiz.pbm/en/some-image.png +0 -0
  11. package/assets/problems/quizzes/heroes-of-computation.pbm/README.md +11 -0
  12. package/assets/problems/quizzes/heroes-of-computation.pbm/en/handler.yml +1 -0
  13. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q01.yml +14 -0
  14. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q02.yml +14 -0
  15. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q03.yml +14 -0
  16. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q04.yml +15 -0
  17. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q05.yml +32 -0
  18. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q06.yml +12 -0
  19. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q07.yml +23 -0
  20. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q08.yml +14 -0
  21. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q09.yml +14 -0
  22. package/assets/problems/quizzes/heroes-of-computation.pbm/en/q10.yml +23 -0
  23. package/assets/problems/quizzes/heroes-of-computation.pbm/en/quiz.yml +48 -0
  24. package/assets/problems/quizzes/the-answer-is-42.pbm/en/quiz.yml +2 -0
  25. package/dist/index.js +469 -465
  26. package/docs/quiz-anatomy.md +93 -61
  27. package/package.json +2 -1
  28. package/toolkit/quiz.ts +30 -3
@@ -4,17 +4,13 @@ This document describes the anatomy of a **quiz problem** in [Jutge.org](https:/
4
4
 
5
5
  ## Terminology
6
6
 
7
- A **quiz** is a problem whose handler is set to `quiz`. It is made of a **quiz root** and a list of **questions**. The quiz root is defined in `quiz.yml` and holds the quiz title, statement, whether questions are shuffled, and the list of questions with their scores. Each **question** is defined in a separate YAML file (e.g. `single-choice.yml`) and has a **type**: SingleChoice, MultipleChoice, FillIn, Ordering, Matching, or OpenQuestion.
7
+ A **quiz** is a problem whose handler is set to `quiz`. It is made of a **quiz root** and a list of **questions**. The quiz root is defined in `quiz.yml` and holds the quiz title, author, statement, whether questions are shuffled, and the list of questions with their scores. Each **question** is defined in a separate YAML file (e.g. `single-choice.yml`) and has a `type`: SingleChoice, MultipleChoice, FillIn, Ordering, Matching, or OpenQuestion.
8
8
 
9
9
  Quiz content can be **localized**: the same quiz can have different `quiz.yml` and question files per language (e.g. under `en/` and `ca/`). The toolkit runs or lints the quiz for a given directory, so you typically run it from a language-specific subdirectory. Each language should live inside its own folder.
10
10
 
11
- **Variable substitution** allows question text and options to depend on values generated at run time. If a question file is named `example.yml`, the toolkit looks for `example.py` in the same directory. When present, it runs the Python script with a random **seed** and collects the script’s global variables. Those variables can be referenced in the question YAML with `$name` or `${name}`. This makes it possible to have different numbers, strings, or options for each run while keeping the same correct answer logic (e.g. “What is $a + $b?” with `a` and `b` random).
11
+ **Variable substitution** allows question text and options to depend on values generated at run time. If a question file is named `example.yml`, the toolkit looks for `example.py` in the same directory. When present, it runs the Python script with a random `seed` and collects the script’s global variables. Those variables can be referenced in the question YAML with `$name` or `${name}`. This makes it possible to have different numbers, strings, or options for each run while keeping the same correct answer logic (e.g. “What is $a + $b?” with `a` and `b` random).
12
12
 
13
- **Scoring**: Each question has a **score** between 0–100, and the total of all question scores listed in `quiz.yml` must add up to 100. Users earn points for each question based on question type and that question’s **partial_answer** setting. The **partial_answer** option is set per question in the question YAML (not in `quiz.yml`):
14
-
15
- - If `partial_answer` is set to `false` (default), users get full points for that question only when their answer is completely correct; any mistake gives zero points for that question.
16
-
17
- - If `partial_answer` is set to `true`, users can receive partial points for that question when the answer is partially correct (e.g. proportional to how many parts are right), and the response may still be marked as "correct" if at least one part is right.
13
+ **Scoring**: Each question has a `score` between 0–100, and the total of all question scores listed in `quiz.yml` must add up to 100. Users earn points for each question.
18
14
 
19
15
  ## Quiz structure
20
16
 
@@ -43,25 +39,26 @@ You run or lint the quiz from the directory that contains `quiz.yml` (e.g. `jtk
43
39
 
44
40
  `yml` files are YAML (YAML Ain't Markup Language) files. YAML is a human-readable data-serialization language; see [YAML documentation](https://yaml.org/) for more details. Also, see [YAML multiline info](https://yaml-multiline.info/) for more details on how to write multiline strings in YAML.
45
41
 
46
- Many items are written in Markdown. See [Markdown documentation](https://www.markdownguide.org/) for more details. In addition, you can use a small subset of LaTeX for mathematical expressions but these have to be enclosed between `·` signs, not standard `$` signs.
47
-
48
42
  ## The `quiz.yml` file
49
43
 
50
44
  The file `quiz.yml` defines the quiz root.
51
45
 
52
- - **title**: Title of the quiz.
53
- - **statement**: Short description or instructions shown for the quiz (Markdown).
54
- - **questions**: List of question entries. Each entry has:
55
- - **title**: Title of the question (e.g. for display in a table of contents).
56
- - **file**: Base name of the question file, without the `.yml` extension (e.g. `question` for `question.yml`).
57
- - **score**: Integer from 0 to 100. The sum of all `score` values in the list must be 100.
58
- - **shuffle** (optional): Whether to shuffle the order of questions when running the quiz. Defaults to `false`.
46
+ - `title`: Title of the quiz.
47
+ - `author`: Author of the quiz.
48
+ - `statement`: Short description or instructions shown for the quiz (Markdown).
49
+ - `questions`: List of question entries. Each entry has:
50
+ - `title`: Title of the question (e.g. for display in a table of contents).
51
+ - `file`: Base name of the question file, without the `.yml` extension (e.g. `question` for `question.yml`).
52
+ - `score`: Integer from 0 to 100. The sum of all `score` values in the list must be 100.
53
+ - `shuffle` (optional): Whether to shuffle the order of questions when running the quiz. Defaults to `false`.
59
54
 
60
55
  ### Example
61
56
 
62
57
  ```yaml
63
58
  title: Demo quiz
64
59
 
60
+ author: Jordi Petit
61
+
65
62
  statement: This quiz showcases the possibilities of the quiz problems at Jutge.org.
66
63
 
67
64
  shuffle: true
@@ -90,16 +87,24 @@ questions:
90
87
 
91
88
  ## Question types
92
89
 
93
- Each question is stored in a YAML file whose name matches the `file` field in `quiz.yml` (e.g. `question.yml`). The file must contain a **type** field that identifies the kind of question. All question types support an optional **hide_score** (default `false`) and an optional **partial_answer** (default `false`); see [Scoring](#terminology) above. Variable substitution applies to text fields and options when a corresponding `.py` file exists.
90
+ Each question is stored in a YAML file whose name matches the `file` field in `quiz.yml` (e.g. `question.yml`). The file must contain a `type` field that identifies the kind of question. Variable substitution applies to text fields and options when a corresponding `.py` file exists. All question types support an optional `hide_score` (default `false`) and an optional `partial_answer` (default `false`).
91
+
92
+ The `partial_answer` option is set per question in the question YAML:
93
+
94
+ - If `partial_answer` is set to `false` (default), users get full points for that question only when their answer is completely correct; any mistake gives zero points for that question.
95
+
96
+ - If `partial_answer` is set to `true`, users can receive partial points for that question when the answer is partially correct (e.g. proportional to how many parts are right), and the response may still be marked as "correct" if at least one part is right.
97
+
98
+ The `hide_score` option is set per question in the question YAML. If set to `true`, the question score is not shown to the user.
94
99
 
95
100
  ### SingleChoice
96
101
 
97
- One correct option among several. Exactly one choice must have `correct: true`. Choices can be shuffled (optional **shuffle**, default `true`). Each choice can have an optional **hint**. Duplicate choice text is not allowed.
102
+ One correct option among several. Exactly one choice must have `correct: true`. Choices can be shuffled (optional `shuffle`, default `true`). Each choice can have an optional `hint`. Duplicate choice text is not allowed.
98
103
 
99
- - **text**: Question text (supports `$var` and `${var}`).
100
- - **choices**: List of `{ text, correct?, hint? }`. One and only one choice must have `correct: true`.
101
- - **shuffle** (optional): Whether to shuffle choices. Defaults to `true`.
102
- - **partial_answer** (optional): Whether to award partial credit for this question. Defaults to `false`.
104
+ - `text`: Question text (supports `$var` and `${var}`).
105
+ - `choices`: List of `{ text, correct?, hint? }`. One and only one choice must have `correct: true`.
106
+ - `shuffle` (optional): Whether to shuffle choices. Defaults to `true`.
107
+ - `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
103
108
 
104
109
  Example:
105
110
 
@@ -121,12 +126,12 @@ Variables `a`, `b`, `s1`, `s2`, `s3` would be produced by a `single-choice.py` s
121
126
 
122
127
  ### MultipleChoice
123
128
 
124
- Zero or more correct options. Multiple choices can have `correct: true`. Choices can be shuffled (optional **shuffle**, default `true`).
129
+ Zero or more correct options. Multiple choices can have `correct: true`. Choices can be shuffled (optional `shuffle`, default `true`).
125
130
 
126
- - **text**: Question text (supports variables).
127
- - **choices**: List of `{ text, correct?, hint? }`.
128
- - **shuffle** (optional): Whether to shuffle choices. Defaults to `true`.
129
- - **partial_answer** (optional): Whether to award partial credit for this question. Defaults to `false`.
131
+ - `text`: Question text (supports variables).
132
+ - `choices`: List of `{ text, correct?, hint? }`.
133
+ - `shuffle` (optional): Whether to shuffle choices. Defaults to `true`.
134
+ - `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
130
135
 
131
136
  Example:
132
137
 
@@ -146,19 +151,19 @@ choices:
146
151
 
147
152
  ### FillIn
148
153
 
149
- One or more blanks in a text or code block. Each blank is identified by a placeholder (e.g. `S1`, `XXXX`) and has a correct answer and optional options (dropdown). If **options** are given, the correct answer must be one of them.
154
+ One or more blanks in a text or code block. Each blank is identified by a placeholder (e.g. `S1`, `XXXX`) and has a correct answer and optional options (dropdown). If `options` are given, the correct answer must be one of them.
150
155
 
151
- - **text**: Question or instructions (supports variables).
152
- - **context**: Text containing placeholders (e.g. `S1`, `S2`, `XXXX`). Placeholders are the keys in **items**.
153
- - **items**: Map from placeholder name to an item object:
154
- - **correct**: Correct answer (string).
155
- - **options** (optional): List of strings; if present, the blank is shown as a dropdown and **correct** must be in this list.
156
- - **maxlength** (optional): Max length for the answer. Defaults to 100.
157
- - **placeholder** (optional): Placeholder text in the input (e.g. `"?"`).
158
- - **ignorecase** (optional): Whether to ignore case when checking. Defaults to `true`.
159
- - **trim** (optional): Whether to trim spaces. Defaults to `true`.
160
- - **partial_answer** (optional): Whether this blank contributes to partial credit for the question. Defaults to `false`.
161
- - **partial_answer** (optional, at question level): Whether to award partial credit for this question. Defaults to `false`.
156
+ - `text`: Question or instructions (supports variables).
157
+ - `context`: Text containing placeholders (e.g. `S1`, `S2`, `XXXX`). Placeholders are the keys in `items`.
158
+ - `items`: Map from placeholder name to an item object:
159
+ - `correct`: Correct answer (string).
160
+ - `options` (optional): List of strings; if present, the blank is shown as a dropdown and `correct` must be in this list.
161
+ - `maxlength` (optional): Max length for the answer. Defaults to 100.
162
+ - `placeholder` (optional): Placeholder text in the input (e.g. `"?"`).
163
+ - `ignorecase` (optional): Whether to ignore case when checking. Defaults to `true`.
164
+ - `trim` (optional): Whether to trim spaces. Defaults to `true`.
165
+ - `partial_answer` (optional): Whether this blank contributes to partial credit for the question. Defaults to `false`.
166
+ - `partial_answer` (optional, at question level): Whether to award partial credit for this question. Defaults to `false`.
162
167
 
163
168
  Example with dropdowns:
164
169
 
@@ -217,13 +222,13 @@ items:
217
222
 
218
223
  ### Ordering
219
224
 
220
- User must order a list of items (e.g. chronological order). Items can be shown in shuffled order (optional **shuffle**, default `true`).
225
+ User must order a list of items (e.g. chronological order). Items can be shown in shuffled order (optional `shuffle`, default `true`).
221
226
 
222
- - **text**: Question text (supports variables).
223
- - **label**: Label for the list (e.g. “Programming language”).
224
- - **items**: List of strings in the **correct** order.
225
- - **shuffle** (optional): Whether to show items in random order. Defaults to `true`.
226
- - **partial_answer** (optional): Whether to award partial credit for this question. Defaults to `false`.
227
+ - `text`: Question text (supports variables).
228
+ - `label`: Label for the list (e.g. “Programming language”).
229
+ - `items`: List of strings in the correct order.
230
+ - `shuffle` (optional): Whether to show items in random order. Defaults to `true`.
231
+ - `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
227
232
 
228
233
  Example:
229
234
 
@@ -245,14 +250,14 @@ items:
245
250
 
246
251
  ### Matching
247
252
 
248
- Two columns: user matches each left item with one right item. Left and right lists can be shuffled (optional **shuffle**, default `true`).
253
+ Two columns: user matches each left item with one right item. Left and right lists can be shuffled (optional `shuffle`, default `true`).
249
254
 
250
- - **text**: Question text (supports variables).
251
- - **labels**: Two strings, e.g. `["Countries", "Capitals"]`.
252
- - **left**: List of strings (e.g. countries).
253
- - **right**: List of strings (e.g. capitals), in the same order as **left** (left[i] matches right[i]).
254
- - **shuffle** (optional): Whether to shuffle left and right columns. Defaults to `true`.
255
- - **partial_answer** (optional): Whether to award partial credit for this question. Defaults to `false`.
255
+ - `text`: Question text (supports variables).
256
+ - `labels`: Two strings, e.g. `["Countries", "Capitals"]`.
257
+ - `left`: List of strings (e.g. countries).
258
+ - `right`: List of strings (e.g. capitals), in the same order as `left` (left[i] matches right[i]).
259
+ - `shuffle` (optional): Whether to shuffle left and right columns. Defaults to `true`.
260
+ - `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
256
261
 
257
262
  Example:
258
263
 
@@ -284,9 +289,9 @@ right:
284
289
 
285
290
  Free-text answer with no automatic correction. Useful for open-ended or reflective answers.
286
291
 
287
- - **text**: Question text (supports variables).
288
- - **placeholder** (optional): Placeholder for the text area. Defaults to `""`. Supports variables.
289
- - **partial_answer** (optional): Whether to award partial credit for this question. Defaults to `false`.
292
+ - `text`: Question text (supports variables).
293
+ - `placeholder` (optional): Placeholder for the text area. Defaults to `""`. Supports variables.
294
+ - `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
290
295
 
291
296
  Example:
292
297
 
@@ -300,11 +305,30 @@ placeholder: 'My name is **$name** and I want to pass this course.'
300
305
 
301
306
  The variable `name` can be set by an optional `open-ended.py` (or the same base name as the question file) in the same directory.
302
307
 
308
+ ## Markdown usage
309
+
310
+ Markdown is supported in most fields. See [Markdown documentation](https://www.markdownguide.org/) for more details.
311
+
312
+ For quizzes, you may use:
313
+
314
+ - Lists (e.g. `- Item 1`, `- Item 2`, `- Item 3`)
315
+ - Italic text (e.g. `*italic text*`)
316
+ - Bold text (e.g. `**bold text**`)
317
+ - Underline text (e.g. `__underline text__`)
318
+ - Code (with backticks, e.g. `` `code` ``)
319
+ - Code blocks (with triple backticks and a programming language specifier)
320
+
321
+ Avoid sections, links, and tables. HTML is not supported.
322
+
323
+ In order to include images, you can use the `![Image description](FIG_DIR/image.png)` syntax. The `FIG_DIR` variable will be replaced by the URL where the images are stored. PNG and SVG images are supported.
324
+
325
+ In addition, you can use a small subset of LaTeX for mathematical expressions but these have to be enclosed between `·` signs, not standard `$` signs. Take into account that math in quizzes is fragile, as it conflicts with Markdown and other features.
326
+
303
327
  ## Variable substitution (`.py` files)
304
328
 
305
329
  If a question file is named `example.yml`, the toolkit looks for `example.py` in the same directory. When present:
306
330
 
307
- 1. The Python script is run with a given **seed** (passed as an argument by the toolkit) so that the run is reproducible.
331
+ 1. The Python script is run with a given `seed` (passed as an argument by the toolkit) so that the run is reproducible.
308
332
  2. The script’s global variables (that are JSON-serializable and whose names do not start with `__`) are collected.
309
333
  3. In the question YAML, any string field that supports substitution can use `$name` or `${name}` to be replaced by the value of `name`.
310
334
 
@@ -337,11 +361,11 @@ handler: quiz
337
361
 
338
362
  Other handler options (e.g. `std`, `graphic`) are for non-quiz problems. See [Problem anatomy — handler.yml](problem-anatomy.md#the-handleryml-file) for the full list of handler and option descriptions.
339
363
 
340
- ## Running and linting quizzes
364
+ ## Linting, running and playing quizzes
341
365
 
342
366
  From the toolkit CLI:
343
367
 
344
- - **Lint** a quiz (validate `quiz.yml` and all referenced question YAML files):
368
+ - `jtk quiz lint` — lint a quiz (validate `quiz.yml` and all referenced question YAML files):
345
369
 
346
370
  ```bash
347
371
  jtk quiz lint -d <directory>
@@ -349,12 +373,20 @@ From the toolkit CLI:
349
373
 
350
374
  Use the directory that contains `quiz.yml` (e.g. the `en` subdirectory).
351
375
 
352
- - **Run** a quiz (build questions, apply variables, output JSON or YAML):
376
+ - `jtk quiz run` — run a quiz (build questions, apply variables, output JSON or YAML):
377
+
353
378
  ```bash
354
379
  jtk quiz run -d <directory> [-s <seed>] [-f json|yaml]
355
380
  ```
381
+
356
382
  If no seed is provided, a random one is used. Running the quiz applies variable substitution and, if `shuffle` is true, shuffles question order (and, per question, choices or ordering/matching items when their `shuffle` is true).
357
383
 
384
+ - `jtk quiz play` — play a quiz in the terminal:
385
+ ```bash
386
+ jtk quiz play -d <directory> [-i <input>] [-o <output>] [-s <seed>]
387
+ ```
388
+ If no seed is provided, a random one is used. Playing the quiz applies variable substitution and, if `shuffle` is true, shuffles question order (and, per question, choices or ordering/matching items when their `shuffle` is true).
389
+
358
390
  ## Quick checklist
359
391
 
360
392
  - Use a problem folder with `handler: quiz` in `handler.yml`.
@@ -362,6 +394,6 @@ From the toolkit CLI:
362
394
  - Ensure every `file` in `quiz.yml` has a corresponding `file.yml` in the same directory.
363
395
  - Ensure question scores in `quiz.yml` sum to 100.
364
396
  - For SingleChoice, set exactly one choice with `correct: true`.
365
- - For FillIn with **options**, ensure **correct** is in the **options** list.
366
- - For Matching, ensure **left** and **right** have the same length and are in matching order.
397
+ - For FillIn with `options`, ensure `correct` is in the `options` list.
398
+ - For Matching, ensure `left` and `right` have the same length and are in matching order.
367
399
  - Use variable names in `$name` / `${name}` that are produced by the optional `file.py` script; the script is run with the toolkit’s seed for reproducibility.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jutge.org/toolkit",
3
3
  "description": "Toolkit to prepare problems for Jutge.org",
4
- "version": "4.4.17",
4
+ "version": "4.4.20",
5
5
  "homepage": "https://jutge.org",
6
6
  "author": {
7
7
  "name": "Jutge.org",
@@ -72,6 +72,7 @@
72
72
  "chalk": "^5.6.2",
73
73
  "chokidar": "^5.0.0",
74
74
  "cli-highlight": "^2.1.11",
75
+ "cli-table3": "^0.6.5",
75
76
  "commander": "^14.0.3",
76
77
  "dayjs": "^1.11.19",
77
78
  "env-paths": "^4.0.0",
package/toolkit/quiz.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { Command } from '@commander-js/extra-typings'
2
2
  import { runQuiz, lintQuiz } from '../lib/quiz'
3
+ import {
4
+ loadQuizTestInput,
5
+ playQuiz,
6
+ writeQuizTestOutput,
7
+ } from '../lib/play-quiz'
3
8
  import tui from '../lib/tui'
4
9
  import { findRealDirectories } from '../lib/helpers'
5
10
  import { random } from 'radash'
@@ -30,12 +35,11 @@ quizCmd
30
35
  .command('run')
31
36
  .summary('Run a quiz problem')
32
37
  .description(
33
- `Run a quiz problem. This command is work-in-progress and may not work as expected yet.
38
+ `Run a quiz problem.
34
39
 
35
40
  This command will run the quiz problem and print the resulting object to stdout.
36
41
  A random seed will be generated if not provided.
37
- `,
38
- )
42
+ `)
39
43
 
40
44
  .option('-d, --directory <directory>', 'problem directory', '.')
41
45
  .option('-s, --seed <seed>', 'random seed')
@@ -50,3 +54,26 @@ A random seed will be generated if not provided.
50
54
  tui.yaml(object)
51
55
  }
52
56
  })
57
+
58
+
59
+ quizCmd
60
+ .command('play')
61
+ .summary('Play a quiz problem')
62
+ .description(
63
+ `Play an interactive quiz test. Questions are shown in sequence; you can review and change answers before submitting.
64
+ Input is a JSON file from \`quiz run\`; if --input is omitted, a run is generated with a random seed from the given directory.
65
+ If --output is given, the results (your answers, correct answers, and score per question) are written to that file.`)
66
+
67
+ .option('-i, --input <input>', 'input JSON file from quiz run')
68
+ .option('-o, --output <output>', 'output file for results')
69
+ .option('-d, --directory <directory>', 'problem directory (used when --input is not provided)', '.')
70
+ .option('-s, --seed <seed>', 'random seed (used when --input is not provided)')
71
+
72
+ .action(async ({ input, output, directory, seed }) => {
73
+ const seedValue = seed !== undefined ? parseInt(seed, 10) : undefined
74
+ const quizInput = await loadQuizTestInput(input, directory, seedValue)
75
+ const results = await playQuiz(quizInput)
76
+ if (output !== undefined) {
77
+ await writeQuizTestOutput(output, results)
78
+ }
79
+ })