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
, aPoint
, and aCircle
-
for the circle object, the
draw()
and thesetFill()
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:
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!
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.