Objectives

The goal of this project is to provide you with experience writing classes with inheritance. We will also be using a dictionary to make working with collisions of physical objects easier in our simulations.

Tasks

Floors that Inherit Things

The first task is to create a new Floor class using inheritance (the parent class is Thing). The __init__() method should have five parameters.

winReference to a GraphWin object
x0Anchor point x value. The anchor is in the middle of the left side.
y0Anchor point y value. The anchor is in the middle of the left side.
lengthHorizontal-dimension distance of the floor.
thicknessVertical-dimension distance of the floor.

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 "floor", and pass in x0 and y0 for the position. (The position field will store a 2-element list [x0, y0], but you'll pass in x0 and y0 as separate arguments.) Next, assign the values of parameters length and thickness to two fields width and height. Since you will have three classes (Floor, Wall, and then Block) with objects that are visualized as rectangular in shape, it makes sense to use width as the field for the horizontal-dimension distance and height as the field for the vertical-dimension distance in all three classes. However, since those fields are not created as part of the parent Thing class, they need to be created and assigned in the subclasses.

Next, create the Rectangle object to represent the floor, just as you did for project 8. (You can reuse that code.)

Finally, add two getter methods, getHeight() and getWidth(), that return the width and height of the object.

Walls that Inherit Things

Repeat this process with the Wall class, which will look almost identical. (You can change the parameter names length and thickness if you want, but please put the horizontal and vertical dimension values in the fields named width and height. The instructions here are written as if you're using those field names.) Use "wall" as the type of the Wall class when calling the Thing.__init__() method.

Recall from project 8 that, for a Wall, the point (x0, y0) is in the middle of the bottom side of the rectangle. (This is the kind of thing for which it might be useful to add a comment in your code! When debugging, it can help to have that right in front of you on screen, rather than needing to think about it / remember it from the project instructions.)

Blocks that Inherit Things

Repeat this process with a new Block class. The parameters for the Block class should be the same as the Floor and Wall classes. When creating the visualization for the block, though, put the anchor point (x0, y0) in the center of the Block. One corner of the Block, then, should be at (x0 - width/2, y0 - height/2), and the other corner should be at (x0 + width/2, y0 + height/2).

A note about Floors, Walls, and Blocks

Note: For this project, the major functional difference between a Wall, a Floor, and a Block is in how they are treated with respect to collisions by the code in collision.py (see below). A collision with a Floor can only happen from above or below--that is, a Floor is treated as if it were a horizontal plane that fully covers the space in that dimension, so nothing could be next to it or collide with it horizontally. Similarly, a Wall is treated as if it were a vertical plane that extends the full length of the space, so collisions with a Wall could only happen from the left or right. In contrast, a Block permits collisions to happen from both the horizontal and the vertical directions. This way, the Floor and Wall classes let us handle collisions more efficiently than a Block does. Please keep this in mind when you use these objects in your own design--for example, if you want to allow a ball to collide with some rectangular object from the side (i.e., horizontally), make sure that object isn't a Floor!

collision.py

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 colliding. 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 types of the two colliding objects as the key, making use of the type field in the Thing class. For example, if we have two colliding objects, item1 and item2, we can generate a key by writing

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

This creates a tuple that has two strings in it. Based on this idea, we can generate a dictionary that contains all of the possible keys and the corresponding 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

At the bottom of the collision.py file, at the top level (that is, totally unindented), create an empty dictionary called collision_router. Then add an entry to the dictionary (as above) for each possible combination of a Ball and another type. There are four other types: Floor, Wall, Block, and Ball. There are also four collision functions, one for each combination.

Finally, create a function called collision() that takes in two Thing objects plus a time step, and then uses the collision_router dictionary to call the collision function associated with the two input objects. For now, you can assume the first argument will be a ball. However, think about how you could code this up, possibly by creating a few additional one line functions, such that it would not matter which order the objects appeared in the collision function parameters.

Pinball Table

Create a scene that is like a pinball table. (It might function more like an obstacle course, but we'll call it a pinball table!) It should have overall boundaries forming a box, then some stationary (i.e., non-moving) obstacles inside that box. Create a launch location and launch a single ball into the scene. Respawn if the ball goes out of play.

Put this in a file called pinball.py. The file should have the following structure.

# Your name / date / etc. comment block

# import statements as needed

# build the pinball table
# inputs: - x0, y0: the anchor point for the scene--the lower left corner
#             of the area of the scene
#           - width, height: the horizontal and vertical dimensions of the scene
#              (does not need to occupy the full graphics window)
def buildGame(win, x0, y0, width, height):
    # Create all of the obstacles in the scene and put them in a list
    # Draw all of the obstacles
    # You might want to set the obstacle elasticity to something larger than 1
    # return the list of obstacles

# launch the ball into the scene
# inputs dx, dy specify how much the force (forceMag) should
#          be distributed across the horizontal and vertical directions
#          (e.g., dx == 1, dy == 0 would have force be fully horizontal;
#           dx == 1, dy == 1 would have force split evenly between
#           the x and y directions)
def launch( ball, x0, y0, dx, dy, forceMag ):

    d = math.sqrt(dx*dx + dy*dy)
    dx /= d
    dy /= d

    fx = dx * forceMag
    fy = dy * forceMag

    ball.setElasticity( 0.9 )
    ball.setPosition( [x0, y0] )
    ball.setForce( [fx, fy] )

    for i in range(5):
        ball.update(0.02)

    ball.setForce( [0., 0.] )
    ball.setAcceleration( [0., -1.] )

# main code
def main():
    # Create a GraphWin
    # Assign to obstacles the result of calling buildGame
    # Make a Ball object, draw it, and launch it into the scene

    # assign to dt the value 0.01
    # assign to frame the value 0
    # while win.checkMouse is equal to None:
        # assign to collided the value False
        # for item in obstacles
            # if the result of calling the collision function with the ball and the item is True
                # set collided to True

        # if collided is False
            # call the update method of the ball with dt as the time step

        # if frame modulo 10 is equal to 0
            # call win.update()

        # increment frame

        # if the ball goes out of the window, re-launch it

    # wait for a mouse click, then close the window

if __name__ == "__main__":
    main()

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

Adding Shapes

Create a new class that makes a new shape. Specify its type as either a block or a ball, depending on which shape's outline is more appropriate, for the purpose of handling collisions. Add an example of this shape to your pinball machine (obstacle course).

For example, you could make a shape like an H and specify that it is of type 'ball' for the purposes of collisions. Then you can shoot it into your obstacle course and it should act like a ball with respect to collisions. Note, things of type 'block' can't move, but things of type 'ball' can (at least with respect to collisions).

Include a picture of your new shape as part of your wiki. If you are feeling ambitious, include the shape in your video or make a second video.

Example Extensions

The following are a few examples of potential extensions. Please do not feel that your extensions must be drawn from this list. Get creative, design extensions that interest you, and explain why they are awesome when you present the results in your writeup. Your interest in your own extensions really does make a difference.

Writeup

Make a new wiki page for your assignment. Put the label cs152f18project9 on the page.

In addition to making the wiki page writeup, put the python files you wrote on the Courses server in your private directory in a folder named project9.

Colby Wiki

In general, your writeup should follow the outline below.


© 2018 Eric Aaron (with contributions from Colby CS colleagues).