Project 5 — The Quiz Application

← Projects home · ← Project 4 · Tier: Basic · Units first: 05–08 — loops, functions, references, decomposition

Overview

A self-test engine for this course’s students: a built-in bank of multiple-choice questions on units 01–06, served in quiz sessions. The player picks a session length, answers, gets a per-question verdict and a final grade, can review wrong answers, and replay until satisfied. Statistics (best score, sessions played) persist for the run.

Learning objectives

Prerequisites

Unit What you need
05–06 loops, counters
07–08 functions, references, records-lite thinking (parallel vectors)

Requirements

  1. A bank of at least 8 MCQs on Units 01–06, each with 4 options, a correct index, and a one-line explanation.
  2. Session: the player chooses 3, 5, or all questions; the bank serves them in order.
  3. Per question: print, read 1–4 (invalid → re-prompt, not scored), verdict + explanation; wrong answers are recorded.
  4. Final: score, percentage, grade message (≥ 80 Excellent, ≥ 60 Good, ≥ 40 Keep practising, else See the tutor), then the review of wrong answers.
  5. Post-session menu: replay, view bank statistics (sessions played, best %), quit.

Functional requirements

ID Statement Verified by
F1 a 3-question session scores and grades correctly T1
F2 an invalid answer (0, 5, abc) re-prompts without scoring T2
F3 the review lists exactly the wrong answers with their explanations T3
F4 bank statistics survive across sessions in one run T4
F5 every bank question prints with its four options T5

Suggested data structures

Milestones

Tasks

  1. Write the bank vectors with your 8 questions (draw them from Units 01–06 honestly).
  2. Write askQuestion(index, given&) — print, validate, verdict, return correctness.
  3. Write runSession(bank..., length, results&) — the loop, counters, the review.
  4. Write printStats(sessions, bestPercent).
  5. Write the session menu; run the test plan.

Test plan

# Sequence Expected
T1 3-question session, all correct 3/3 (100%), Excellent, empty review
T2 answer 0 then 5 then the right option two re-prompts, then scored once
T3 answer two wrong review lists exactly those two, with explanations
T4 session 100%, session 0% → stats sessions 2, best 100%
T5 full-bank session all 8 questions in bank order

Edge cases

Extension ideas

  1. Shuffle the question order (seeded, so tests stay deterministic).
  2. Load questions from a file — the door to Project 9.
  3. Category tags per question; the player picks a topic.

Grading / self-assessment

Shared rubric plus:

Hints

  1. The bank is the program: if the questions live in vectors, the engine is one loop; if they live in code, it’s eight ifs. Choose vectors.
  2. Record givenAnswers as chars — the review pairs them with askedIndexes to find the wrong ones.
  3. The percentage is correct * 100 / asked — integer arithmetic is exact when asked divides evenly; use double when it doesn’t.

Complete reference solution

// quiz.cpp — Programming Fundamentals Using C++
// Project 5 · The Quiz Application
// Build: g++ -std=c++17 -Wall -Wextra quiz.cpp -o quiz

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
using namespace std;

// The bank: 8 MCQs on Units 01-06. Adding a question = adding to these vectors.
// Parallel-vector scaffolding — Unit 14's struct will replace it cleanly.

void loadBank(vector<string>& q, vector<string>& o1, vector<string>& o2,
              vector<string>& o3, vector<string>& o4, vector<int>& answer,
              vector<string>& why) {
    q.push_back("What does a compiler do?");
    o1.push_back("Runs the program line by line");
    o2.push_back("Translates source code to machine code");
    o3.push_back("Formats the source code");
    o4.push_back("Stores variables");
    answer.push_back(2);
    why.push_back("A compiler translates the whole source to machine code before running.");

    q.push_back("Which type holds 3.14 exactly-ish?");
    o1.push_back("int"); o2.push_back("char"); o3.push_back("double"); o4.push_back("bool");
    answer.push_back(3);
    why.push_back("double is the floating-point type for fractional values.");

    q.push_back("What does cin >> x leave in the buffer?");
    o1.push_back("Nothing"); o2.push_back("The newline"); o3.push_back("The value"); o4.push_back("An error");
    answer.push_back(2);
    why.push_back(">> leaves the newline, which is why getline needs cin.ignore after it.");

    q.push_back("Which loop runs at least once?");
    o1.push_back("while"); o2.push_back("for"); o3.push_back("do-while"); o4.push_back("none");
    answer.push_back(3);
    why.push_back("do-while tests the condition after the body.");

    q.push_back("What prints: cout << 7 / 2;");
    o1.push_back("3.5"); o2.push_back("3"); o3.push_back("4"); o4.push_back("error");
    answer.push_back(2);
    why.push_back("Integer division truncates: 7/2 is 3.");

    q.push_back("Which operator is the equality test?");
    o1.push_back("="); o2.push_back("=="); o3.push_back("==="); o4.push_back("!=");
    answer.push_back(2);
    why.push_back("== compares; = assigns.");

    q.push_back("A sentinel is...");
    o1.push_back("A loop counter");
    o2.push_back("A special value ending input");
    o3.push_back("A kind of if");
    o4.push_back("The loop body");
    answer.push_back(2);
    why.push_back("A sentinel is the agreed value that means 'no more data'.");

    q.push_back("How many times does for(int i=0;i<3;++i) run?");
    o1.push_back("2"); o2.push_back("3"); o3.push_back("4"); o4.push_back("forever");
    answer.push_back(2);
    why.push_back("i = 0, 1, 2 — three iterations.");
}

bool askQuestion(const vector<string>& q, const vector<string>& o1, const vector<string>& o2,
                 const vector<string>& o3, const vector<string>& o4, const vector<int>& answer,
                 const vector<string>& why, size_t index, char& given) {
    cout << "\n" << q[index] << "\n";
    cout << "  1. " << o1[index] << "\n"
         << "  2. " << o2[index] << "\n"
         << "  3. " << o3[index] << "\n"
         << "  4. " << o4[index] << "\n";

    int choice;
    cout << "Your answer (1-4): ";
    cin >> choice;
    while (choice < 1 || choice > 4) {          // invalid: re-prompt, never score
        cout << "Answer with 1-4: ";
        cin >> choice;
    }
    given = static_cast<char>('0' + choice);

    bool correct = (choice == answer[index]);
    cout << (correct ? "Correct! " : "Wrong. ") << why[index] << "\n";
    return correct;
}

void runSession(const vector<string>& q, const vector<string>& o1, const vector<string>& o2,
                const vector<string>& o3, const vector<string>& o4, const vector<int>& answer,
                const vector<string>& why, size_t length,
                vector<size_t>& askedIndexes, vector<char>& givenAnswers) {
    int correct = 0;
    askedIndexes.clear();
    givenAnswers.clear();

    for (size_t i = 0; i < length; ++i) {
        char given;
        if (askQuestion(q, o1, o2, o3, o4, answer, why, i, given)) ++correct;
        askedIndexes.push_back(i);
        givenAnswers.push_back(given);
    }

    int percent = static_cast<int>(correct * 100 / length);
    cout << "\nScore: " << correct << "/" << length << " (" << percent << "%)\n";
    if      (percent >= 80) cout << "Excellent\n";
    else if (percent >= 60) cout << "Good\n";
    else if (percent >= 40) cout << "Keep practising\n";
    else                    cout << "See the tutor\n";

    if (correct == length) {
        cout << "Perfect session — nothing to review\n";
    } else {
        cout << "\nReview of wrong answers:\n";
        for (size_t i = 0; i < askedIndexes.size(); ++i) {
            size_t qi = askedIndexes[i];
            if (givenAnswers[i] - '0' != answer[qi])
                cout << "  " << q[qi] << "\n    you said " << givenAnswers[i]
                     << " — " << why[qi] << "\n";
        }
    }
}

int main() {
    vector<string> q, o1, o2, o3, o4, why;
    vector<int> answer;
    loadBank(q, o1, o2, o3, o4, answer, why);

    int sessions = 0, bestPercent = -1;

    int choice;
    do {
        cout << "\n=== QUIZ ===\n1 Quick (3) · 2 Standard (5) · 3 Full bank ("
             << q.size() << ") · 4 Stats · 0 Quit: ";
        cin >> choice;

        if (choice >= 1 && choice <= 3) {
            size_t length = (choice == 1) ? 3 : (choice == 2) ? 5 : q.size();
            vector<size_t> asked;
            vector<char> given;
            runSession(q, o1, o2, o3, o4, answer, why, length, asked, given);

            ++sessions;
            int correct = 0;
            for (size_t i = 0; i < asked.size(); ++i)
                if (given[i] - '0' == answer[asked[i]]) ++correct;
            int percent = static_cast<int>(correct * 100 / asked.size());
            if (percent > bestPercent) bestPercent = percent;
        } else if (choice == 4) {
            cout << "Sessions played: " << sessions << "\n";
            if (bestPercent >= 0) cout << "Best score: " << bestPercent << "%\n";
        } else if (choice != 0) {
            cout << "Invalid choice\n";
        }
    } while (choice != 0);

    cout << "Goodbye — " << sessions << " session(s) this run\n";
    return 0;
}

Explanation of important design decisions

← Project 4 · Projects home · Next: Project 6 — The Inventory Management System

— Engr. Dr. Muhammad Nadeem Majeed, Professor, Department of Data Science (PUCIT), University of the Punjab, Lahore · About the instructor