Intermediate Project 7 — Quiz Engine¶
Difficulty: Intermediate · Estimated effort: 14–18 hours · Prerequisites: Module 8 — Abstract Classes, Module 12 — Templates, Module 13 — Exception Handling, Module 14 — The STL with OOP · Combines: polymorphic question types, file-driven content, a scoring policy, a session object
This is one of the designated integration projects. It must use, explicitly and in writing: multiple classes · relationships between classes · encapsulation · inheritance or composition · polymorphism · the STL · exception handling.
No complete solution is provided. Adding a fifth question type is part of the marking, so the design has to leave room you have not filled in.
1. Scenario¶
A teacher wants a small quiz program: questions loaded from a text file, presented in a shuffled order, answered at the keyboard, marked, and summarised at the end with a breakdown by topic. Four question types are needed now — single choice, multiple choice, true/false, and short answer — and a fifth (fill in the blank with several accepted strings) is expected next term.
The last attempt had one Question struct with a type field and a switch in four places.
Adding the fifth type would have touched all four.
2. Problem statement¶
Build a quiz engine in which a question is defined by its behaviour, not by a tag: presenting itself, reading an answer, marking it, and reporting the correct answer. Content lives in a file; scoring is a policy; a session records what happened.
3. User requirements¶
- [ ] Load a quiz file and see how many questions it holds, by topic.
- [ ] Take the quiz: one question at a time, answer at the keyboard, immediate or deferred feedback as configured.
- [ ] Get a score with a per-topic breakdown at the end.
- [ ] Review every question afterwards, with the correct answer shown.
- [ ] Have the question order and the options shuffled, reproducibly from a seed.
- [ ] Be told which line of a bad quiz file is wrong.
4. Functional requirements¶
- [ ] An abstract
Questioninterface:prompt(),readAnswer(istream&),mark(),explain(). - [ ] Four concrete question types, each owning its own answer data.
- [ ] A
Quizowning questions instd::unique_ptrs, loaded from a file. - [ ] A
Sessionrecording each question, the given answer and the outcome. - [ ] A
ScoringPolicyinterface: at least "all-or-nothing" and "partial credit for multiple choice". - [ ] Shuffling with a seeded generator, so a session can be reproduced exactly.
- [ ] Typed errors for a malformed file, a closed session, and an unanswerable question.
5. Non-functional requirements¶
- [ ] No
switchon a question type anywhere outside the loader. - [ ] Answers are compared case-insensitively for text, and whitespace is normalised — with the normalisation in one tested function.
- [ ] A partially loaded quiz is impossible: the file either produces a whole quiz or an error.
- [ ] The engine is driven by streams, so a session can be scripted from a file in tests.
- [ ] Builds warning-free with
g++ -std=c++17 -Wall -Wextra -Werror.
6. Suggested features¶
- A timer per question, using an injected clock.
- Question pools by topic and a fixed-length draw, with the draw rule documented.
- Export a session report to a file, as a second format behind an interface.
- A
QuestionFactoryso the loader names no question type — then decide whether that removed theswitchor moved it.
7. OOP concepts involved¶
| Concept | Required | Where it appears |
|---|---|---|
| Multiple classes | yes | Question + four implementations, Quiz, Session, scoring policies, loader, errors |
| Relationships | yes | Quiz ◆ questions; session ◆ records; scoring injected; loader ┈┈> factory |
| Encapsulation | yes | A question's correct answer is private and never returned as mutable state |
| Inheritance or composition | yes | Interface inheritance for questions; the alternative (composition over a behaviour record) must be considered and rejected in writing |
| Polymorphism | yes | Present, mark and explain all dispatch; no type tag |
| The STL | yes | std::vector and unique_ptr for the quiz, std::map for the topic breakdown, <algorithm> for shuffling |
| Exception handling | yes | Malformed content, illegal answer shapes and closed sessions each throw their own type |
8. Suggested design considerations¶
Make mark() a pure function of the stored answer. A question marked twice must give the
same result, and marking must not mutate the question. That single rule makes the session
reviewable and the scoring policy testable.
Decide where partial credit lives. In the question (it knows what a half-right multiple choice is) or in the policy (it owns the rules)? Both work; the wrong answer is both, which makes the same decision in two places.
Shuffling must be reproducible. Seeded std::mt19937 in one place, seeded from an argument.
A quiz engine you cannot replay is a quiz engine you cannot debug or test.
Plan for the fifth type now. Write the loader so that a new type is one file plus one registration line. Then, when you add it in the advanced extension, count the files touched and compare with your estimate. Being wrong by a factor is the lesson.
9. Minimum viable version¶
Two question types, a quiz loaded from a simple file, a session that asks and marks each question, and a final score. No shuffling, no policies.
10. Intermediate extension¶
Add the remaining two types, then the scoring policies, and prove with one multiple-choice question that partial credit and all-or-nothing differ by the documented amount. Add the per-topic breakdown, computed with STL algorithms.
11. Advanced extension¶
Add the fifth question type and count the files it touched against your written estimate. Then
add a QuestionFactory so the loader contains no type names, and answer in writing whether the
factory removed the conditional logic or relocated it — with the line counts as evidence.
12. Testing checklist¶
- [ ] A quiz file with one of each type loads to four questions in file order
- [ ] Each type marks a correct answer as correct and a wrong one as incorrect
- [ ] Multiple choice marks a partially correct answer as documented under each policy
- [ ] Text answers are matched case-insensitively and after whitespace normalisation
- [ ] A text answer that differs only in internal spacing is documented as right or wrong
- [ ] The topic breakdown totals equal the overall score
- [ ] A malformed file is refused with a line number, and produces no quiz
- [ ] The same seed produces the same question order, twice
- [ ] A different seed produces a different order (checked over several seeds)
- [ ] Reviewing a session shows the given answer and the correct answer for each question
- [ ] Marking does not mutate a question: marking twice gives the same result
13. Documentation checklist¶
- [ ] The quiz file format, with one example per question type and three invalid ones
- [ ] The class diagram, marking the interface and its four implementations
- [ ] The scoring policies, stated precisely enough to compute by hand
- [ ] The normalisation rule for text answers
- [ ] The fifth-type change cost, as estimated and as measured
14. Self-evaluation rubric¶
| Criterion | Excellent looks like | Weight |
|---|---|---|
| Polymorphic design | No type tags or switches outside the loader; a new type is local | 30% |
| Correctness | Marking and normalisation verified on awkward answers | 20% |
| Extensibility | The fifth type added with the estimated change cost, measured | 15% |
| Testability | Streams and seeds throughout; sessions reproducible | 15% |
| Failure handling | Malformed files and illegal answers rejected with context | 10% |
| Documentation | Format, policies and normalisation rules written precisely | 10% |
Related: Intermediate projects · Project roadmap · All projects · Module 8 — Abstract Classes