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, move them, and change their color. A scene is 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 Proj6 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. You will need to make a Circle object, and can look up the syntax for it in the documentation. We'll create a list for the circle because later on we'll add more circles to the list.
    def main():
        # assign to window a GraphWin object made with title, width, and height
    
        # assign to shapes an empty list
    
        # assign to circle a new Circle object at (250, 250) with radius 10
        # invoke circle's draw method
        # append the circle to the list shapes
    
        # while True
            # call time.sleep with a half-second delay (0.5)
            # for each shape in shapes
                # assign to dx a random integer between -10 and 10
                # assign to dy a random integer between -10 and 10
                # invoke the move method of shape, passing in dx and dy
    
            # if window.checkMouse() is not None
                # 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, which another way of saying it should move randomly around the window.

  3. Next, experiment with changing object colors. Inside the inner for loop in your main program, generate a random color and set the fill color of the circle to the new 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)
                # invoke shape's setFill method, passing in color
    
  4. The next step is to explore the clone method, which duplicates an object. 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 oldShape the result of calling the function random.choice with shapes as its input
            # assign to newShape the result of invoking oldShape's clone method
            # invoke newShape's draw method, passing in window
            # append newShape to the shapes list
    

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

  5. Make a new python file called multi_shape.py. Import the graphics, time and random packages. Then define a function called steam_init(x, y, scale). The function should have the following overall structure.
    def steam_init(x, y, scale):
        """ Create the list of objects needed to draw a steam plant
        at position (x,y) with the given scale """
        
        # assign to shapes an empty list
    
        # assign to rectangle a new Rectangle to represent the steam plant (x, y) and (x+scale*100, y-scale*30)
        # use rectangle's setFill method to set it to a light grey (185, 185, 185)
        # append rectangle to shapes
    
        # assign to rectangle a new Rectangle for the roof (x-scale*1, y-scale*30) 
        # (continued) and (x+scale*101, y-scale*40)
        
        # use the setFill method to set the rectangle to a light brown (176, 133, 85)
        
        # append rectangle to shapes
    
        # assign to rectangle a new Rectangle for the smokestack (x, y) and (x+scale*10, y-scale*100)
        # use the setFill method to give rectangle a rusty color (136, 96, 90)
        # append rectangle to shapes
    
        # return the shapes list
    

    Note the structure of the above function. First, create an empty list. Second, create all of the basic shape objects needed to create the complex scene multi_shape. For each of the basic shape objects, append it to the list. Third, return the list. All of the primitive objects in your scene will be contained in the list returned by the corresponding init function.

  6. Copy and paste the test_steam function below into multi_shape.py. The function creates a GraphWin, calls steam_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 multi_shape.py file.
    def test_steam():
        """ Create a window and plot a scene with a 
        of a steam plant in it.
        """
        win = gr.GraphWin( 'title', 400, 400 )
    
        steamplant = steam_init( 100, 300, 1.0 )
    
        for thing in steamplant:
            thing.draw( win )
    
        win.getMouse()
        win.close()
    
    
    if __name__ == "__main__":
        test_steam()
    
  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 needs to loop over the elements in the object list and call the appropriate methods.

    To encapsulate this functionality, create functions at the top of your multi_shape.py file for draw, move, and undraw. The skeletons are below.

    def draw( shapeList, window ):
        """ Draws all of the shapes in shapeList in the window. """
        # for each shape in shapeList
            # invoke shape's draw method with window as the argument
    
    def move( shapeList, dx, dy ):
        """ Draw all of the shapes in shapeList by dx in the x-direction
        and dy in the y-direction """
        # for each shape in shapeList
            # invoke shape's move method with dx and dy as arguments
    
    def undraw( shapeList ):
        """ Undraw all of the shapes in shapeList. """
        # for each shape in shapeList
            # invoke shape's undraw method
    

    When you are done with that, go back to the test_steam function and replace the for loop that calls thing.draw with a call to draw( steamplant, win ). Test it.

  8. Now we're going to animate the steam plant. In multi_shape.py, create a function steam_animate immediately after the steam_init function, 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. Once a puff reaches the top of the screen, it will be "recycled" by being moved back down to the top of the smokestack.

    The steam_animate function should be called once for each frame of the animation.

    def steam_animate( shapes, frame, win ):
        """ Animate the steam plant by adding smoke.
        shapes is a list containing the graphics objects needed to draw
        the steam plant.
        frame is a number indicating which frame of the animation
        it is. win is the GraphWin object containing the scene.
        This animates by creating up to 20 steam circles. Each circle
        creeps up the screen until it gets to the top, when it is
        brought back down to the smokestack so it can be used again.
        (Wouldn't it be great if the actual steam plant could do
        that?)
        """
        
        # assign to p1 the result of calling the getP1 method on the third element in shapes
        # assign to p2 the result of calling the getP2 method on the third element in shapes
    
        # assign to dx the width of the smokestack p2.getX() - p1.getX()
        # assign to newX the middle of the smokestack (p1.getX() + p2.getX())*0.5
        # assign to newY a location above the top of the smokestack p2.getY() - dx*0.5
    
        # if the frame number is even and the length of the shapes list is less than 20
    
            # assign to circle a new Circle located at newX, newY with a radius of 0.4*dx
            # use circle's setFill method to color the circle grey (150, 150, 150)
            # invoke circle's draw method to draw it into the window
            # append circle to the shapes list
    
        # for each shape in shapes, excluding the first three: shapes[3:]
            # use the move method of shape to move the smoke up (-y direction)
    
            # assign to center the result of invoking shape's getCenter method
    
            # if center.y is less than 0 (it's off the screen)
                # assign to mx the value newX - center.getX()
                # assign to my the value newY - center.getY()
                # use shape's move method with (mx, my) as arguments
    

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

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

At the end of this lab, you will have the file multi_shape.py, which will contain your complex object functions. The steam_init and steam_animate functions are a template for you to design other complex scene elements. If you make it more interesting, you may use the steam plant as part of your project.