๐Ÿ My First Python Game ยท Episode 6 of 7 ยท See All Episodes
โšก Episode 6 ยท Improver+ ยท Upgrades Episode 5

Power Up!
Breakout Part 2

Open your Episode 5 code, we are upgrading it. Falling power-ups (wide paddle, multi-ball, slow-mo), tough two-hit bricks, multiple levels and screen shake. This is how a demo becomes a game.

๐Ÿ‘ถ Ages 10+ โฑ๏ธ ~1.5 Hours ๐Ÿ Python โœ“ Free
๐ŸŽ Power-up drops โ†”๏ธ Wide paddle โšช Multi-ball ๐Ÿงฑ Tough bricks ๐Ÿ—บ๏ธ Level patterns ๐Ÿ“ณ Screen shake
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐ŸŽ
Power-Up Drops
20% of broken bricks drop a falling capsule
Active
๐ŸŽฏ
Goal for this step

Make destroyed bricks sometimes drop a capsule that falls toward the paddle.

  • 1Open your finished Episode 5 file, everything here extends it.
  • 2New list: powerups = []. When a brick dies, 20% chance to append one at the brickโ€™s position.
  • 3Each is a dict: rect + kind, picked randomly from "wide", "multi", "slow".
  • 4They fall 3 px/frame; delete off-screen ones. Draw as coloured capsules with a letter.
game.py
powerups = []
KINDS = ["wide", "multi", "slow"]

# when a brick is destroyed:
if random.random() < 0.2:
    powerups.append({
        "rect": pygame.Rect(b["rect"].centerx - 12, b["rect"].y, 24, 24),
        "kind": random.choice(KINDS)
    })

for p in powerups[:]:
    p["rect"].y += 3
    if p["rect"].top > 520:
        powerups.remove(p)
โœ๏ธ
Fill in the Blanks
+15 XP
A destroyed brick drops a capsule with probability 0.2, i.e. %. The capsule kind is picked with random.(KINDS).
๐Ÿง 
Knowledge Check
+15 XP
Why do power-ups FALL instead of activating instantly?
AFalling is easier to code
BCatching them is a choice and a risk, chase the capsule or stay safe for the ball?
CInstant effects crash Pygame
2
โ†”๏ธ
Catching: Wide Paddle
Catch a capsule, grow the paddle, temporarily
Locked
๐ŸŽฏ
Goal for this step

Implement catching and your first timed effect: a wider paddle.

  • 1Paddle-capsule collision: apply the effect, remove the capsule.
  • 2"wide": paddle width becomes 170 and a frame timer starts: wide_timer = 600 (10 seconds).
  • 3Each frame tick it down; at 0, width back to 110. Keep the paddle centred when resizing (adjust x by half the change).
  • 4Draw the timer as a shrinking bar so players see it running out.
game.py
wide_timer = 0

for p in powerups[:]:
    if p["rect"].colliderect(paddle):
        if p["kind"] == "wide":
            if wide_timer == 0:
                paddle.x -= 30        # keep centred
                paddle.width = 170
            wide_timer = 600          # 10 seconds
        powerups.remove(p)

if wide_timer > 0:
    wide_timer -= 1
    if wide_timer == 0:
        paddle.x += 30
        paddle.width = 110
โŒจ๏ธ
Code Challenge
+20 XP
Tick the effect down and end it cleanly:
game.py
if wide_timer > 0:
    wide_timer -= 
    if wide_timer == 0:
        paddle.width = 
๐Ÿ’ก Hint: Count down one per frame; when it reaches zero restore the original width.
๐Ÿง 
Knowledge Check
+15 XP
Catching a second "wide" while one is active just sets wide_timer = 600 again. What does this prevent?
ANothing, it is decoration
BThe paddle growing twice (width 230!), the guard means refresh, not stack
CSlower frame rates
3
โšช
Multi-Ball!
Refactor to a list of balls, then split into three
Locked
๐ŸŽฏ
Goal for this step

The big refactor: many balls at once, and losing a life only when the LAST drops.

  • 1Change the single ball into a list of dicts: balls = [{"rect": ..., "vx": 4, "vy": -4}].
  • 2Wrap ALL ball logic (move, walls, paddle, bricks) in for bl in balls[:]:, mostly re-indenting.
  • 3"multi" effect: for each current ball add two clones with vx -3 and +3.
  • 4A dropped ball is just removed, you lose a life only when balls is empty, then serve a fresh one.
game.py
balls = [{"rect": pygame.Rect(340, 300, 14, 14), "vx": 4, "vy": -4}]

# "multi" effect:
for bl in balls[:]:
    for new_vx in (-3, 3):
        balls.append({"rect": bl["rect"].copy(),
                      "vx": new_vx, "vy": bl["vy"]})

# dropped ball:
for bl in balls[:]:
    if bl["rect"].top > 520:
        balls.remove(bl)
if len(balls) == 0:
    lives -= 1
    balls.append({"rect": pygame.Rect(340, 300, 14, 14), "vx": 4, "vy": -4})
โœ๏ธ
Fill in the Blanks
+15 XP
After the refactor every ball is a in the balls list. A life is only lost when len(balls) == .
๐Ÿง 
Knowledge Check
+15 XP
This refactor (one thing โ†’ list of things) is the same one you did in which earlier game?
AThe maze ghost
BSpace bullets / falling gems, one object becomes a list, logic wraps in a loop
CThe fruit basket
4
๐Ÿงฑ
Tough Bricks & Slow-Mo
Two-hit silver bricks and the slow-motion catch
Locked
๐ŸŽฏ
Goal for this step

Add bricks with hit points and implement the "slow" power-up.

  • 1Give each brick "hp": 1, but the top row spawns with hp 2 (silver).
  • 2A hit now does hp -= 1; only remove at 0. Silver bricks darken when damaged, visible feedback.
  • 3"slow" effect: a slow_timer (400 frames), while active, balls move HALF speed each frame (move every other frame using a frame counter, keeping velocities intact).
  • 4Draw silver bricks grey, damaged ones darker grey.
game.py
# building the wall:
hp = 2 if row == 0 else 1
bricks.append({"rect": ..., "row": row, "hp": hp})

# on hit:
b["hp"] -= 1
if b["hp"] == 0:
    bricks.remove(b)
    ...

# slow effect (move balls every other frame):
frame += 1
move_balls = (slow_timer == 0) or (frame % 2 == 0)
โŒจ๏ธ
Code Challenge
+20 XP
Damage a brick and only destroy it at zero HP:
game.py
b["hp"] -= 
if b["hp"] == :
    bricks.(b)
๐Ÿ’ก Hint: Reduce the hit points by one; removal only happens when none remain.
๐Ÿง 
Knowledge Check
+15 XP
Why slow the balls by skipping every other frame instead of halving vx/vy?
AIt looks cooler
BInteger velocities like 3 halve badly (1.5 โ†’ rounding bugs); frame-skipping keeps exact velocities
CPygame cannot halve numbers
5
๐Ÿ—บ๏ธ
Level Patterns
Design walls with ASCII art strings
Locked
๐ŸŽฏ
Goal for this step

Define multiple levels as text patterns and progress through them.

  • 1Levels as strings: each line a row, # = brick, 2 = silver, . = gap.
  • 2A build_wall(pattern) function converts text to the bricks list.
  • 3This is data-driven design: adding a level means typing a picture, not code.
  • 4Clearing a wall loads the next pattern, +1 ball speed. After the last: the grand victory.
game.py
LEVELS = [
    [ "##########",
      "##########",
      ".########.",
      "..######..",],
    [ "2222222222",
      "#.#.#.#.#.",
      ".#.#.#.#.#",
      "##########",],
]

def build_wall(pattern):
    wall = []
    for r, line in enumerate(pattern):
        for c, ch in enumerate(line):
            if ch != ".":
                wall.append({"rect": pygame.Rect(c*68+12, r*26+50, 64, 22),
                             "row": r, "hp": 2 if ch == "2" else 1})
    return wall

bricks = build_wall(LEVELS[0])
โœ๏ธ
Fill in the Blanks
+15 XP
In the pattern strings, # is a normal brick, 2 is a two-hit brick, and . is a . The converter function is called .
๐Ÿง 
Knowledge Check
+15 XP
The biggest advantage of levels-as-text isโ€ฆ
AStrings are fast
BAnyone (even non-coders) can design a level by drawing with characters, data-driven design
CIt removes all bugs
6
๐Ÿ“ณ
Juice: Shake & Flash
Screen shake on brick breaks, flash on power-ups
Locked
๐ŸŽฏ
Goal for this step

Add game-feel effects that make every hit feel chunky.

  • 1Screen shake: draw everything onto an offscreen Surface, then blit it at a small random offset while shake > 0.
  • 2Set shake = 6 on every brick break, tick it down per frame.
  • 3Flash: on power-up catch, set flash = 8 and overlay a translucent white rect while it ticks down.
  • 4Play it. Feel the difference. That is juice, and your Breakout is DONE. ๐ŸŽ‰
game.py
world = pygame.Surface((700, 520))
shake = 0

# draw everything onto world instead of screen, then:
offset = (0, 0)
if shake > 0:
    shake -= 1
    offset = (random.randint(-4, 4), random.randint(-4, 4))
screen.blit(world, offset)
pygame.display.flip()
โŒจ๏ธ
Code Challenge
+20 XP
Blit the world with a shake offset:
game.py
if shake > 0:
    shake -= 1
    offset = (random.randint(, 4), random.randint(-4, ))
screen.blit(, offset)
๐Ÿ’ก Hint: The offset wobbles between -4 and 4 in both axes; blit the offscreen surface, not the screen.
๐Ÿง 
Knowledge Check
+15 XP
Why draw to an offscreen surface for screen shake?
AScreens cannot be moved
BShifting ONE pre-drawn surface moves everything together cheaply, no need to offset every draw call
CSurfaces are prettier
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
Power-ups, multi-ball, levels and juice, your Breakout is now a complete arcade game. One more episode: the platformer supercharge!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 7: Platformer Part 2 โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi