← Objects with turtles

Stars that move themselves

Give the Star class methods (move, restart, touches), so the game loop is short and a new kind of falling thing is one class.

In a game engine you never move every enemy by hand: you tell each enemy to update itself, and it knows how.

The game still moved and checked each star itself. A method is a function inside a class: star.move() asks a star to move itself. The game loop shrinks to a few lines, and the star's rules live with the star.

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

stars.py
class Counter:
    def __init__(self):
        self.count = 0

    def add(self, n):
        self.count = self.count + n

c = Counter()
c.add(2)
c.add(5)
print(c.count)

def add(self, n): inside the class is a method. c.add(2) runs it for c: self is c and n is 2. What does it print?

What number does it print?

RunRun it and compare the result with your prediction.

Run

The same game, now with methods: move, restart and touches. Read tick first: it only asks each star to move, and asks whether it touches you.

stars.py
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))

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()
    show_score()
    ontimer(tick, 50)

onkey(go_left, "Left")
onkey(go_right, "Right")
listen()
show_score()
tick()
mainloop()
Try it yourselfYour edits are saved in this browser.

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

Investigate

stars.py
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))

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()
    show_score()
    ontimer(tick, 50)

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

What each method call does.

A change to how every star behaves.

A method that forgot something.

Match each call with what it does.

star.move()
star.restart()
star.touches(xcor(), ycor())
Star(-150 + i * 100)

You want stars to fall faster as the game goes on. Where would that change go?

This stops with TypeError: faster() takes 0 positional arguments but 1 was given. Click the line that causes it.

ModifyChange the code on purpose and check the result.

Modify

Add a method flash that turns a star orange, and call it when a star is caught.

Need a hint?

Add def flash(self): inside the class, with self.turtle.color("orange") in it.

Another hint

Call star.flash() in tick, where a star is caught.

Add a method level_up that makes a caught star worth one more point and a little faster.

Need a hint?

A method can change the star's own values: self.points += 1 and self.speed += 1.

Another hint

Call it when a star is caught.

Try it yourselfYour edits are saved in this browser.

Add a flash method that turns a star orange, and call it when the star is caught.

    Run your program. This ticks when your code does all of the above.

    Add a level_up method that makes a caught star worth more and faster.

      Run your program. This ticks when your code does all of the above.

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

      Make rocks that move themselves

      Add a Rock class with the same three methods as Star (move, restart, touches). Make two rocks, and in tick ask each rock to move and to check whether it touches you. A rock costs 3 points. How many lines in tick did the rocks need?

      Try it yourselfYour edits are saved in this browser.

      Add a Rock class with move, restart and touches methods, and two rocks that cost 3 points.

        Run your program. This ticks when your code does all of the above.

        Info

        Where this leads

        You started with 11 lines per star. Now a star is one line, Star(x), and each kind of thing knows how to move itself. Game engines such as microStudio and MakeCode Arcade work this way: sprites are objects, and you write how they behave.

        Your game now has stars and rocks. Adding a third kind of falling thing (a heart that gives a life) means…

        For teachers

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

        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.

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

        What each step is for

        Now you can
        You reached the endBack to Objects with turtles