@jutge.org/toolkit 4.4.17 → 4.4.19
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.
- package/assets/problems/quizzes/demo-quiz.pbm/README.md +1 -0
- package/assets/problems/quizzes/demo-quiz.pbm/ca/award.png +0 -0
- package/assets/problems/quizzes/demo-quiz.pbm/ca/quiz.yml +1 -1
- package/assets/problems/quizzes/demo-quiz.pbm/ca/single-choice.yml +9 -1
- package/assets/problems/quizzes/demo-quiz.pbm/ca/some-drawing.svg +150 -0
- package/assets/problems/quizzes/demo-quiz.pbm/ca/some-image.png +0 -0
- package/assets/problems/quizzes/demo-quiz.pbm/en/quiz.yml +1 -1
- package/assets/problems/quizzes/demo-quiz.pbm/en/single-choice.yml +9 -1
- package/assets/problems/quizzes/demo-quiz.pbm/en/some-drawing.svg +150 -0
- package/assets/problems/quizzes/demo-quiz.pbm/en/some-image.png +0 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/README.md +11 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/handler.yml +1 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q01.yml +14 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q02.yml +14 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q03.yml +14 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q04.yml +15 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q05.yml +32 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q06.yml +12 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q07.yml +23 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q08.yml +14 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q09.yml +14 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/q10.yml +23 -0
- package/assets/problems/quizzes/heroes-of-computation.pbm/en/quiz.yml +48 -0
- package/assets/problems/quizzes/the-answer-is-42.pbm/en/quiz.yml +2 -0
- package/dist/index.js +469 -465
- package/docs/quiz-anatomy.md +74 -59
- package/package.json +2 -1
- package/toolkit/quiz.ts +30 -3
package/docs/quiz-anatomy.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
|
@@ -49,19 +45,22 @@ Many items are written in Markdown. See [Markdown documentation](https://www.mar
|
|
|
49
45
|
|
|
50
46
|
The file `quiz.yml` defines the quiz root.
|
|
51
47
|
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
48
|
+
- `title`: Title of the quiz.
|
|
49
|
+
- `author`: Author of the quiz.
|
|
50
|
+
- `statement`: Short description or instructions shown for the quiz (Markdown).
|
|
51
|
+
- `questions`: List of question entries. Each entry has:
|
|
52
|
+
- `title`: Title of the question (e.g. for display in a table of contents).
|
|
53
|
+
- `file`: Base name of the question file, without the `.yml` extension (e.g. `question` for `question.yml`).
|
|
54
|
+
- `score`: Integer from 0 to 100. The sum of all `score` values in the list must be 100.
|
|
55
|
+
- `shuffle` (optional): Whether to shuffle the order of questions when running the quiz. Defaults to `false`.
|
|
59
56
|
|
|
60
57
|
### Example
|
|
61
58
|
|
|
62
59
|
```yaml
|
|
63
60
|
title: Demo quiz
|
|
64
61
|
|
|
62
|
+
author: Jordi Petit
|
|
63
|
+
|
|
65
64
|
statement: This quiz showcases the possibilities of the quiz problems at Jutge.org.
|
|
66
65
|
|
|
67
66
|
shuffle: true
|
|
@@ -90,16 +89,24 @@ questions:
|
|
|
90
89
|
|
|
91
90
|
## Question types
|
|
92
91
|
|
|
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
|
|
92
|
+
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`).
|
|
93
|
+
|
|
94
|
+
The `partial_answer` option is set per question in the question YAML:
|
|
95
|
+
|
|
96
|
+
- 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.
|
|
97
|
+
|
|
98
|
+
- 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.
|
|
99
|
+
|
|
100
|
+
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
101
|
|
|
95
102
|
### SingleChoice
|
|
96
103
|
|
|
97
|
-
One correct option among several. Exactly one choice must have `correct: true`. Choices can be shuffled (optional
|
|
104
|
+
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
105
|
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
106
|
+
- `text`: Question text (supports `$var` and `${var}`).
|
|
107
|
+
- `choices`: List of `{ text, correct?, hint? }`. One and only one choice must have `correct: true`.
|
|
108
|
+
- `shuffle` (optional): Whether to shuffle choices. Defaults to `true`.
|
|
109
|
+
- `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
|
|
103
110
|
|
|
104
111
|
Example:
|
|
105
112
|
|
|
@@ -121,12 +128,12 @@ Variables `a`, `b`, `s1`, `s2`, `s3` would be produced by a `single-choice.py` s
|
|
|
121
128
|
|
|
122
129
|
### MultipleChoice
|
|
123
130
|
|
|
124
|
-
Zero or more correct options. Multiple choices can have `correct: true`. Choices can be shuffled (optional
|
|
131
|
+
Zero or more correct options. Multiple choices can have `correct: true`. Choices can be shuffled (optional `shuffle`, default `true`).
|
|
125
132
|
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
133
|
+
- `text`: Question text (supports variables).
|
|
134
|
+
- `choices`: List of `{ text, correct?, hint? }`.
|
|
135
|
+
- `shuffle` (optional): Whether to shuffle choices. Defaults to `true`.
|
|
136
|
+
- `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
|
|
130
137
|
|
|
131
138
|
Example:
|
|
132
139
|
|
|
@@ -146,19 +153,19 @@ choices:
|
|
|
146
153
|
|
|
147
154
|
### FillIn
|
|
148
155
|
|
|
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
|
|
156
|
+
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
157
|
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
158
|
+
- `text`: Question or instructions (supports variables).
|
|
159
|
+
- `context`: Text containing placeholders (e.g. `S1`, `S2`, `XXXX`). Placeholders are the keys in `items`.
|
|
160
|
+
- `items`: Map from placeholder name to an item object:
|
|
161
|
+
- `correct`: Correct answer (string).
|
|
162
|
+
- `options` (optional): List of strings; if present, the blank is shown as a dropdown and `correct` must be in this list.
|
|
163
|
+
- `maxlength` (optional): Max length for the answer. Defaults to 100.
|
|
164
|
+
- `placeholder` (optional): Placeholder text in the input (e.g. `"?"`).
|
|
165
|
+
- `ignorecase` (optional): Whether to ignore case when checking. Defaults to `true`.
|
|
166
|
+
- `trim` (optional): Whether to trim spaces. Defaults to `true`.
|
|
167
|
+
- `partial_answer` (optional): Whether this blank contributes to partial credit for the question. Defaults to `false`.
|
|
168
|
+
- `partial_answer` (optional, at question level): Whether to award partial credit for this question. Defaults to `false`.
|
|
162
169
|
|
|
163
170
|
Example with dropdowns:
|
|
164
171
|
|
|
@@ -217,13 +224,13 @@ items:
|
|
|
217
224
|
|
|
218
225
|
### Ordering
|
|
219
226
|
|
|
220
|
-
User must order a list of items (e.g. chronological order). Items can be shown in shuffled order (optional
|
|
227
|
+
User must order a list of items (e.g. chronological order). Items can be shown in shuffled order (optional `shuffle`, default `true`).
|
|
221
228
|
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
229
|
+
- `text`: Question text (supports variables).
|
|
230
|
+
- `label`: Label for the list (e.g. “Programming language”).
|
|
231
|
+
- `items`: List of strings in the correct order.
|
|
232
|
+
- `shuffle` (optional): Whether to show items in random order. Defaults to `true`.
|
|
233
|
+
- `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
|
|
227
234
|
|
|
228
235
|
Example:
|
|
229
236
|
|
|
@@ -245,14 +252,14 @@ items:
|
|
|
245
252
|
|
|
246
253
|
### Matching
|
|
247
254
|
|
|
248
|
-
Two columns: user matches each left item with one right item. Left and right lists can be shuffled (optional
|
|
255
|
+
Two columns: user matches each left item with one right item. Left and right lists can be shuffled (optional `shuffle`, default `true`).
|
|
249
256
|
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
257
|
+
- `text`: Question text (supports variables).
|
|
258
|
+
- `labels`: Two strings, e.g. `["Countries", "Capitals"]`.
|
|
259
|
+
- `left`: List of strings (e.g. countries).
|
|
260
|
+
- `right`: List of strings (e.g. capitals), in the same order as `left` (left[i] matches right[i]).
|
|
261
|
+
- `shuffle` (optional): Whether to shuffle left and right columns. Defaults to `true`.
|
|
262
|
+
- `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
|
|
256
263
|
|
|
257
264
|
Example:
|
|
258
265
|
|
|
@@ -284,9 +291,9 @@ right:
|
|
|
284
291
|
|
|
285
292
|
Free-text answer with no automatic correction. Useful for open-ended or reflective answers.
|
|
286
293
|
|
|
287
|
-
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
294
|
+
- `text`: Question text (supports variables).
|
|
295
|
+
- `placeholder` (optional): Placeholder for the text area. Defaults to `""`. Supports variables.
|
|
296
|
+
- `partial_answer` (optional): Whether to award partial credit for this question. Defaults to `false`.
|
|
290
297
|
|
|
291
298
|
Example:
|
|
292
299
|
|
|
@@ -304,7 +311,7 @@ The variable `name` can be set by an optional `open-ended.py` (or the same base
|
|
|
304
311
|
|
|
305
312
|
If a question file is named `example.yml`, the toolkit looks for `example.py` in the same directory. When present:
|
|
306
313
|
|
|
307
|
-
1. The Python script is run with a given
|
|
314
|
+
1. The Python script is run with a given `seed` (passed as an argument by the toolkit) so that the run is reproducible.
|
|
308
315
|
2. The script’s global variables (that are JSON-serializable and whose names do not start with `__`) are collected.
|
|
309
316
|
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
317
|
|
|
@@ -337,11 +344,11 @@ handler: quiz
|
|
|
337
344
|
|
|
338
345
|
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
346
|
|
|
340
|
-
##
|
|
347
|
+
## Linting, running and playing quizzes
|
|
341
348
|
|
|
342
349
|
From the toolkit CLI:
|
|
343
350
|
|
|
344
|
-
-
|
|
351
|
+
- `jtk quiz lint` — lint a quiz (validate `quiz.yml` and all referenced question YAML files):
|
|
345
352
|
|
|
346
353
|
```bash
|
|
347
354
|
jtk quiz lint -d <directory>
|
|
@@ -349,12 +356,20 @@ From the toolkit CLI:
|
|
|
349
356
|
|
|
350
357
|
Use the directory that contains `quiz.yml` (e.g. the `en` subdirectory).
|
|
351
358
|
|
|
352
|
-
-
|
|
359
|
+
- `jtk quiz run` — run a quiz (build questions, apply variables, output JSON or YAML):
|
|
360
|
+
|
|
353
361
|
```bash
|
|
354
362
|
jtk quiz run -d <directory> [-s <seed>] [-f json|yaml]
|
|
355
363
|
```
|
|
364
|
+
|
|
356
365
|
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
366
|
|
|
367
|
+
- `jtk quiz play` — play a quiz in the terminal:
|
|
368
|
+
```bash
|
|
369
|
+
jtk quiz play -d <directory> [-i <input>] [-o <output>] [-s <seed>]
|
|
370
|
+
```
|
|
371
|
+
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).
|
|
372
|
+
|
|
358
373
|
## Quick checklist
|
|
359
374
|
|
|
360
375
|
- Use a problem folder with `handler: quiz` in `handler.yml`.
|
|
@@ -362,6 +377,6 @@ From the toolkit CLI:
|
|
|
362
377
|
- Ensure every `file` in `quiz.yml` has a corresponding `file.yml` in the same directory.
|
|
363
378
|
- Ensure question scores in `quiz.yml` sum to 100.
|
|
364
379
|
- For SingleChoice, set exactly one choice with `correct: true`.
|
|
365
|
-
- For FillIn with
|
|
366
|
-
- For Matching, ensure
|
|
380
|
+
- For FillIn with `options`, ensure `correct` is in the `options` list.
|
|
381
|
+
- For Matching, ensure `left` and `right` have the same length and are in matching order.
|
|
367
382
|
- 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.
|
|
4
|
+
"version": "4.4.19",
|
|
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.
|
|
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
|
+
})
|