← Games with microStudio

Catch and score

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.

PredictRead the code and say what it will do before you run anything. A wrong guess is fine: it shows you what to look for.

Predict

game.py
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.

What does it print?

RunRun it and compare the result with your prediction.

Run

Click the game and catch some stars. Then put this code in a microStudio project and run it there.

game.py
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")
Play itClick the game to play. Make your own on microstudio.dev.

InvestigateTrace how each line of code connects to what you see.

Investigate

game.py
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.

Why does touches() use and, not or?

What would happen without star.restart() after score += 1?

The score never goes past 1, however many stars you catch. Click the line that is wrong.

ModifyChange the code on purpose and check the result.

Modify

game.py
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.

Need a hint?

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.

Another hint

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.

Need a hint?

When lives is 0, check pressed("SPACE").

Another hint

Calling init() sets everything back to the start: stars, score and lives.

Play itClick the game to play. Make your own on microstudio.dev.

MakeBuild something new using what you have learned. Expect a few tries; that is normal.

A power-up

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.

game.py
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.
Play itClick the game to play. Make your own on microstudio.dev.

Info

Where this leads

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.

Your game is finished. What makes it better to play, not just bigger?

For teachers

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.

Students will be able to

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

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().

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.

What each step is for

Now you can
Next lesson5. Make it yoursAdd sound with audio.beep, draw your own sprites in microStudio's sprite editor, then finish a game of your own and decide how to share it safely.