This page contains exercise answers and teaching guidance for
Step 2 — Loop & Events.
Not linked from the student pages.
Students: please go back and try the activities first.
STEP 2 OF 10 · FACILITATOR NOTES
What students will be able to do after Step 2:
pygame.time.Clock.tick(fps) limits frame rateKey code block: the game loop.
clock = pygame.time.Clock()
running = True # Boolean flag — controls the loop
while running: # "Keep going while running is True"
# 1. PROCESS — read all events from the queue
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False # set flag → loop exits next iteration
# 2. UPDATE — move the game state (nothing yet in Step 2)
# 3. DRAW — render to screen
screen.fill(BG)
pygame.display.flip() # swap buffers: make the new frame visible
clock.tick(8) # wait until 1/8 second has elapsed → 8 FPS
running = True at start; running = False to stop. Better than break because it is visible and testable — you can inspect running in any frame to know the loop state.
pygame collects every keyboard, mouse, and window event in a queue each frame. pygame.event.get() drains the entire queue. If you never drain it, the queue fills and pygame's event system stalls — the window becomes unresponsive.
pygame.QUITFired when the user clicks the red × button. Without this check the window appears "frozen" (unresponsive) but the loop keeps running in the background.
pygame.display.flip()Swaps the back buffer to the screen — required every frame. pygame draws to a hidden back buffer; flip() is the "reveal." Without it, every frame is drawn but never shown, so students see a blank/black window.
clock.tick(8)Sleeps until 1/8 second has elapsed since the last call; returns the actual milliseconds since last call. Essential for consistent speed across fast and slow machines.
Students complete 9 blanks inside a game-loop code snippet. Answers must match exactly (case-insensitive trimming applied). Up to 2 attempts; scores recorded per attempt in t1_b1_1…t1_b9_2.
| # | Hint shown | Correct answer | Context in code |
|---|---|---|---|
| 1 | T___ | True | running = True — Boolean flag that keeps the loop alive |
| 2 | C____ | Clock | clock = pygame.time.Clock() — creates the clock object |
| 3 | r______ | running | while running: — loop condition using the flag |
| 4 | g__ | get | pygame.event.get() — drains the event queue each frame |
| 5 | Q___ | QUIT | pygame.QUIT — event fired when user closes the window |
| 6 | F____ | False | running = False — signals the loop to stop next iteration |
| 7 | f___ | flip | pygame.display.flip() — swaps back-buffer to screen |
| 8 | t___ | tick | clock.tick(SNAKE_SPEED) — caps frame rate |
| 9 | S__________ | SNAKE_SPEED | clock.tick(SNAKE_SPEED) — uses the constant for FPS |
Recorded in Sheets: t1_attempts, t1_b1_1 … t1_b9_1 (attempt 1 blanks), t1_score_1, t1_b1_2 … t1_b9_2 (attempt 2 blanks), t1_score_2.
Three separate buggy lines are shown. Students type corrections. Answers must match exactly (trimmed). Each bug+attempt is recorded.
| Bug | Buggy line shown | Correct fix | Why |
|---|---|---|---|
| 1 | if event.type == QUIT: |
if event.type == pygame.QUIT: |
QUIT is an attribute of the pygame module — the prefix is required |
| 2 | clock.tick(8) |
clock.tick(SNAKE_SPEED) |
Hard-coded 8 should use the constant SNAKE_SPEED so FPS is controlled in one place |
| 3 | pygame.display.update() |
pygame.display.flip() |
flip() is the correct call to swap buffers for a full-screen surface; update() is for partial updates |
Recorded in Sheets: t2_attempt_num, t2_bug1_1, t2_bug2_1, t2_bug3_1 (attempt 1), t2_bug1_2, t2_bug2_2, t2_bug3_2 (attempt 2).
Students write code to add a frame counter that increments every loop iteration. A sample correct answer:
# Before the while loop:
frame_count = 0
# Inside the while loop (after clock.tick):
frame_count += 1
if frame_count % 60 == 0:
print(f"Frame: {frame_count}")
Accept variations: any working counter that increments per frame. Look for: variable initialised before the loop, incremented inside, += 1 or equivalent. The exact print format is flexible.
Recorded in Sheets: t3_attempt_num, t3_code_1, t3_code_2, t3_code_3 — raw student code per attempt for misconception analysis.
Students write code to add a second boolean flag (e.g. paused) that toggles on/off when the spacebar is pressed, and only runs game updates when not paused. A sample correct answer:
# Before loop:
paused = False
# Inside event loop:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
paused = not paused # toggle
# Wrap updates in:
if not paused:
# ... game update logic here ...
Accept variations: any working pause mechanic using a boolean flag. Key concepts to look for: toggle with not, guarded update block, KEYDOWN event detection. Students who use running = not running miss the point — that would exit the loop.
Recorded in Sheets: t4_attempt_num, t4_code_1, t4_code_2, t4_code_3 — raw student code per attempt for misconception analysis.
| Prompt | Key ideas a strong answer contains |
|---|---|
| 1. Most confusing part? | Usually "why do we need pygame.display.flip()?" — the double-buffer concept. Answer: pygame draws to a hidden buffer; flip() makes it visible. |
| 2. What is a Boolean? | A value that is only True or False. Used as a switch/flag to control program flow. |
| 3. Why a while loop instead of a for loop? | We don't know in advance how many frames the game will have — the loop runs indefinitely until the player quits. |
| 4. What does "event" mean in programming? | An action the user or system performs (click, key press, window close) that the program can respond to. |
| 5. Explain to a classmate? | Should cover: the loop runs 8 times per second; each time it checks for events, updates the game, then draws the screen. |
| What they did | What they see | What to say |
|---|---|---|
Removed pygame.display.flip() |
Black/blank window — loop runs but nothing shows | "pygame draws to a hidden buffer. flip() is the reveal — without it, every frame is drawn but never shown." |
running = true (lowercase) |
NameError: name 'true' is not defined |
"Python Boolean values are True and False with capital first letters — they're not strings." |
Put clock.tick() before the draw calls |
Works but FPS measurement is off | "clock.tick() measures time since last call. Put it last so it times the full frame including drawing." |
Forgot for event in pygame.event.get(): |
Window freezes after ~1 second (event queue fills) | "Even if you don't handle any events, you must drain the queue every frame or pygame's event system stalls." |
Used while True: with no exit |
Can't close the window gracefully | "A Boolean flag makes the exit condition visible and testable. break works but hides the intent." |
"How does Netflix keep video playing smoothly? How does your mouse pointer follow your hand in real time?" Both use an event loop — a program that loops rapidly, checking what happened and updating what to show. A game loop is the same idea.
Write Process → Update → Draw on the board. Every frame follows this pattern. Step 2 has no Update yet (the snake doesn't move) — that's intentional so students can observe the loop structure in isolation before adding complexity.
The game loop IS an algorithm. Ask: "What is the input? What is the output? What are the steps?" This surfaces computational thinking through a real artifact students have written themselves.
If students ask "why is 8 FPS so slow?" — that's the perfect moment to explain that Steps 2–4 run slowly on purpose so we can observe each frame. The snake will speed up in later steps.
Differentiation: Fast finishers can change the FPS and observe the effect, or try printing event.type for every event to see what pygame generates (mouse movements, etc.).
These 5 questions appear in the activity page after Tier 4 (post-calibration gate). Pass mark is 4 of 5 (80%). Students who fail may retry; the system records attempts and final score in Google Sheets.
| Question (displayed to student) | Correct Answer |
|---|---|
| Q1: What does clock.tick(FPS) control inside the game loop? | ✓ The frame rate — how fast the loop runs |
| Q2: Which pygame event type fires when the user closes the window? | ✓ pygame.QUIT |
| Q3: What is the game loop responsible for each iteration? | ✓ Reading input, updating state, and drawing — every frame |
| Q4: How many times does while True: run (with no break)? | ✓ Until break or sys.exit() is called |
| Q5: What value does pygame.event.get() return when no events occurred? | ✓ An empty list [] |
Recorded in Google Sheet (Act_2 tab):
concept_q1–concept_q5 (student’s 0-based answer index),
concept_score_pct, concept_passed (1 = pass, 0 = fail),
concept_attempts (retry count).
Every submission to this step writes one row to the Act_2 tab in the research spreadsheet. All 13 tabs (Student_Reg, Pre_Test, Post_Test, Act_1–Act_10) share the same student identity columns.
| Column | Description |
|---|---|
| STUDENT IDENTITY (10 fields) | |
matric | Matric / student ID |
name | Full name |
gender | Gender (Female / Male / Other) |
age | Age in years |
mykid | MyKid / IC number |
home_state | Home state in Malaysia |
class | Class or cohort code |
school_code | School or programme code |
phone | Phone number |
email | Email address |
| SUBMISSION | |
step | Step number (2) |
submitted_iso | KL timestamp (UTC+8, ISO 8601) |
| PRE-CALIBRATION | |
cal_confidence | Self-confidence before activity (1–5 scale) |
cal_predicted | Predicted score before activity (%) |
cal_reflection | Free-text: what will be hard? |
| ACTIVITY TIERS | |
t1_score_pct | Tier 1 Fill-in-Blanks score (%) |
t2_attempts | Tier 2 Debug — number of attempts |
refl2_text | Tier 2 reflection free text |
t3_attempts | Tier 3 Complete-Code attempts |
t4_attempts | Tier 4 New Task attempts |
| CONCEPT CHECK | |
concept_q1–concept_q5 | Student answer index (0-based) per question |
concept_score_pct | Percentage correct (0–100) |
concept_passed | 1 = passed (≥80%), 0 = failed |
concept_attempts | Total retries |
| REFLECTIVE JOURNAL | |
jr1–jr5 | Journal prompts 1–5 free-text responses |
| POST-CALIBRATION | |
post_confidence | Confidence rating after activity (1–5) |
post_actual | Self-reported actual score (%) |
post_r1 | Reflection: how accurate was the prediction? |
post_r2 | Reflection: what would you do differently? |
calibration_index | post_actual − cal_predicted (negative = overconfident) |