The goal of this project is to provide you with more experience writing classes. Now that you have a working Ball class, it's time to create some things for the balls to bounce against. In particular, you will create a Floor class and a Wall class to represent floors and walls.


Floor Class

In your physics_objects.py file, start a new class called Floor. A Floor object needs fields for its position, length, and thickness, and also (similar to a Ball object) fields for a GraphWin object, the scale, and a list with a single Rectangle graphics object, for its visual representation (see below).

The __init__() method should take in a GraphWin object and the anchor x location (e.g., x0), anchor y location (e.g., y0), length, and thickness of the new floor. You can add other optional arguments to the __init__() method, such as a fill color. If you add an argument, make sure you give it a reasonable default value and use it in your code.

To create the visual representation of a Floor object, use a graphics Rectangle object. The first point (upper left corner) should be the anchor point, but add half the thickness to the y0 value--i.e., in physics coordinates, it should be at (x0, y0 + thickness/2.0). To create the point in the screen coordinates, multiply both coordinates by self.scale and subtract the y-position from the window's height -- x is x0*self.scale and y is self.win.getHeight() - (y0 + thickness/2)*self.scale. The second point defining the Rectangle (lower right corner) should be located at (x0 + length, y0 - thickness/2) in physics coordinates and ((x0 + length)*self.scale, self.win.getHeight() - (y0 - thickness/2)*self.scale) in screen coordinates.

The next method to write is the draw() method, which draws all of the elements of the vis list onto the window (self.win). This should be identical to the draw() method of the Ball class.

The final method is the collision() method. We're going to implement the simplest possible method of detecting collisions by assuming that the balls will not be moving fast enough to zip through the floor in a single time step. The collision() method will have self and a Ball object as its arguments. It will return True if there is a collision and False otherwise.

The concept is as follows. We can get the position of the ball (e.g., ball.getPosition() and we know the y-value of the Floor. We also know the radius of the ball (e.g. ball.getRadius()) and the thickness of the Floor. The amount of material that has to be between the center of the ball and the center-line of the Floor is the radius plus the thickness/2. Therefore, if the center of the ball and the centerline of the Floor are closer than the amount of material that has to be between them, there is a collision. As the floor is horizontal, we need only check the y coordinates to test for a collision. You may want to divide the function into two cases: one for when the position of the ball is less than the position of the wall; and a second for when the position of the ball is greater than the position of the wall. Return True if the ball and wall are too close.

Modify your fall.py code. Create a Floor object after making the Ball object, but before the main loop. Place the floor at coordinates [0, 5], with a length of 50 and a thickness of 5. Then draw the floor into the window. You should test it at this point to see if it is there.

Next, add the following to your main loop.

# if there is a collision between the floor and ball (call the floor collision method)
    # assign to v the velocity of the ball, using getVelocity
    # set the velocity of the ball to (v[0], -v[1]*0.95), using setVelocity
    # while there is still a collision
        call the ball's update method with a time step of 0.001 (small)

Test your code with the fall.py program. The ball should bounce against the floor and lose a little energy over time. If you want, you can re-spawn a ball--i.e., start a new ball at roughly the top of the screen, at a relatively random location--every 300 frames or so (or less, if that's taking too long in your simulation).

Wall Class

Create a Wall class. The only differences from the Floor class are the initial arguments to the __init__() method--win, x, y, height, thickness--and the fact that you will need to check the x-coordinate instead of the y-coordinate when testing for collisions. The __init__(), draw(), and collision() methods are all you need for now.

The visualization of the wall will also be a Rectangle. Use (x0 - thickness/2, y0) as the lower left corner, then use (x0 + thickness/2 and y0 + height) as the upper right corner (these are in physics coordinates). Be sure to transform these coordinates to screen coordinates to make the Point objects that are passed into the Rectangle. Note that the "height" referred to in the physics coordinates is the height of the wall, not the height of the window.

Test your code by modifying the fall.py code to include a wall. You will need to create a Wall object, draw it, and then check for collisions with it inside the main loop. A good place to put the wall is on the left side (position 5, 0) with a height of 50 and thickness of 5). The algorithm for handling collisions will be almost identical to the one above, except that it is the x velocity that needs to be modified. Then give your balls an initial x velocity (like -30 or -20) so they bounce into the wall.


Using your Ball, Wall, and Floor classes, write a new main program: bucket.py. Have your program make a U-shaped space (2 walls and a floor). Then have your program make five balls (put them in a list) with randomized starting locations. Give each ball a randomized starting velocity and position (don't forget to draw each ball into the window).

Your main loop should be similar to the one in fall.py. Have a while loop that uses win.checkMouse() == None as its running condition. Inside the while loop, loop over each ball in the balls list, and for each, do two things: first, update the ball; second, check for collisions with the floor or the walls. If any collisions occur, handle them as in the fall.py file. The goal is to make a simulation where multiple balls are bouncing around the U-shaped space. The balls will not interact with each other, but they should bounce off the walls and floor.

Create a variable to represent the loss factor (the constant used to reduce the velocity when a collision occurs). Then run your simulation several times with different loss factors. It might be helpful to even make this a parameter of your main function and control it with a command-line argument.

Create a short video of two different simulations with different loss factors. You can do this using the QuickTime Player, which is installed on the lab computers. The basic procedure for recording a movie on your screen is to open QuickTime Player, select "New Screen Recording", press the red square in the window that pops up, then record the screen (either click on the screen to record the entire screen or select a region of the screen to record). While the screen is being recorded, a square icon will appear in your menu bar (top right of the screen). Click that to stop the recording. You can then save the file (it has a .mov extension).

If you want to record just the graphics window, then you may want to get that window up before starting the QuickTime movie recording. There might be a bit of a timing issue--it's possible to lose the first seconds of the simulation before recording starts--but to avoid that, you can put a call to win.getMouse() before your animation begins. Then, your window will appear, you can set up the recording, and then you can click in the window for the animation to begin.

Example Extensions


Make a new wiki page for your assignment. Put the label cs152f18project8 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 project8.

Colby Wiki

In general, your writeup should follow the outline below.

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