Week 6: graphics, objects, methods

Announcements

  • Lab 5 due Saturday, written part due in class on Friday

Two things for this week: have fun with graphics/drawing pictures, learn about objects and object-oriented programming (OOP).

Monday

objects and methods

Last time we started talking about objects and methods.

Technically, almost everything in python is an object. Most objects have both data and functions (now called methods), together in one thing (the object).

Here are some simple examples of calling methods on a python list object:

>>> L = []
>>> L.append("A")
>>> L.append("B")
>>> L.append("C")
>>> print(L)
['A', 'B', 'C']
>>> L.reverse()
>>> print(L)
['C', 'B', 'A']
>>> L.sort()
>>> print(L)
['A', 'B', 'C']
>>> L.count("A")
1
>>> L.count("Z")
0

The list methods used above are append() (add an item to the list), reverse() (reverse the order of the list), sort() (sort items from low to high), and count() (count how many are in list). There are other python list methods available.

Here are some string methods, acting on a string object:

>>> S = "swarthmore"
>>> S.upper()
'SWARTHMORE'
>>> S.count("r")
2
>>> S.index("w")
1
>>> S.isalpha()
True
>>> print(S)
swarthmore

The string methods used above are: upper() (return uppercase version of string), count() (count how many are in string), index() (show where char is in string), and isalpha() (return True if string in only alphabetic chars). There are many other python str methods available.

Note: strings are immutable, so even calling S.upper() doesn’t change what is stored in S (it just returns a new, uppercase, string). If you want to change what is stored in the variable S, you could use S = S.upper().

Using objects in your programs is just a different way to organize your data, but also provides advantages as programs and programming teams get larger and more complex (divide and conquer, code reuse, easier to read/write/debug, etc).

syntax

The syntax for calling methods on objects is object.method(), where the method may or may not require arguments.

Zelle graphics library

This week we will learn the Zelle graphics library, which is an excellent example of using objects (i.e., it’s easy to see the objects, because they are graphical objects, like circles and squares, drawn on the screen).

To use the Zelle graphics library, we need to import the classes and functions with this at the top of our programs:

from graphics import *

(and don’t name your program graphics.py, because that’s what the Zelle graphics file you are importing is named).

examples

This program creates a graphics window and draws a circle in the middle of the window:

from graphics import *

def main():

   width = 400
   height = 400
   gw = GraphWin("first graphics!", width, height)

   cp = Point(width/2, height/2)   # center point
   c = Circle(cp, width/3)
   c.draw(gw)
   c.setFill("green")

   # wait for click to end
   gw.getMouse()

main()

Things to note:

  • three objects are constructed: a GraphWin, a Point, and a Circle

  • for the circle object, the draw() and the setFill() methods are used

  • the point object is used to place the circle, but it isn’t drawn

  • the getMouse() method is used to wait for a mouse click, which ends the program

And here’s the image:

circle graphics

challenge

Look through the documentation on the Zelle graphics library and learn about all of the possible objects (Circle, Rectangle, Text, etc), as well as the methods that go with each object (draw(), move(), setFill(), etc).

See if you can draw one of these simple graphics pictures

Wednesday

Here’s the moon and stars program:

from graphics import *
from random import *

def main():
    width = 800
    height = 600

    gw = GraphWin("moon and stars", width, height)
    gw.setBackground("black")

    nstars = 60
    for i in range(nstars):
        x = randrange(width)
        y = randrange(height)
        star = Point(x,y)
        r = randrange(150,256)
        g = randrange(150,256)
        b = randrange(200,256)
        color = color_rgb(r,g,b)
        star.setFill(color)
        star.draw(gw)

    pt = Point(width/2, height/2)
    radius = width*0.1
    moon = Circle(pt, radius)
    moon.setFill("white")
    moon.draw(gw)
    shadow = moon.clone()
    shadow.setFill("black")
    shadow.move(5,10)
    shadow.draw(gw)

    gw.getMouse()

main()

Two things that are new in this program: the color_rgb() function and the clone()/move() methods.

colors

So far we’ve just been using simple colors, like "red", "yellow", and "green". The Zelle graphics library includes a color_rgb() function that allows us more control over the colors. Here’s a quick example of using color_rgb():

mygreen = color_rgb(50,205,50)
p = Point(10,20)
c = Circle(p, 50)
c.setFill(mygreen)

The color_rgb() function takes three integer arguments, one for the amount of red, one for the amount of green, and one for blue. Each amount is an integer from 0-255, where 0 means none, and 255 means "as much as possible". So, for example, white would be color_rgb(255,255,255), and black would be color_rgb(0,0,0), and the green above would have some red and blue (50), but more green (205).

clone()

When making two or more similar objects, it is often easier to make the first object, set it’s attributes (size, color, location), then clone it to make the other objects. A cloned object has all of the same attributes, but isn’t drawn yet.

In the moon and stars program above, first I make the full moon (a white circle), then clone that to make the "shadow". For the shadow circle, we just have to change it’s fill to "black" and move it a little, creating the crescent moon effect.

your turn (stoplight)

See if you can make a stoplight image, with red, yellow, and green lights!

stoplight graphic

If you have time, make the stoplight switch between red, green, and yellow. In our programs, if we want them to pause or "sleep" for a little bit, we can use the sleep() function in the time library:

>>> from time import *
>>> sleep(4)

This causes the program to sleep for 4 seconds.

So here’s the pseudo-code for the stoplight algorithm:

make the light red
sleep(5)
make the light green
sleep(5)
make the light yellow
sleep(3)
repeat the above cycle a few times...

Friday

The goal for today is to animate a circle bouncing around inside the graphics window (bouncing off the "walls").

getKey() vs checkKey()

Both of these methods look for a key press, but getKey() pauses the whole program, waiting for a key press. This is not what we want during an animation, where things are moving, and we don’t want to pause the animation. Using checkKey() we can check if a key has been pressed, and do something if it has. However, if a key has not been pressed, the program just keeps going.

Here’s a simple while loop that, if a key has been pressed, prints out what key was pressed. Also, if the 'q' key was pressed, it quits the program:

done = False
while not done:
  key = gw.checkKey()
  if key != None:
    print(key)
  if key == "q":
    done = True

We’re using the boolean flag variable done to control the loop. The flag is only set to True if the 'q' key is pressed, so that’s the only way to end the loop.

Also, checkKey() returns None if no key was pressed (it checks for key presses many times per second). So if the key variable is not None, we must have received a key press, so we print it.

If you try the above code you’ll see printouts for any keys you press. Here’s what I see in the terminal when I press these keys: z w Enter Esc Space q

z
w
Return
Escape
space
q

animation

We can use the loop above to animate an object. Inside the loop we will simply move() an object a very small amount (eg, x=1). This will move the object to the right in our graphics window:

p = Point(100,200)
c = Circle(p, 50)
c.draw(gw)

done = False
while not done:
  key = gw.checkKey()
  if key != None:
    print(key)
  if key == "q":
    done = True
  c.move(1,0)

If you run the above, the circle will quickly move off to the right. In fact, it moves so fast it may be hard to see. If we slow down the program, the animation looks better (smoother).

Python has a sleep() function in the time library we can use. Adding a sleep(0.01) call in the loop will pause the program for 0.01 seconds, making the animation much slower.

from time import sleep
...

p = Point(100,200)
c = Circle(p, 50)
c.draw(gw)

done = False
while not done:
  key = gw.checkKey()
  if key != None:
    print(key)
  if key == "q":
    done = True
  c.move(1,0)
  sleep(0.01)

bouncing off the walls

Finally, instead of just moving a circle to the right, we would like to turn it around when it gets to the right side of the graphics window. We can use the circle methods getCenter() and getRadius() to figure out the position of the circle (since it is moving all the time), then change it’s velocity when it "hits" the right wall.

from time import sleep
...

width = 600
height = 400
gw = GraphWin("animation", width, height)
p = Point(100,200)
c = Circle(p, 50)
c.draw(gw)
vx = 1       # use a variable for velocity in x direction

done = False
while not done:
  key = gw.checkKey()
  if key != None:
    print(key)
  if key == "q":
    done = True
  c.move(vx,0)
  center = c.getCenter()
  radius = c.getRadius()
  cx = center.getX()
  # decide if we should switch direction at right wall
  if cx+radius > width:
    vx = -1*vx
  sleep(0.01)

Add to the above the ability to change direction when we hit the left wall.