Project 6: Animated Scene Assigned: Wed Mar 12 2014 Due: 11:59:59 PM on Tue Mar 18 2014 Team Size: 1 Language: Python Lab:
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.
-
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.
-
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.
-
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
-
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.
-
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.
-
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()
-
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.
-
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
-
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.
Assignment:
The purpose of this project is to give you experience with both top-down design and efficient coding practices that take advantage of things with similar structures.
Tasks
The result of this assignment will be similar to project 3. You'll
create a scene that is a collection of complex objects. The complex
objects will all be collections of Zelle graphics primitives and have
the same organization as the steam plant you created in lab. Each
complex object will have a function that initializes it and any
complex object that changes will have a function that animates it.
The big difference from project 3 is that your scene can be animated,
with objects moving or changing colors.
-
Think about a scene you want to create. Design the scene on paper as
a collection of complex objects like buildings, streets, stoplights,
and cars. Keep it simple. Come up with at least 2 complex objects of
your own that you want to create for your scene. At least one of them
will need to animate in some way. Animation can involve motion or
changing color.
For each of the complex objects, create a new init function in
multi_shape.py. For the steam plant we created steam_init. Follow the
same convention for your other complex objects. For a stoplight, for
example, create stoplight_init.
The init function should always take an x, y, and scale, which you
should use just as in project 3 so that the object can be placed
anywhere at any scale. The init function should return a list
of the primitive objects that make up the complex object, just like we
did with the steam_init function.
If your complex object should animate, create an animation function
for the complex object. Use the same naming scheme, putting a
_animate after the object's name. We created steam_animate for the
steam plant. You would create stoplight_animate for a stoplight.
You need to animate at least one of your new complex objects, even if
it means just changing colors. The animate function should take in at
least three parameters: the list of objects in the shape, the frame
number, and the window, just like our steam_animate function. You
can give the animate function any number of other parameters necessary
for it to work properly. For example, you may want to include the scale used to create the image, so that movement can scale with the size of the shape.
For each complex object you create, make a test function in
multi_shape.py, just like we did with test_steam. The test function
should create a window, create multiple versions of the complex
object, and then wait for a mouse click to quit. If your animate
function does something interesting, test that out as well.
Include a small picture for each complex object in your writeup.
-
Make a file scene.py and import your multi_shape package, the graphics
package, and the time package. This file should have at least a main
function (you can create other functions as you see fit to organize the
code).
The main function should initialize the complex objects in the scene and draw them. It should
then execute a loop and animate the complex objects that change (i.e. the ones with animate functions). It will be similar to the lab6test.py main function from lab. Note that in lab6test.py, we put the steam plant complex objects in a list. You may want to use lists if you have multiple copies of the same object. But if you have one copy of each type of object, then you may want one variable for each object. The important thing is to make your code both succinct and readable.
Do something creative within this framework.
Include several pictures of your scene animating in your writeup.
Note, you can use the time.sleep() function to make your animation
slow enough that you can do a screen capture on each frame. If you do
that, then you can create an animated gif on the lab machines. Use the following command inside the directory where your screen shots are saved:
$ convert -delay 60 *.png myanimation.gif
The file myanimation.gif will be an animated gif that you can put on a
web page.
Alternatively, you can also create movies of your screen on the lab machines using Quicktime.
Extensions
Each assignment will have a set of suggested extensions. The required tasks constitute about 85% of the assignment, and if you do only the required tasks and do them well you will earn a B+. To earn a higher grade, you need to undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. One complex extension, done well, or 2-3 simple extensions are typical.
-
Make additional complex objects beyond the required 2-3.
-
Set up a system that creates a scene out of complex objects based on a list that gives the
name, location, and scale of each complex object in the scene.
-
Use a Python language feature new to you (not just a new library feature or function)
Writeup and Hand-In
Before handing in your code, double check that it is well-styled:
- All variable names and function names use either camelCase or snake_case.
- All files and functions have docstrings.
- Comments are added to explain complicated code blocks.
- All variable and function names are appropriately descriptive.
- Functions are defined before any other code is added at the top level of each file.
- In a file with any functions defined, top level code is wrapped so that it won't execute if that file is imported by another.
Make a new wiki page for your assignment. Put the label cs151s14project6
on the page. Each of you needs to make your own writeup.
In addition to making the wiki page writeup, put the python files you
wrote on the Academics server in your private handin directory.
Colby Wiki
In general, your writeup should follow the outline below.
-
A brief summary of the task, in your own words. This should be no
more than a few sentences. Give the reader context and identify the
key purpose of the assignment.
-
A description of your solution to the tasks, including any images
you created. (Make sure your images are appropriately sized to fit onto the wiki page.) This should be a description of the form and
functionality of your final code. You may want to incorporate code
snippets in your description to point out relevant features. Note
any unique computational solutions you developed.
-
A description of any extensions you undertook, including images
demonstrating those extensions. If you added any modules,
functions, or other design components, note their structure and the
algorithms you used.
-
A brief description (1-3 sentences) of what you learned.
-
A list of people you worked with, including students who took the course in previous semesters, TAs, and professors. Include in this list anyone whose code you may have seen. If you didn't work with anyone, please say so.
-
Don't forget to label your writeup so that it is easy for others to find. For this lab, use cs151s14project6
|