SeeThinkExploreMarvel

UMPSA STEM LAB · Step 7 of 10

Step 7 — Wrap-Around ⭐

Replace wall death with Pac-Man style wrap-around using the modulo operator %. Also refactor direction handling to use a Python dictionary.

% moduloDictionarydict lookupwrap-around
Step 7 / 10

The Modulo Operator %

Modulo returns the remainder after division. It's how you make numbers "wrap around" — the same maths that makes clocks work.

🔄 Pattern Recognition — the modulo pattern

Modulo is one of the most powerful patterns in programming. It appears in: clock arithmetic (12 + 5 = 5 PM, not 17), cyclic animations (frame % total_frames), hash tables (key % bucket_count), audio beats (frame % tempo == 0), and every game with a "circular" world. Once you see this pattern, you'll recognise it everywhere.

Interactive modulo calculator

Drag the slider to change the numerator. Watch it cycle:

0
0 % 20 =
0
The result always stays in 0–19 (grid columns)

How it replaces the wall collision

Step 4: die at wall

if hx < 0 or hx >= GRID_COLS:
    running = False   # game over!

Step 7: wrap at wall

hx = hx % GRID_COLS   # wrap 20→0, -1→19
hy = hy % GRID_ROWS   # wrap 15→0, -1→14

That's it! Two lines replace the four-condition wall check. When hx reaches 20, 20 % 20 = 0 — the snake teleports to the left edge.

Edge cases — negative numbers

Python's modulo handles negative numbers elegantly. -1 % 20 = 19 — the snake going left past column 0 reappears at column 19.

ExpressionResultSnake effect
20 % 200Right wall → left edge
-1 % 2019Left wall → right edge
15 % 150Bottom wall → top edge
-1 % 1514Top wall → bottom edge
Python vs C/Java: Python's % always returns a non-negative result when the divisor is positive. C and Java can return negative results (-1 % 20 = -1 in C). Python's behaviour is mathematically consistent and better for wrap-around.

Refactoring with a Dictionary

Replace the chain of if/elif for direction with a Python dictionary. Lookup is O(1) and the code is shorter and easier to extend.

💡 Abstraction — data vs logic

The if/elif chain is logic. The dictionary is data. Whenever you find yourself writing a long chain of if/elif that maps one value to another, consider a dictionary instead. It separates the "what" (the mapping) from the "how" (the lookup), making the code easier to change (add "WARP" direction? Just add one entry).

Direction dictionary

"RIGHT"→(+1, 0)
"LEFT"→(-1, 0)
"UP"→(0, -1)
"DOWN"→(0, +1)

Before: if/elif chain

if   direction=="RIGHT": hx+=1
elif direction=="LEFT":  hx-=1
elif direction=="UP":    hy-=1
elif direction=="DOWN":  hy+=1

After: dictionary lookup

MOVES = {"RIGHT":(1,0),"LEFT":(-1,0),
         "UP":(0,-1),"DOWN":(0,1)}
dx, dy = MOVES[direction]
hx += dx; hy += dy

🌒 Watch the Snake Wrap Around — Interactive Grid

Click the arrow buttons to move the snake head. When it reaches an edge, hx = hx % GRID_COLS fires and it reappears on the other side. Watch the calculation update live.

Press an arrow to start moving. Move toward an edge to see wrap-around!

The Complete Step 7 Changes

# At the top (constants section) — add direction map
MOVES = {
    "RIGHT": ( 1,  0),
    "LEFT":  (-1,  0),
    "UP":    ( 0, -1),
    "DOWN":  ( 0,  1)
}

# In update_game() — replace if/elif chain with:
hx, hy = snake[0]
dx, dy = MOVES[direction]
hx += dx; hy += dy

# Wrap-around (replaces wall-death check):
hx = hx % GRID_COLS   # wraps 0..GRID_COLS-1
hy = hy % GRID_ROWS   # wraps 0..GRID_ROWS-1

new_head = (hx, hy)
# (No more "if hx < 0 or..." — wall is now a portal)

# Self-collision check stays the same:
if new_head in snake[1:]:
    return snake, food, score, False

Try It — Exercises

1

Modulo in Python shell 🔄 Pattern

Open Python: try 20 % 20, -1 % 20, 21 % 20, 40 % 20. What pattern do you notice? Then try 7 % 3 and 10 % 4.

2

Add walls as optional 📋 Algorithm

Add a global WRAP_MODE = True. In update_game, use if WRAP_MODE: hx%=GRID_COLS; else: if hx<0 ...: running=False. Toggle it with the W key in the event loop.

3

Add a fifth direction 💡 Abstraction

Add "STOP": (0, 0) to MOVES. Bind it to the space bar. The snake pauses! Notice you only needed to add one dictionary entry — no if/elif change needed.

⚠️ Common Mistakes — Spot These Before You Start

These are the errors beginners make most often in Step 7. Read them now so you can recognise them in your own code.

✗ Mistake 1: Using == instead of % for wrap-around
if x == GRID_COLS:
    x = 0
if x == -1:
    x = GRID_COLS - 1
🐞 This works at speed 1, but if the snake ever moves more than 1 cell per frame (faster speeds), it can jump past the boundary entirely without being wrapped.
x = x % GRID_COLS    # handles any value in one line
✗ Mistake 2: Wrapping only x but not y
x = x % GRID_COLS   # x wraps ✓
# y not wrapped — game still ends at top/bottom!
🐞 Horizontal wrap works, but the snake dies when it hits the top or bottom edge. Both axes need wrapping.
x = x % GRID_COLS
y = y % GRID_ROWS    # must wrap both axes
⚡ Mistake 3: Confusion about negative modulo
# Student expects -1 % 20 to give -1 (like in C/Java)
⚡ Python's % always returns a non-negative result when the divisor is positive. So -1 % 20 gives 19, not -1. This is actually what we want — the snake appears at the right edge when leaving the left edge!
print(-1 % 20)   # prints 19 — Python wraps correctly for us

✅ Quick Check — Are You Ready?

Three questions — not graded. They help you spot gaps before the activities.

1 What does the expression x % GRID_COLS do when x = GRID_COLS (i.e., the snake exits the right edge)?
2 Why is % better than an if/elif chain for wrap-around?
3 What does -1 % 20 return in Python?

You've covered all the concepts for Step 7. Time to apply them.

Start Activities → Tier 1 → 2 → 3 → 4
Previous← Functions