Learn
03.6 | PROJECT: A QUIZ WITH A SCOREBOARD
01 | DEFINE A QUIZ YOU CAN TEST
The project asks each player three multiple-choice questions. A player selects an option number. Each correct answer earns one point; a wrong accepted answer earns zero.
Invalid option text is retried for the same question and does not move to the next question. After all questions, the program stores a name and score on a session scoreboard. Another player can take the same quiz, or enter quit at the name prompt to finish.
The scoreboard is kept only during the current run. Displaying it does not save it to a file or database. Persistence is a later course topic.
02 | STORE QUESTION DATA SEPARATELY
Each question is a dictionary with prompt, options and answer. options is a list of strings; answer is the accepted option code as a string such as "2".
A list holds the question dictionaries in quiz order. Adding a question means adding another consistent record rather than copying an entire decision block.
The author must keep answer consistent with the numbered options. The core program assumes a non-empty, correctly authored question bank. A successful player-input check does not verify the quality of the stored answers.
question = {"prompt": "Which type stores text?",
"options": ["int", "str", "bool"],
"answer": "2"}
print(question["prompt"])
print(question["options"][1])EXPECTED OUTPUT
Which type stores text? str
03 | DISPLAY OPTIONS WITH NUMBERS
enumerate(options, start=1) supplies a one-based display number and each option string. The underlying options list still begins at index zero.
The player enters a display code. This design compares that code with the answer code directly, so no subtraction or list indexing is needed to check correctness.
Build the permitted codes from the option count. A three-option question accepts "1", "2" and "3". It does not accept "0" or "4", and it does not need integer conversion just to compare these labels.
04 | RETRY INVALID CHOICES IN THE CURRENT QUESTION
Read a stripped response. While it is not in valid_choices, print feedback and read again. The question loop advances only after an accepted response has been processed.
An input such as "two" is rejected as an unsupported option code, not sent to int conversion. This is a deliberate text-code design. "02" is also rejected even if a human might interpret it as two.
Check each question’s own option count rather than assuming every question always has exactly three choices.
05 | COUNT POINTS FOR ONE PLAYER
Initialise score once when a new player starts. For each accepted answer, compare the response with question["answer"]. If they match, add one; otherwise give correction feedback without increasing the score.
The outer player loop and inner question loop have different roles. The player’s score reset belongs inside the player loop, before the question loop. Resetting it per question would lose earlier points.
Percentage is score / len(questions) × 100. The known non-empty question bank prevents a zero divisor; an editable empty bank would require a guard.
06 | KEEP A RESULT AS ONE RECORD
The scoreboard stores a tuple (score, name) for each completed player. That keeps a result paired with its owner. Duplicate player names are allowed and remain separate completed plays in this version.
sorted(scoreboard, reverse=True) returns a new descending list. Tuple comparison considers score first, then name if scores tie. Here tied names display in descending text order; that is an explicit display rule, not a claim that one tied player deserves a higher score.
The printed positions are display positions, not shared competition ranks. A more sophisticated ranking policy is a useful later extension.
07 | THE COMPLETE CORE QUIZ
Read the data records first, then trace the two loop levels. The program uses collection methods and control flow already introduced, without requiring a function definition from the next section.
# Session quiz: authored question bank must be non-empty and consistent.
questions = [
{"prompt": "Which Python type stores text?",
"options": ["int", "str", "bool"], "answer": "2"},
{"prompt": "What is the first non-negative list index?",
"options": ["0", "1", "-1"], "answer": "1"},
{"prompt": "Which collection keeps distinct members?",
"options": ["list", "tuple", "set"], "answer": "3"}
]
scoreboard = []
print("Python collections quiz")
while True:
name = input("Player first name, or quit to finish: ").strip()
if name == "quit":
break
if not name:
print("Enter a non-empty name")
continue
score = 0
for question in questions:
print()
print(question["prompt"])
for number, option in enumerate(question["options"], start=1):
print(f"{number}. {option}")
valid_choices = [str(number) for number in range(1, len(question["options"]) + 1)]
response = input("Option number: ").strip()
while response not in valid_choices:
print("Choose one of the displayed option numbers")
response = input("Option number: ").strip()
if response == question["answer"]:
score += 1
print("Correct")
else:
print(f"Not correct. Answer code: {question['answer']}")
percentage = score / len(questions) * 100
print(f"{name}: {score}/{len(questions)} ({percentage:.1f}%)")
scoreboard.append((score, name))
print()
print("Session scoreboard: score descending, then name descending for ties")
if scoreboard:
ranked = sorted(scoreboard, reverse=True)
for position, result in enumerate(ranked, start=1):
score, name = result
print(f"Display position {position}: {name} - {score}/{len(questions)}")
else:
print("No completed players")08 | TRACE A TWO-PLAYER SESSION
Maya enters codes 2, 1 and 3, earning three points. Kai enters 1, 1 and 2, earning one point. The scoreboard arrival order is [(3, "Maya"), (1, "Kai")].
If Kai plays first, arrival order changes but sorted display still places the three-point result first. The tuple keeps name and score together while ordering changes.
An invalid option such as 9 causes a retry in the same question. The score does not change and the question loop has not advanced to another record.
09 | TRY A BROWSER QUIZ MODEL
The model uses the same three questions and permits one completed player at a time. Its scoreboard lasts while this page is open. It does not execute Python or store results on the server.
10 | TEST THE RULES, NOT ONLY A HAPPY PATH
All correct codes 2, 1, 3 should give three points and 100.0%. All wrong accepted codes should give zero. Invalid code 9 followed by 2 for the first question should retry and then score one for that question.
Immediate quit should produce No completed players. An empty player name should retry the name prompt without adding a record. Test two players, reversed arrival order and tied scores to verify the stated display policy.
Also inspect the question data. A missing key or an answer code outside the displayed options is an authoring fault, not something the player-response loop should silently repair.
11 | PERSONALISE THE CONTENT AND FEEDBACK
Add a new correctly authored question, then rerun the score and percentage tests with the new question count. Keep each answer code aligned with the option’s display position.
Add a short explanation field to each dictionary and print it after an answer. This makes the quiz more useful for learning, while the collection keeps related question data together.
Keep a working original before extending. Do not reveal the correct answer before the player responds or reset the scoreboard for every new player.
12 | REFLECT ON DATA DESIGN
Explain which objects are lists, dictionaries, tuples and strings. Trace one nested access and one comprehension used to build allowed codes. Describe why scoreboard.append(result) changes the session list but sorted(scoreboard) returns another list.
Identify limitations: names are not unique identities, results are not saved permanently, and the bank is assumed to be non-empty and valid. These are useful next design questions rather than hidden claims of completeness.
Finish with a user guide and a test record. The next section introduces functions so you can separate repeated tasks into named reusable units.
PUT YOUR SKILLS TO WORK
Use the Section 3 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.
Section 3 challenges Python course navigation