quizforge / feature tour

Every capability, with the code that produces it

Each snippet is a complete question — drop it into any quiz. rendered versions ↗ · full source ↗

Multiple choice

A ✓ (or ✔ / #yes) marks the correct option; option order shuffles per set. #explain shows only in the key.

+ #m(2) Which option is correct?
  - ✓ This one
  - Not this one
  #explain[Key-only explanation.]

Auto multi-select

Two or more ✓ options make a multi-select — “(Select all that apply.)” is added and the key reports all letters (e.g. AB).

+ #m(2) Which reduce overfitting?
  - ✓ Dropout
  - ✓ Weight decay
  - More parameters

Fill in the blank

The answer lives inside the blank: hidden on the paper, printed in the key, exported to the grading CSV. Math answers work.

+ #m(1) Water is H#sub[2]#blank[O], and
  $e^(i pi) = #blank(width: 1.5cm)[$-1$]$.

Subjective + rubric

answer(space) prints writing space on the paper; the model answer and rubric appear only in the key.

+ #m(3) Derive the normal equation.
  #answer(4cm, rubric: [+2 gradient; +1 solve.])[
    $hat(theta) = (X^T X)^(-1) X^T y$]

Booklet mode

answer(none) prints no space — for exams answered in a separate booklet. Quiz-wide default: quiz.with(answer-space: none).

+ #m(2) Answer in your booklet.
  #answer(none)[Model answer, key only.]

Pinned options

“None/All/Both of the above” pins itself last automatically; #pin / #pin-first pin anything else under shuffling.

+ #m(1) What is the output size?
  - $30 times 30$
  - $32 times 32$
  - ✓ None of the above  // auto-pinned last

Two-column options

Short options in two columns to save vertical space.

+ #m(1) #opts(columns: 2) Which direction?
  - ✓ North
  - South
  - East
  - West

Compact options

Options flow inline in one wrapping row — the densest layout.

+ #m(1) #opts(compact: true) $2 + 2 = $?
  - $3$
  - ✓ $4$
  - $5$

Frozen option order

I/II/III progressions must not shuffle.

+ #m(1) #opts(shuffle: false) Which are true?
  - I only
  - ✓ I and II
  - II and III

Frozen identity

Question identity defaults to a hash of its own text; #qid freezes it so wording edits never reshuffle its options.

+ #m(1) #qid("conv-output") Editable wording,
  stable shuffle.
  - ✓ Stable
  - Equally stable

Frozen question order

A part whose questions stay in authored order in every set.

= Long Answers #section(shuffle: false)

+ First, always first.
  #answer(3cm)[...]

Custom page furniture

header:/footer: accept auto (generated), none, fixed content, or a function of (exam, set, mode, total). Totals and part subtotals are always computed.

#show: quiz.with(
  id: "quiz-1", sets: ("A", "B"),
  header: none,
  footer: info => align(center,
    text(9pt)[Set #info.at("set") · #info.total marks]),
)

Question banks + sampling

The constructor front-end: build banks with mcq()/fib()/subj(), filter by type/topic/difficulty, and pick N — the same N in every set.

#make-exam(
  exam: (id: "midsem", sets: ("A", "B")),
  questions: bank,
  sections: ((title: "MCQ",
    use: ("must-appear",),   // guaranteed
    filter: (type: "mcq"),
    pick: 10),),             // same 10 in every set
)

The tour, rendered

All of the snippets above live in one real randomized exam (exams/demo-features.typ). Left: the student paper. Right: the same pages of its answer key.

Feature tour paper, page 1
Student paper — page 1
Feature tour key, page 1
Answer key — page 1
Feature tour paper, page 2
Student paper — page 2
Feature tour key, page 2
Answer key — page 2