Dojo Defender: Session 1

Your spaceship has landed in dark space. Time to bring it to life. In this session you add a ship you can steer and you make asteroids fall down. Dodge them or lose a life. How long do you survive?

What you’ll learn today

  • Using lists to keep track of multiple asteroids
  • Spawning and removing objects
  • Detecting collisions with colliderect
  • Keeping track of lives and showing an explosion effect
  • Forcing game over when your lives run out

Step 0: Setup

  1. Open Thonny.
  2. Download the Dojo Defender starter, unpack the ZIP and open main.py.
  3. Click Run. You see a dark blue screen with your spaceship at the bottom. You can already move it with the arrow keys.
  4. Problems? Take a look at Installing Thonny.

Step 1: Let asteroids spawn

A game without danger is boring. Add a list in which you keep track of all the asteroids, and a function that creates new asteroids:

# at the top, after WIDTH and HEIGHT:
asteroids = []

def spawn_asteroid():
    rock = Actor('asteroid_big')
    rock.x = random.randint(30, WIDTH - 30)
    rock.y = -30
    rock.speed = random.uniform(3, 5)
    asteroids.append(rock)

You also need import random at the top of the file. Add that line.

Call the function in update() with a counter so that an asteroid does not appear every single frame:

# at the top, outside the functions:
timer = 0

def update():
    global timer
    timer += 1
    if timer % 60 == 0:   # every second (60 frames)
        spawn_asteroid()

Put this before the movement code of the ship.

Step 2: ✅ Basic: dropping and drawing asteroids

Asteroids have to fall down and disappear when they are off the screen:

def update():
    global timer
    timer += 1
    if timer % 60 == 0:
        spawn_asteroid()

    for rock in asteroids[:]:      # [:] = copy so you can remove safely
        rock.y += rock.speed
        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)

Don’t forget to draw the asteroids in draw():

def draw():
    screen.fill((10, 10, 30))
    for rock in asteroids:
        rock.draw()
    ship.draw()

Step 3: ⭐ Stretch: collision + lives

When an asteroid hits your ship, you lose a life. Add lives and game_over at the top:

lives = 3
game_over = False

Add collision detection in the for loop:

    for rock in asteroids[:]:
        rock.y += rock.speed

        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)
        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over
            lives -= 1
            if lives <= 0:
                game_over = True

Show the lives and the score in draw():

    screen.draw.text(f"Score: {score}", (10, 10), color="white", fontsize=24)
    screen.draw.text(f"Lives: {lives}", (10, 40), color="red", fontsize=24)

Stop the game when game_over is True:

def update():
    if game_over:
        return
    ...

Show a game-over screen in draw():

    if game_over:
        screen.draw.text(
            "GAME OVER",
            center=(WIDTH / 2, HEIGHT / 2),
            color="red",
            fontsize=60,
        )
        screen.draw.text(
            f"Final Score: {score}",
            center=(WIDTH / 2, HEIGHT / 2 + 60),
            color="white",
            fontsize=30,
        )

Step 4: 🔥 Expert: explosions + invisibility

When an asteroid hits you, you want to see an explosion instead of the ship just disappearing. Use the four explosion frames (explosion_1 to explosion_4):

explosion = Actor('explosion_1')
explosion_active = False
explosion_frame = 0
explosion_timer = 0

On a collision: hide the ship, start the explosion:

        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over, explosion_active, explosion_frame, explosion_timer
            lives -= 1

            explosion.pos = (ship.x, ship.y)
            explosion.image = 'explosion_1'
            explosion_frame = 1
            explosion_timer = 5
            explosion_active = True
            ship.visible = False

            if lives <= 0:
                game_over = True

Play the explosion in update():

    if explosion_active:
        explosion_timer -= 1
        if explosion_timer <= 0:
            explosion_frame += 1
            if explosion_frame > 4:
                explosion_active = False
                ship.visible = True
                ship.pos = (WIDTH / 2, HEIGHT - 50)
            else:
                explosion.image = f'explosion_{explosion_frame}'
                explosion_timer = 5

Then add an invisibility period so that you don’t get hit again straight away:

invincible = False
invincible_timer = 0

In update():

    if invincible:
        invincible_timer -= 1
        if invincible_timer <= 0:
            invincible = False
            ship.visible = True

Adjust the collision check:

        elif rock.colliderect(ship) and not invincible and ship.visible:

Draw the explosion in draw():

    if explosion_active:
        explosion.draw()

Showcase

Show your Dojo Defender to a coach and a buddy. How long do you survive? Do you have explosions? Does it look cool?

See you next time!

“Next time: your spaceship SHOOTS BACK. Enemies in formation, and you blast them away with lasers. This is going to be epic.”

Take it home

Try one of these extensions at home:

  1. Sound — Add sounds/explosion.wav when an asteroid explodes (sounds.explosion.play()).
  2. Harder — Make asteroids fall faster as the score rises: rock.speed = random.uniform(3 + score // 10, 5 + score // 10).
  3. Power-up — Add a rare green power-up that gives an extra life.
  4. Smaller — Use asteroid_med or asteroid_small for variation in size and speed.

Download Dojo Defender