← Teacher guide

Stars that move themselves

Students give the Star class methods (move, restart, touches), so the game loop asks each star to act, then add methods of their own and a Rock class with the same method names.

Objects with turtles · Classes · 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 15 min · Modify 10 min · Make 20 min

Before class

Read the unit primer first. A method is a function inside a class; its first parameter is self, the object it was called on. star.move() runs move with self as that star, which is why a method without self (the bug task) stops with "takes 0 positional arguments but 1 was given". Methods can call each other (self.restart()) and change the object's own values (self.points += 1). The Make task gives Rock the same method names as Star, so tick treats both the same way. That is the idea behind game engines, without the word polymorphism.

Running the lesson

  1. Predict: trace c.count on the board: 0, then 2, then 7.
  2. Run: read tick aloud. Ask how much shorter it is than the first lesson's.
  3. Investigate: the 'where does it belong' question is the main idea. Behaviour lives with the object.
  4. Bug task: Python always passes the object first. That is what self receives.
  5. Make: when students finish, ask how many lines a third kind of thing would need in tick.

The hard part (on purpose)

Remembering self in a method's def and inside it. Let the TypeError happen, then ask what Python hands to a method first.

When students get stuck

What you will seeA question to askThe fix
TypeError: ... takes 0 positional arguments but 1 was given.Does the method's def start with self?def flash(self):, then use self inside it.
NameError: name 'turtle' is not defined (or speed).Inside a method, how do you reach the star's own values?Use self. in front: self.turtle, self.speed.
The method is written but nothing happens.Where is the method called?Call it on a star where it should happen, such as star.flash() when a star is caught.

Answers to the checks

  1. Predict: What number does it print?
    7. c starts at 0; c.add(2) makes it 2 and c.add(5) makes it 7. Each call runs add with self as c.
  2. Match the calls: Match each call with what it does.
    star.move() → B. moves this star down, and sends it back to the top if it falls off; star.restart() → D. puts this star back at the top; star.touches(xcor(), ycor()) → A. gives True if this star is close to you; Star(-150 + i * 100) → C. makes a new star
  3. Where does it belong?: You want stars to fall faster as the game goes on. Where would that change go?
    Inside the Star class (in move, or a new method)
    How a star behaves belongs to the Star class. Change it there once and every star follows.
  4. Spot the bug: This stops with TypeError: faster() takes 0 positional arguments but 1 was given. Click the line that causes it.
    line 5
    Python passes the star itself as the first argument of every method, so the method needs self: def faster(self):.
  5. Where this leads: Your game now has stars and rocks. Adding a third kind of falling thing (a heart that gives a life) means…
    One new class with move, restart and touches, and a list of them
    That is why programs are built from classes: each new kind of thing is one class, and the code that uses them stays the same shape.

Solutions

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

Modify › Flash

from turtle import *
import random

speed(0)
penup()
goto(0, -180)
left(90)
score = 0

pen = Turtle()
pen.hideturtle()
pen.penup()
pen.goto(-220, 200)

class Star:
    def __init__(self, x):
        self.turtle = Turtle()
        self.turtle.shape("circle")
        self.turtle.color("gold")
        self.turtle.penup()
        self.turtle.goto(x, 200)
        self.turtle.setheading(270)
        self.speed = random.randint(3, 7)
        self.points = 1

    def move(self):
        self.turtle.forward(self.speed)
        if self.turtle.ycor() < -220:
            self.restart()

    def restart(self):
        self.turtle.goto(random.randint(-200, 200), 200)

    def touches(self, x, y):
        return self.turtle.distance(x, y) < 25

    def flash(self):
        self.turtle.color("orange")

stars = []
for i in range(4):
    stars.append(Star(-150 + i * 100))

def show_score():
    pen.clear()
    pen.write("Score: " + str(score), False, "left", ("Arial", 16, "normal"))

def go_left():
    goto(xcor() - 25, ycor())

def go_right():
    goto(xcor() + 25, ycor())

def tick():
    global score
    for star in stars:
        star.move()
        if star.touches(xcor(), ycor()):
            score += star.points
            star.flash()
            star.restart()
    show_score()
    ontimer(tick, 50)

onkey(go_left, "Left")
onkey(go_right, "Right")
listen()
show_score()
tick()
mainloop()

Modify › Level up

from turtle import *
import random

speed(0)
penup()
goto(0, -180)
left(90)
score = 0

pen = Turtle()
pen.hideturtle()
pen.penup()
pen.goto(-220, 200)

class Star:
    def __init__(self, x):
        self.turtle = Turtle()
        self.turtle.shape("circle")
        self.turtle.color("gold")
        self.turtle.penup()
        self.turtle.goto(x, 200)
        self.turtle.setheading(270)
        self.speed = random.randint(3, 7)
        self.points = 1

    def move(self):
        self.turtle.forward(self.speed)
        if self.turtle.ycor() < -220:
            self.restart()

    def restart(self):
        self.turtle.goto(random.randint(-200, 200), 200)

    def touches(self, x, y):
        return self.turtle.distance(x, y) < 25

    def level_up(self):
        self.points += 1
        self.speed += 1

stars = []
for i in range(4):
    stars.append(Star(-150 + i * 100))

def show_score():
    pen.clear()
    pen.write("Score: " + str(score), False, "left", ("Arial", 16, "normal"))

def go_left():
    goto(xcor() - 25, ycor())

def go_right():
    goto(xcor() + 25, ycor())

def tick():
    global score
    for star in stars:
        star.move()
        if star.touches(xcor(), ycor()):
            score += star.points
            star.level_up()
            star.restart()
    show_score()
    ontimer(tick, 50)

onkey(go_left, "Left")
onkey(go_right, "Right")
listen()
show_score()
tick()
mainloop()

Make rocks that move themselves

from turtle import *
import random

speed(0)
penup()
goto(0, -180)
left(90)
score = 0

pen = Turtle()
pen.hideturtle()
pen.penup()
pen.goto(-220, 200)

class Star:
    def __init__(self, x):
        self.turtle = Turtle()
        self.turtle.shape("circle")
        self.turtle.color("gold")
        self.turtle.penup()
        self.turtle.goto(x, 200)
        self.turtle.setheading(270)
        self.speed = random.randint(3, 7)
        self.points = 1

    def move(self):
        self.turtle.forward(self.speed)
        if self.turtle.ycor() < -220:
            self.restart()

    def restart(self):
        self.turtle.goto(random.randint(-200, 200), 200)

    def touches(self, x, y):
        return self.turtle.distance(x, y) < 25

stars = []
for i in range(4):
    stars.append(Star(-150 + i * 100))

class Rock:
    def __init__(self, x):
        self.turtle = Turtle()
        self.turtle.shape("square")
        self.turtle.color("gray")
        self.turtle.penup()
        self.turtle.goto(x, 200)
        self.turtle.setheading(270)
        self.speed = random.randint(4, 8)

    def move(self):
        self.turtle.forward(self.speed)
        if self.turtle.ycor() < -220:
            self.restart()

    def restart(self):
        self.turtle.goto(random.randint(-200, 200), 200)

    def touches(self, x, y):
        return self.turtle.distance(x, y) < 25

rocks = []
for i in range(2):
    rocks.append(Rock(-100 + i * 200))

def show_score():
    pen.clear()
    pen.write("Score: " + str(score), False, "left", ("Arial", 16, "normal"))

def go_left():
    goto(xcor() - 25, ycor())

def go_right():
    goto(xcor() + 25, ycor())

def tick():
    global score
    for star in stars:
        star.move()
        if star.touches(xcor(), ycor()):
            score += star.points
            star.restart()
    for rock in rocks:
        rock.move()
        if rock.touches(xcor(), ycor()):
            score -= 3
            rock.restart()
    show_score()
    ontimer(tick, 50)

onkey(go_left, "Left")
onkey(go_right, "Right")
listen()
show_score()
tick()
mainloop()

Stretch and support

Stretch: Add a Heart class that gives back a life, and a lives counter that ends the game at 0.

Support: Give students the Rock class with __init__ and move written, and ask them to add restart and touches by copying Star's.

Exit ticket

class Lamp: def __init__(self): self.on = False. Write a method switch that turns it on.

def switch(self): self.on = True (inside the class), called as lamp.switch().

What each step is for

Where it leads

This completes the course. microStudio's sprites and MakeCode Arcade's sprites are objects with their own values and behaviour. Apps with Gradio and Data detective use objects too (a Question class, a row of data).