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.
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.
Predict and Run 10 min · Investigate 10 min · Modify 20 min · Make 15 min
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.
Lives: moving the bottom check out of move() so update() can count a miss. Talk it through before students type.
| What you will see | A question to ask | The 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. |
One possible solution for each task. Other correct programs are fine.
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)")
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")
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: 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().
Why does a caught star call restart()?
Otherwise it is still touching the paddle next frame and scores again, 60 times a second.
Next lesson: their own sprites, sound and sharing the game. Collision checks like touches() are in every engine, usually as boxes around sprites.