Lab Exercise 9: Dictionaries and Inheritance
This project continues our work with classes within the domain of physical simulations.
This is the second part of a multi-part project where we will look at increasingly complex physical simulations. This week we add more sophisticated collisions with more types of objects.
If you have not already done so, mount your personal space, create a new project9 folder and bring up TextWrangler and a Terminal. As with last week, you will need the Zelle graphics package file graphicsPlus.py.
The Zelle documentation, is also available.
Dictionaries are a different method of storing information
A dictionary is like a list, but the information in a dictionary is not stored in sequential order. Instead, each value stored in a dictionary has a unique key. In a dictionary, the key plays the same role as the index in a list. The difference is that a key can be any Python type that is immutable (can't be modified). These include the types int, str, float, and tuple. Examples of valid keys are: 1, 42, '42', 'ice cream', 'chocolate', 'cookie', 1.7, and (5, 'bluberry', 'muffins').
Create a new Python file called wordmap.py. Put your name and a date at the top, then start a main function (no parameters). The goal of this program is to give the user a set of word prompts and then record their answer. To record the answer, use a dictionary with the word as the key and the response as the value.
- Print out a prompt for the user that tells them what to do.
- Create a list of about ten words, call it words.
- Create an empty dictionary, calling it mapping.
- Loop over the words. Each time through the loop, do two things. First, assign to response the return value of input, using the word as the argument to input. Second, using the word as the key, assign to your dictionary the response (the output from input).
- Finally, loop over the keys in mapping (mapping.keys()) and print out the key/response pairs to show the user their results.
Inheritance is sharing the capabilities of one class with one or more other classes.
This week we are going to use inheritance to simplify the code in the physics_objects.py file and make it easier to create new kinds of shapes. The concept is to create a parent class, Thing, that has all of the common fields and methods for physical objects. Then we create child classes, like Ball, Block, and Triangle, that make use of the parent Thing class.
- Define a parent Thing class
Create a new file, physics_objects.py, in your project 9 directory. You may want to also have a copy of your physics_objects.py file from last week open, since you will be able to re-use a good bit of the code.
Start a new class, Thing. Give it a comment that this is the parent class for simulated objects.
- Define the Thing __init__ method
The __init__ method for Thing should have two required parameters, win and the_type. The win parameter will be the GraphWin window for drawing and the_type will be the type of thing being created (e.g. a string like 'ball' or 'block').
You can add any number of default parameters for the remaining fields later on.
in the __init__ method, create the following fields and assign them either a reasonable default value or their corresponding parameter.
type A string, indicating the type of the object. mass A scalar value indicating the mass of the object. position A 2-element list indicating the current position of the object. velocity A 2-element list indicating the current velocity of the object. acceleration A 2-element list indicating acceleration acting on the object. elasticity The amount of energy retained after a collision. scale The scale factor between the simulation space and pixels. Default to 10. win A reference to the GraphWin object representing the window. vis An empty list that will hold Zelle graphics. color An (r, g, b) tuple. A good default value is black (0, 0, 0). drawn An boolean indicating if the shape has been drawn, initially False.
- Create the get methods
Create get methods for all of the above fields except vis, win, and drawn. For the most of them, you can copy and paste these from the Ball class from last week..
- Create draw and undraw methods
The draw method should loop over the self.vis list of graphics objects and draw them into the window specified by self.win. The draw method should finish by setting self.drawn to True.
The undraw method should also loop over the self.vis list of graphics objects and undraw each one. The undraw method should finish by setting self.drawn to False.
- Create the set methods
Create set methods for all of the above fields except type, position, scale, win, vis, color, and drawn. These methods will all be simple and should only update the value of the corresponding field.
- Create the setPosition method
def setPosition(self, px, py):
There is no change from last week's function.
- Calculate the difference between the new position (px, py) and the current position (i.e. self.position and self.position). Put these values in dx and dy.
- Update the ball's position fields with the new position.
- Multiply dx by self.scale and dy by -self.scale.
- In a for loop, move all of the items in the vis list by dx and dy.
- Create the setColor method
def setColor(self, c): # takes in an (r, g, b) tuple
The setColor method first set the color field to c. Then, if c is not None, it should loop over the self.vis list and set the fill color of each object to the specified color. You may want to override this method in the child classes. (Remember to use gr.color_rgb() to convert the three values in c into a Zelle color object.)
- Create an update method
The update method is identical to the prior week's method from the Ball class. It should update the simulated position and velocity using the standard equations of motion. It should also move the graphics objects the appropriate amount.
- Define a parent Thing class
- Create a new Ball class that inherits the Thing class
The Ball class will need four methods, __init__, refresh, getRadius, and setRadius.
- Define the __init__ method
The __init__ method should have win as the only required argument, but you may also want to add radius, x0, y0, and color as default arguments so you can create a Ball in a location (x0, y0), of a particular size and color.
The first step in the __init__ method is to call the Thing (parent) __init__ method. Call it by using the name of the class, Thing, followed by the name of the method, __init__, connected by a dot. The first three arguments should be self, win, and the string "ball". You may also want to pass in other parameters if you have them as optional arguments to the Thing __init__ method.
Second, create a field self.radius and assign it the radius of the Ball.
Third, call self.refresh(), which is a function that will define the vis list.
Finally, call self.setColor().
- Define a refresh method
The refresh method should implement the following algorithm.
assign to a local variable (e.g. drawn) the value of self.drawn if drawn: undraw the object (use self.undraw()) define the self.vis list of graphics objects using the current position, radius, and window if drawn: draw the object
To define the vis list you can use the same code as last week.
- Define getRadius and setRadius
The getRadius method should return the current value of the radius field.
def setRadius(self, r):
The setRadius method should assign r to the radius field and then call self.refresh().
- Define the __init__ method
When you are done with the lab exercises, you may begin the project.