UMPSA STEM LAB · Step 2 of 10
Build the heartbeat of every game: a loop that runs 8 times per second — listening for input, drawing the screen, and keeping perfect time.
You'll upgrade Step 1's 3-second window into a proper game window that stays open until the player closes it — and runs at a steady 8 frames per second.
Understand while loops, Boolean values, the event queue, and how a clock controls game speed.
This game loop is the skeleton every later step builds on. Steps 3–9 only add code inside this loop — the structure never changes.
Each badge below shows where each CT skill appears in this step.
Every game — Snake, Minecraft, any mobile app — has the same three-part cycle. Recognising this pattern is Decomposition at work.
Breaking a complex problem (run a game) into smaller, repeatable sub-problems (handle input → update state → draw screen).
This pattern appears in every real-time game ever made. You are learning the industry standard, not a classroom abstraction.
Read keyboard, mouse, window-close events every frame.
Move snake, check collisions, grow body, update score.
Fill background, draw snake segments, food, score text.
Wait so the loop always runs at the same speed.
A while loop checks a condition. If it is True, it runs the body. Then it checks again. It stops only when the condition becomes False.
while condition: # ← check condition each time
# body runs if condition is True
# (can be thousands of lines)
# code here runs AFTER the loop ends
A bool is Python's fourth data type (after int, float, str). It has exactly two possible values:
Loop keeps running
Game is active
Loop stops
Game exits
The running flag starts as True. Each frame, the loop checks it. Click to simulate what happens when the player closes the window:
sys.exit() directly when we detect QUIT, but using a running flag is cleaner: it lets the loop finish its current frame (saving data, playing a sound) before exiting gracefully.Between frames, the operating system collects everything the user did — key presses, mouse moves, window clicks — and places each event in a queue. Your code processes them all with pygame.event.get().
The event loop matches each incoming event against known patterns (event.type == pygame.QUIT). This is the same idea as spam filtering and facial recognition — check each item against a known pattern and act accordingly.
Click the event types below to simulate processing the queue:
| Constant | When it fires | Extra info on event |
|---|---|---|
pygame.QUIT | Player clicks the ✕ button | None |
pygame.KEYDOWN | Key is pressed down | event.key — which key |
pygame.KEYUP | Key is released | event.key |
pygame.MOUSEBUTTONDOWN | Mouse button clicked | event.pos, event.button |
pygame.event.get() returns a list of events. The for loop processes them one by one. Nested loops (a loop inside another loop) are a very common programming pattern — you're already using two loop types at once.FPS (frames per second) is how many times the game loop runs every second. Without a clock, the loop runs as fast as possible — thousands of times per second — making the game unplayable and burning CPU.
clock.tick(8) hides all the timing complexity — sleep calculations, OS scheduling — behind a single function call. You don't need to know how it works, only what it does: "make this frame last at least 1/8 of a second".
| SNAKE_SPEED | Frames / sec | Effect on game |
|---|---|---|
2 | 2 | Very slow — snake moves like it's underwater |
8 | 8 | Default — comfortable beginner speed |
15 | 15 | Challenge mode — harder to control |
60 | 60 | Unplayable — snake crosses the grid in 0.3 s |
Every frame, the same four things happen in the same order. Watch the loop cycle at SNAKE_SPEED = 8:
An algorithm is a precise, step-by-step procedure that solves a problem. The game loop is an algorithm — it has clear steps, a termination condition (running = False), and produces a predictable result (smooth game play) every time.
ALGORITHM GameLoop:
running ← True
WHILE running = True:
FOR each event in event_queue:
IF event = QUIT THEN running ← False
IF event = KEY_ESC THEN running ← False
END FOR
fill screen with BG colour
push frame to display
wait until 1/SNAKE_SPEED seconds have passed
END WHILE
quit pygame
This pseudocode maps exactly to the Python code you'll write. Pseudocode is a CT tool — it lets you plan logic without worrying about syntax.
Events + fill + flip + tick. Minimal — a living window with no game yet.
Arrow key events now change a direction variable inside the event loop.
After events: move snake, check food, check collision. Then draw snake + food on screen.
Functions, wrap-around, high score, OOP — all added inside this same loop skeleton.
Run this in Thonny. The dark window stays open — try pressing Escape or clicking the ✕ to close it.
# Step 2-1: Create clock and the running flag (bool: True = keep going)
clock = pygame.time.Clock()
running = True
# Step 2-2: The game loop — repeats 8 times per second while running is True
while running:
# Step 2-3: Process every event the OS collected since last frame
for event in pygame.event.get():
if event.type == pygame.QUIT: # player clicked ✕
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: # player pressed Escape
running = False
# Step 2-4: Draw — fill background then push the frame to screen
screen.fill(BG)
pygame.display.flip()
# Step 2-5: Limit loop to SNAKE_SPEED frames per second
clock.tick(SNAKE_SPEED)
# Step 2-6: Loop ended — clean up Pygame
pygame.quit()
import pygame
pygame.init()
GRID_COLS = 20
GRID_ROWS = 15
CELL_SIZE = 30
SNAKE_SPEED = 8
WHITE = (255, 255, 255)
GREEN = ( 0, 200, 80)
DKGREEN = ( 0, 160, 50)
RED = (220, 50, 50)
BG = ( 30, 30, 30)
WINDOW_W = GRID_COLS * CELL_SIZE
WINDOW_H = GRID_ROWS * CELL_SIZE
screen = pygame.display.set_mode((WINDOW_W, WINDOW_H))
pygame.display.set_caption("Snake")
# ── Step 2 additions ──────────────────────────────────────────────
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
screen.fill(BG)
pygame.display.flip()
clock.tick(SNAKE_SPEED)
pygame.quit()
clock = pygame.time.Clock()Creates a clock object that can track and control time. Like buying a stopwatch — it doesn't do anything yet, but it's ready.
running = TrueA Boolean variable (type: bool). Starts True because we want the game to run. Setting it to False anywhere in the loop causes the loop to exit after the current frame completes.
while running:Python evaluates running before each loop iteration. running is truthy, so it's equivalent to while running == True:. The indented block is the loop body.
for event in pygame.event.get():pygame.event.get() returns a list of Event objects and clears the queue. The for loop iterates each one. If no events occurred this frame, the list is empty and the for loop body doesn't run at all — that's fine.
if event.type == pygame.QUIT:Each Event has a .type attribute. pygame.QUIT is an integer constant (value 256). We use the name for readability. The == operator compares — it produces True or False.
screen.fill(BG) + pygame.display.flip()Every frame we repaint the entire screen. flip() swaps the hidden drawing buffer to the visible screen — this prevents flickering. Without it, students would see objects being drawn mid-frame.
clock.tick(SNAKE_SPEED)Tells the clock "this frame should last at least 1/8 second (125 ms)". If the frame finished in 2 ms, the clock sleeps for 123 ms. Returns the actual milliseconds the frame took — useful for advanced speed control.
Each exercise reinforces a specific concept. Predict the outcome first, then run.
Try SNAKE_SPEED = 2, then 30, then 60. How does this affect how fast the snake will move in later steps? (Hint: 1 frame = 1 cell moved.)
Add print(event.type, event) inside the for event loop. Run the program, then press keys and move the mouse over the window. What integers do you see? What event fires when you press a key?
Add code so pressing Space also exits. In Pygame, space is pygame.K_SPACE. Where in the algorithm should you add this check?
Add a variable frame_count = 0 before the loop. Inside the loop, add frame_count += 1. After the loop, add print("Total frames:", frame_count). Run for about 5 seconds, then close. How many frames ran? Does it match SNAKE_SPEED × seconds?
These are the three errors beginners make most often in Step 2. Read them now so you can recognise them in your own code.
pygame.display.flip() screen.fill(BG)
# pygame.display.flip() ← forgot this!
clock.tick(SNAKE_SPEED)
screen.fill(BG)
pygame.display.flip() # ← push the frame to screen
clock.tick(SNAKE_SPEED)
clock.tick() screen.fill(BG)
pygame.display.flip()
# clock.tick(SNAKE_SPEED) ← forgot this!
screen.fill(BG)
pygame.display.flip()
clock.tick(SNAKE_SPEED) # ← cap to 8 fps
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BG) # ← outdented! outside loop
pygame.display.flip() # ← outside loop
fill and flip run once before the loop, then nothing ever updates. The screen stays blank after the first frame.while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BG) # ← indented: inside loop ✓
pygame.display.flip() # ← inside loop ✓
Three questions — not graded. They just help you spot any gaps before the activities.
running = False inside the event loop do?clock.tick(8) do each frame?You've covered all the concepts for Step 2. Time to apply them.
Start Activities → Tier 1 → 2 → 3 → 4