CS 151: Lab #6

Lab Exercise 6: Object Collections

Main course page

The purpose of this lab is to get you working with the Zelle graphics objects, which are a somewhat higher level graphics package than the turtle. Using the Zelle graphics primitives you can create more complex objects. A scene is then a collection of complex objects.

Tasks

We'll continue to use the graphics.py package, but we'll start using features other than Pixmaps. You probably want to bring up the documentation in a separate tab in the browser.

  1. Create a new project6 directory for today's lab, download the graphics.py file, start up a terminal and cd to your working directory. Open a new python file and call it test.py. We'll use this file to experiment with the graphics package. Start by importing the graphics, time, and random packages.
    import time
    import random
    import graphics as gr
    

    Note how we are importing the graphics package. What that means is, instead of typing something like graphics.GraphWin, we can type gr.GraphWin. It simply assigns the symbol gr to mean the graphics package.

  2. Define a main function. Then flesh out your main function as below. The purpose of the function is to make a circle and then have it move randomly around the screen until the user clicks in the window.
    def main():
        # assign to win a GraphWin object made with title, width, and height
    
        # assign to shapes an empty list
    
        # assign to c a new circle object at (250, 250) with radius 10
        # call the draw function of the circle object stored in c
        # append the variable c to the list held in shapes
    
        # while True
            # call time.sleep with a half-second delay (0.5)
            # for each thing in shapes
                # assign to dx a random integer between -10 and 10
                # assign to dy a random integer between -10 and 10
                # call the move method of the object in thing, passing in dx and dy
    
            # if win.checkMouse() is True        
                # break out of the while loop
    
        # close the window
    

    Set up a call to your main function inside the conditional that runs only if the file is run on the command line.

    if __name__ == "__main__":
        main()
    

    Then run your test.py program. The shape should bounce around the screen in Brownian motion.

  3. Inside the inner for loop, generate a random color and set the fill color of the circle to the random color.
                # assign to r a random value in [0, 255]
                # assign to g a random value in [0, 255]
                # assign to b a random value in [0, 255]
                # assign to color the result of calling color_rgb( r, g, b)
                # call the setFill method of the object in thing, passing in color
    
  4. Now experiment with the clone method. After the for loop, but inside the while loop, clone one of the circles, with some probability, and add it to the shapes list.
        # if a call to random.random is less than 0.2
            # assign to oldthing the result of calling random.choice on shapes
            # assign to newthing the result of calling the clone method on oldthing
            # call the draw method of newthing, passing in the window object in win
            # append newthing to the shapes list
    

    Try out your test.py program again and see what happens.

  5. Make a new python file called steam.py. Import the graphics, time and random packages. Then define a function called init(x, y, scale). The function should have the following overall structure.
    def init(x, y, scale):
        # assign to shapes an empty list
    
        # assign to r a big rectangle for the steam plant (x, y) and (x+scale, y-scale*.3)
        # use the setFill method to set the rectangle light brown (176, 133, 85)
        # append r to shapes
    
        # assign to r a small rectangle for the roof (x-scale*0.01, y-scale*0.3) and (x+scale*1.01, y-scale*0.4)
        # use the setFill method to set the rectangle light grey (185, 185, 185)
        # append r to shapes
    
        # assign to r a rectangle for the smokestack (x, y) and (x+scale*0.1, y-scale)
        # use the setFill method to set the rectangle rusty (136, 96, 90)
        # append r to shapes
    
        # return shapes
    

    Note the structure of the above function. First, create an empty list. Second, build all of the individual objects needed to create the aggregate object. For each of those objects, append it to the list. Third, return the list. All of the primitive objects will be contained in the list returned by the function.

  6. Copy and paste the test function below into steam.py. The function creates a GraphWin, calls init and assigns its return value to a variable like steamplant. It then loops over the variable and calls the draw method on each primitive object. Then it calls the getMouse and close methods of your GraphWin object. Test your steam.py file.
    def test():
    
        win = gr.GraphWin( 'title', 400, 400 )
    
        steamplant = init( 100, 300, 100 )
    
        for thing in steamplant:
            thing.draw( win )
    
        win.getMouse()
        win.close()
    
    
    if __name__ == "__main__":
        test()
    
  7. If all of your complex objects have the same structure--they are all lists of primitive objects--then we should be able to write some functions to draw them, move them, undraw them, and clone them. Each of these functions just needs to loop over the elements in the object list and call the appropriate method.

    To encapsulate this functionality, create a new file aggregate.py. Import graphics and then build functions for draw, move, and undraw. The skeletons are below.

    def draw( objlist, win ):
        # for each thing in objlist
            # call the draw method on thing with win as the argument
    
    def move( objlist, dx, dy ):
        # for each item in objlist
            # call the move method on item with dx and dy as arguments
    
    def undraw( objlist ):
        # for each thing in objlist
            # call the undraw method on thing
    

    When you are done with that, go back to the steam.py file and your test function. Import aggregate, then replace the for loop that calls draw with a call to aggregate.draw( steamplant, win ). Test it out.

  8. Now we're going to animate the steam plant. In steam.py, create a function animate, with the first argument being the shape list, and the second argument being a frame number. Then we want to make puffs of smoke (circles) come out of the smokestack and start moving upwards.
    # animate the steam plant by adding smoke
    def animate( shapes, frame, win ):
    
        # assign to p1 the result of calling the getP1 method on the third item in shapes
        # assign to p2 the result of calling the getP2 method on the third item in shapes
    
        # assign to dx the width of the smokestack p2.x - p1.x
        # assign to newx the middle of the smokestack (p1.x + p2.x)*0.5
        # assign to newy a location above the top of the smokestack p2.y - dx*0.5
    
        # if frame modulo 2 is equal to 0 and the length of the shapes list is < 23
    
            # assign to c a new circle located at newx, newy with a radius of 0.4*dx
            # use the setFill method of c to color the circle grey (150, 150, 150)
            # use the draw method of c to draw the circle into the window
            # append c to the shapes list
    
        # for each item in shapes, excluding the first three: shapes[3:]
            # use the move method of item to move the smoke up (-y direction)
    
            # assign to center the center point of the circle
    
            # if center.y is less than 0 (it's off the screen)
                # assign to mx the value newx - center.x
                # assign to my the value newy - center.y
                # call the move method of item with (mx, my) as arguments
    

    Add the following code to your test function in steam.py and try it out.

        for frame in range(100):
            time.sleep( 0.25 )
            animate( steamplant, frame, win )
            if win.checkMouse():
                break
    
  9. As a final test, download and run lab6test.py . It will import your steam and aggregate modules and create a simple animated scene.

At the end of this lab, you will have the files steam.py, aggregate.py, and test.py. The aggregate.py file will be part of your project this week. The steam.py is a template for you to design other complex scene elements. If you make it more interesting, you may use steam.py as part of your project.

Once you have finished the lab, go ahead and get started on project 6.