CS 152: Project 9

Title image Project 9
Fall 2019

Project 9: Pinball

The focus on this project is to provide you with experience writing classes with inheritance. We will also be using a dictionary to make working with collisions easier.


Tasks

  1. Create a Block class

    The first task is to create a Block child class using inheritance. The parent class is Thing).

    1. Define the Block __init__ method

      The __init__ method should have the following signature. You are welcome to add additional optional arguments.

      def __init__(self, win, x0=0, y0=0, width=2, height=1, color=None):

      As with the Ball class, the first action in the __init__ method is to call the parent Thing.__init__ method. Specify the type as the string "block". Any parameters you don't pass to the Thing.__init__ method should then be assigned to their proper field.

      Assign the dx parameter to the field self.dx and the dy parameter to the field dy. You can use self.width and self.height if you wish (or any other names of your choice).

      Finally, call self.reshape() and self.color() to set up the vis list and set the color.

    2. Create the reshape method

      def reshape(self):

      The reshape method should undraw the graphics objects if they are darwn. Then it should define the self.vis list of graphics objects using the current fields. Then it should draw the graphics objects if they are drawn.

      When creating the visualization for the Block, put the anchor point in the center of the block. One corner should be at (x0-width/2, y0-height/2) and the other corner should be at (x0+width/2, y0+height/2).

    3. Write getWidth and getHeight

      These two functions should return the width or height of the block.

    4. Write setWidth and setHeight

      These two functions should update the value of the width (dx) field or the height (dy) field and then call the reshape method.

  2. Make another shape class of your choice

    Choose a shape. It can be a polygon (e.g. a triangle or a pentagon) or it could be a multi-object shape like a snowman. The new shape class should inherit thing. The __init__ method should make sure all the necessary information is provided and finish by calling reshape and setColor.

    The reshape method should define the graphics objects that define the simulated object. It's ok if this class is just a single graphics object. When you define the object's visualization, define it so that the object's position corresponds to the center of the object, just like it did for the Circle and Block classes.

    If the shape is best modeled as block (for the purpose of collisions) then it will need getHeight, getWidth, setHeight, and setWidth methods. If the shape is best modeled as a circle, then it will need getRadius and setRadius methods.

    If the class has a complicated color scheme, you may need to write a setColor for the child class.

    If the class is a multi-shape object, you may need to write a setPosition function for the child class.

  3. Write a unified collision function

    The next task is to modify the collision.py file to make it simpler to call the right collision function no matter what types of objects are involved. (Note, the collision functions all work with a ball and something else. We don't yet have block-block collisions, for example.)

    The idea is to use a dictionary with the two types involved in the collision as the key, making use of the type field in the Thing class. For example, if we have two objects, item1 and item2, we can create a key by writing:

    key = (item1.getType(), item2.getType())

    The key is a tuple that has two strings in it. Then, we can generate a dictionary entry that contains all the possible keys and stores the proper function to call in each case. For example, the following creates an entry in the dictionary collision_router with the key ('ball', 'ball') and sets its value to the function reference collision_ball_ball.

    collision_router[ ('ball', 'ball') ] = collision_ball_ball

    The above line creates a new entry in the collision_router dictionary with the key ('ball', 'ball') and its value is a function reference to the collision_ball_ball function (note there are no parentheses after collision_ball_ball). This entry in the dictionary can be used to call the collision_ball_ball function using the following syntax.

    collision_router[ ('ball', 'ball') ](thing1, thing2)

    At the bottom of the collision.py file, at the top level (meaning totally unindented), create an empty dictionary called collision_router. Then add an entry to the dictionary (as above) for each possible of a ball and another type: ball, block, and the additional shape you created (which should be treated as either a ball or a block.

    Finally, create a function called collision(ball, thing) that takes in two Thing objects plus a time step and uses the collision_router dictionary to call the right function. Assume that the ball is always the first parameter and the other object (ball, block, or other) is the second parameter.

  4. Create an animated scene with obstacles

    Create a scene that is like a pinball table. It should have overall boundaries made of long thin blocks and then some obstacles inside the bounding box that are all stationary. It does not need to be completely enclosed. The default behavior should be for the program to launch a single ball into the scene and have it bounce around. But you can make it interactive, launch multiple balls, or otherwise modify the scene as you wish.

    + (more detail)

    To create the scene, the following is a suggested structure for your code to get started.

    def buildObstacles(win):
        # Create all of the obstacles in the scene and put them in a list
        # Each obstacle should be a Thing (e.g. Ball, Block, other)
        # You might want to give one or more the obstacles an elasticity > 1
        # Return the list of Things
    
    def main():
        # create a GraphWin
        # call buildObstacles, storing the return list in a variable (e.g. shapes)
        # loop over the shapes list and have each Thing call its draw method
    
        # assign to dt the value 0.02
        # assign to frame the value 0
    
        # create a ball, give it an initial velocity and acceleration, and draw it
    
        # start an infinite loop
            # if frame modulo 10 is equal to 0
                # call win.update()
    
            # using checKey, if the user typed a 'q' then break
    
            # if the ball is out of bounds, re-launch it
    
            # assign to collided the value False
            # for each item in the shapes list
                # if the result of calling the collision function with the ball and the item is True
                    # set collided to True
    
            # if collided is equal to False
                # call the update method of the ball with dt as the time step
    
            # increment frame
    
        # close the window
    
    if __name__ == "__main__":
        main()
    						

    Doing visual updates only every 10th frame will make the animation smoother. What that does is run the virtual simulation for 10 time steps and then redraw the objects in their new positions. You can adjust both the time step and how often the visualization is redrawn to change the visual behavior of the simulation.

    Create a video of your obstacle course in action and include a link to it on your wiki.

  5. Create one more shape class

    Create a new class that makes a new shape with at least two graphics objects. Consider this object to be of type ball for the purpose of collisions. Replace the ball in your obstacle scene with the new shape (so the new shape should be bouncing around).

    Include a link to a short video of your new shape bouncing around in your report.


Follow-up Questions

  1. What is inheritance?
  2. What does it mean for a child class to override a method?
  3. What is a class variable or class global variable?
  4. What is a field of an object?

Extensions

Extensions are your opportunity to customize your project, learn something else of interest to you, and improve your grade. The following are some suggested extensions, but you are free to choose your own. Be sure to describe any extensions you complete in your report.


Submit your code

Turn in your code (all files ending with .py) by putting it in a directory in the Courses server. On the Courses server, you should have access to a directory called CS152, and within that, a directory with your user name. Within this directory is a directory named Private. Files that you put into that private directory you can edit, read, and write, and the professor can edit, read, and write, but no one else. To hand in your code and other materials, create a new directory, such as project6, and then copy your code into the project directory for that week. Please submit only code that you want to be graded.

When submitting your code, double check the following.

  1. Is your name at the top of each code file?
  2. Does every function have a comment or docstring specifying what it does?
  3. Is your handin project directory inside your Private folder on Courses?


Write your project report

For CS 152 please use Google Docs to write your report. Create a new doc for each project. Start the doc with a title and your name. Attach the doc to your project on Google classroom. Make sure you click submit when you are done. The graders cannot provide feedback unless you click submit.

Your intended audience for your report is your peers not in the class. From week to week you can assume your audience has read your prior reports. Your goal should be to be able to use it to explain to friends what you accomplished in this project and to give them a sense of how you did it.

Your project report should contain the following elements.