Put the paddle and the stars together: a star that touches the paddle scores a point and starts again. Add lives, a game over and a restart, then a power-up of your own.
A game is a loop, things that move, and one question asked every frame: are these two touching?
This is the whole game: the paddle from Keys and movement, the stars from Stars as objects, and a touches() method that asks whether a star is close to the paddle. When it is, the score goes up and the star starts again from the top.
Make your prediction first. Help opens once you've locked it in.
def touches(sx, sy, x, y):
return abs(sx - x) < 12 and abs(sy - y) < 8
print(touches(10, -76, 0, -80))
print(touches(30, -80, 0, -80))
abs() gives a distance without its sign: abs(-4) is 4. Work out each distance across and up.
Click the game and catch some stars. Then put this code in a microStudio project and run it there.
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))
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):
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")
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))
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):
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")
Both distances, or either?
What if the star stayed where it is?
A score that is stuck.
The score never goes past 1, however many stars you catch. Click the line that is wrong.
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))
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):
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")
Give the player 3 lives: a star that reaches the bottom takes one. At 0 lives, stop the game and show GAME OVER.
Start lives at 3 in init(). In update(), when a star falls past the bottom (star.y < -100), take a life and restart the star.
Take the y < -100 check out of move(), so update() can count it. At the start of update(): if lives == 0: return, and draw GAME OVER when lives is 0.
After GAME OVER, pressing Space starts a new game. The game on this page has both changes.
When lives is 0, check pressed("SPACE").
Calling init() sets everything back to the start: stars, score and lives.
Add a power-up: a star that looks different and is worth more when caught (a gold star worth 5, a heart that gives back a life, a slow star that freezes the others...). It can be its own class, or a Star with an extra value. The game on this page has a fast gold star worth 5.
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))
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):
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")
# Your own power-up: a different kind of star (a GoldStar class?) worth more points.
You have now built a whole game in a real engine: a loop, input, objects, collisions and a score. Next: draw your own sprites, add sound, and share it.
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. About 55 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
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.
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().
Next lesson: their own sprites, sound and sharing the game. Collision checks like touches() are in every engine, usually as boxes around sprites.