← Teacher guide

Catch and score

Students put the paddle and the stars together, test touching with abs() and and, see why a caught star must restart, fix a score that sets instead of adds, then add lives, a game over, a restart on Space, and a power-up of their own.

Games with microStudio · A whole game · about 55 minutes · Open the student lesson

Classroom resources

Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.

Students will be able to

Timing

Predict and Run 10 min · Investigate 10 min · Modify 20 min · Make 15 min

Before class

Read the unit primer first. This is the turtle course's Catch the turtle and Objects with turtles' falling stars, in a game engine. touches() compares the distance across and the distance up and down with abs() (a distance without its sign); both must be small, hence and. Lives change one thing in the structure: the bottom check moves out of move() into update(), so a missed star can cost a life (the first Modify's second hint says so). Calling init() again resets the game, because init() sets every variable.

Running the lesson

  1. Predict: draw the paddle at (0, -80) and the two stars on squared paper; measure across and up.
  2. Investigate: ask what or would do, then try it in microStudio.
  3. Modify: let students play each other's game before Make: is 3 lives too many?
  4. Make: ask how the game tells the power-up apart when it is caught.

The hard part (on purpose)

Lives: moving the bottom check out of move() so update() can count a miss. Talk it through before students type.

When students get stuck

What you will seeA question to askThe fix
The score never goes above 1.Does the line add one, or set the score to one?score += 1.
Lives go down even when the star is caught.Is the miss check an elif after the catch, or a separate if?Use elif star.y < -100, so a caught star is not counted as a miss.
Lives never go down.Does move() still send the star back to the top before update() can see it fall?Take the y < -100 check out of move() and do it in update().
Space does nothing after GAME OVER.Is the Space check before the return?Inside if lives == 0:, check pressed("SPACE") and call init() before return.

Answers to the checks

  1. Predict: What does it print?
    True, then False. The first star is 10 across and 4 up from the paddle: both are close enough (under 12 and under 8). The second is 30 across, too far.
  2. Close enough: Why does touches() use and, not or?
    A star must be close across and close up and down at the same time to touch
    With or, a star anywhere in the same column (close across, far above) would count as a catch. Touching needs both distances small.
  3. Why restart?: What would happen without star.restart() after score += 1?
    The star stays on the paddle and scores a point every frame
    The star would still be touching the paddle next frame, so the score would jump by 60 a second. restart() moves it back to the top.
  4. Spot the bug: The score never goes past 1, however many stars you catch. Click the line that is wrong.
    line 10
    score = 1 sets the score to 1 every time. score += 1 adds one to what it was.
  5. Where this leads: Your game is finished. What makes it better to play, not just bigger?
    Testing it with someone else and changing what is too hard, too easy or confusing
    Game designers call this playtesting: watching someone play shows what to change. Next lesson: pictures, sound and sharing your game.

Solutions

One possible solution for each task. Other correct programs are fine.

Modify › Lives

import random

class Star:
    def __init__(self, x):
        self.x = x
        self.y = 100
        self.speed = random.randint(5, 15) / 10

    def move(self):
        self.y -= self.speed

    def touches(self, x, y):
        return abs(self.x - x) < 12 and abs(self.y - y) < 8

    def restart(self):
        self.y = 100
        self.x = random.randint(-90, 90)

    def draw(self):
        screen.fillRound(self.x, self.y, 8, 8, "rgb(255,210,0)")

def pressed(key):
    return hasattr(keyboard, key) and keyboard[key] != 0

def init():
    global stars, px, score, lives
    lives = 3
    stars = []
    for i in range(4):
        stars.append(Star(-60 + i * 40))
    px = 0
    score = 0

def update():
    global px, score, lives
    if lives == 0:
        return
    if pressed("LEFT"):
        px -= 2
    if pressed("RIGHT"):
        px += 2
    for star in stars:
        star.move()
        if star.touches(px, -80):
            score += 1
            star.restart()
        elif star.y < -100:
            lives -= 1
            star.restart()

def draw():
    screen.clear("rgb(10,10,40)")
    for star in stars:
        star.draw()
    screen.fillRect(px, -80, 24, 6, "rgb(100,200,255)")
    screen.drawText("Score: " + str(score) + "   Lives: " + str(lives), 0, 88, 10, "#FFF")
    if lives == 0:
        screen.drawText("GAME OVER", 0, 0, 20, "rgb(255,100,100)")

Modify › Play again

import random

class Star:
    def __init__(self, x):
        self.x = x
        self.y = 100
        self.speed = random.randint(5, 15) / 10

    def move(self):
        self.y -= self.speed

    def touches(self, x, y):
        return abs(self.x - x) < 12 and abs(self.y - y) < 8

    def restart(self):
        self.y = 100
        self.x = random.randint(-90, 90)

    def draw(self):
        screen.fillRound(self.x, self.y, 8, 8, "rgb(255,210,0)")

def pressed(key):
    return hasattr(keyboard, key) and keyboard[key] != 0

def init():
    global stars, px, score, lives
    lives = 3
    stars = []
    for i in range(4):
        stars.append(Star(-60 + i * 40))
    px = 0
    score = 0

def update():
    global px, score, lives
    if lives == 0:
        if pressed("SPACE"):
            init()
        return
    if pressed("LEFT"):
        px -= 2
    if pressed("RIGHT"):
        px += 2
    for star in stars:
        star.move()
        if star.touches(px, -80):
            score += 1
            star.restart()
        elif star.y < -100:
            lives -= 1
            star.restart()

def draw():
    screen.clear("rgb(10,10,40)")
    for star in stars:
        star.draw()
    screen.fillRect(px, -80, 24, 6, "rgb(100,200,255)")
    screen.drawText("Score: " + str(score) + "   Lives: " + str(lives), 0, 88, 10, "#FFF")
    if lives == 0:
        screen.drawText("GAME OVER", 0, 0, 20, "rgb(255,100,100)")
        screen.drawText("Press SPACE to play again", 0, -20, 8, "#FFF")

a power-up

import random

class Star:
    def __init__(self, x):
        self.x = x
        self.y = 100
        self.speed = random.randint(5, 15) / 10

    def move(self):
        self.y -= self.speed
        if self.y < -100:
            self.restart()

    def touches(self, x, y):
        return abs(self.x - x) < 12 and abs(self.y - y) < 8

    def restart(self):
        self.y = 100
        self.x = random.randint(-90, 90)

    def draw(self):
        screen.fillRound(self.x, self.y, 8, 8, "rgb(255,210,0)")

def pressed(key):
    return hasattr(keyboard, key) and keyboard[key] != 0

def init():
    global stars, px, score
    stars = []
    for i in range(4):
        stars.append(Star(-60 + i * 40))
    gold = Star(0)
    gold.speed = 2.5
    gold.gold = True
    stars.append(gold)
    px = 0
    score = 0

def update():
    global px, score
    if pressed("LEFT"):
        px -= 2
    if pressed("RIGHT"):
        px += 2
    for star in stars:
        star.move()
        if star.touches(px, -80):
            if hasattr(star, "gold"):
                score += 5
            else:
                score += 1
            star.restart()

def draw():
    screen.clear("rgb(10,10,40)")
    for star in stars:
        star.draw()
    screen.fillRect(px, -80, 24, 6, "rgb(100,200,255)")
    screen.drawText("Score: " + str(score), 0, 88, 10, "#FFF")

Stretch and support

Stretch: Make the stars fall a little faster each time the score reaches 10, or keep a high score that survives a restart (set it outside init()).

Support: Give the lives code for update() and ask students only for the GAME OVER text in draw().

Exit ticket

Why does a caught star call restart()?

Otherwise it is still touching the paddle next frame and scores again, 60 times a second.

What each step is for

Where it leads

Next lesson: their own sprites, sound and sharing the game. Collision checks like touches() are in every engine, usually as boxes around sprites.