Comp 112

Lecture 11

Classes and Interfaces

2018.04.10

Drawing a dot

Suppose that we want turtle to draw a dot at a given point.

We can give the dot an initial location:

And write a function to draw the dot at the given location:

Moving the dot: first attempt

An event handler is a function that we pass to a higher-order function in turtle.

Turtle will call the event handler whenever the specified event occurs.

Let’s try to add an event handler to move the dot.

This won’t work because dot_x is a global variable, so we can’t reassign it from within a function (without some trickery).

It also won’t work to make dot_x a local variable of the move_right function, because then draw_dot won’t know about it.

But if dot_x were a value of mutable type then the move_right could change its value and draw_dot would be able to see it.

Classes as Customized Mutable Types

One solution is to introduce a new type, known as a class.

We can make a new value, called an object, of this type like this:

Each object has its own private variable table where we can store its state:

These private variables are called attributes or fields of the object.

Like dictionaries, objects of a class type are mutable: their state can change over time:

Writing an interface for Points:

Instead of manipulating the state of a Point directly, we can write an interface for it.

This is a collection of functions that lets us interact with its state in a controlled way.

Interfaces are useful so that the user doesn’t need to care about how an object’s state is represented internally.

They can also be used to prevent users from setting an object’s state to an invalid value.

Moving the dot: second attempt

Now we can use the mutable Point object, my_point, to fix our event handler for moving the dot:

Moving the Interface into the Class

Recall that we can use the convenient method syntax for functions that act on certain types.

Unfortunately, this will not work for the Point type yet.

We can fix this by making the interface functions part of the class:

Now we can call them in either function or method form:

my_point = Point ()
Point.set_x (my_point , 0)  #  ordinary function syntax
my_point.set_y (0)          #  or using method syntax
my_point.move_right ()

Initializing the state of new objects

It’s annoying that before we can use a Point object, we have to manually initialize it by calling set_x and set_y.

We can make this initialization step automatic by defining a constructor method.

A constructor is a method with the special name “__init__” (underscore, underscore, “init”, underscore, underscore).

Its first argument represents the newly-created object of its class; it can take any number of additional arguments.

The constructor automatically gets called whenever you create a new object of the class.

It’s conventional (but totally optional) to name the principal argument of a class method “self” or “this”.

Moving the dot: final version

We can streamline our dot-moving program now:

import turtle
dot_size = 10

def dot_at (x , y) :
    turtle.goto (x , y)
    turtle.dot (dot_size)

# use the constructor to create a new Point object:
my_point = Point (0 , 0)

def draw_dot (point) :
    # use interface methods to access the point's state:
    dot_at (point.get_x () , point.get_y ())

def right_event () :
    my_point.move_right ()
    turtle.clear ()     #  erase the old scene
    draw_dot (my_point)
    turtle.update ()    #  paint the new scene

def run_dot () :
    turtle.tracer (0 , 0)      #  don't render to screen unless instructed 
    turtle.hideturtle ()       #  don't show the turtle icon
    turtle.penup ()            #  default turtle state is not drawing
    turtle.onkeypress (right_event , 'Right')    #  register an event handler
    turtle.listen ()                             #  activate event handlers

Turtle Animation

Flying Balls

import time, random, turtle

dot_size = 10
max_speed = 400
timestep = 1/60
balls = []

def draw_ball (ball) :
    (x , y) = ball.get_pos ()
    turtle.goto (x , y)
    turtle.dot (dot_size)

def draw_scene () :
    turtle.clear ()     #  erase the old scene
    for ball in balls :
        draw_ball (ball)
    turtle.update ()    #  paint the new scene

def add_ball () :
    x_vel = random.randint (-max_speed , max_speed)
    y_vel = random.randint (-max_speed , max_speed)
    new_ball = Ball ((0 , 0) , (x_vel , y_vel))    #  calls the Ball constructor
    balls.append (new_ball)

def run_balls () :
    turtle.tracer (0 , 0)    #  don't render to screen unless instructed 
    turtle.hideturtle ()     #  don't show the turtle icon
    turtle.penup ()          #  default turtle state is not drawing
    turtle.onkeypress (add_ball , 'space')     #  register an event handler
    turtle.listen ()                           #  activate event handlers
    while True :                               #  the main loop
        for ball in balls :
            ball.update_pos (timestep)
        draw_scene ()
        time.sleep (timestep)

To Do This Week: