$ mkdir ~/cs170/labs/lab5 $ cd ~/cs170/labs/lab5
No longer an announcement! You will find the quiz on Inquire. There is a 20 minute time limit, but it should not take near that long to complete!
On Monday, you saw how we can take something we had already written, and convert it into a class structure. This is not how we typically go about writing our classes. Instead, we would probably design and write our classes from the beginning, and just use them in our code. Today, you are going to write a class from scratch, using something that is hopefully fairly familiar to you.
Today is the day that you create your first legitimate data type, and this time it is from scratch! One of the major complaints about the floating point data type is that it cannot represent every rational number. So, let's make a data type that can do that.
Create a class called Fraction
in a file called
fractions.py. Your class should be able to represent
arbitrary fractions. You should be able to add two fractions
together, and multiply fractions. You should also be able to print
fractions, so you can see the result of your work. Your code should
only work for positive fractions.
Think about what attributes you need to represent a fraction. This should be decently straight forward, but if you need a refresher on fractions take a look at this web page.
>>> fraction_1 = Fraction(1, 2) >>> fraction_2 = Fraction(3, 4) >>> result = fraction_1.add(fraction_2) >>> print(fraction_1) 1/2 >>> print(fraction_2) 3/4 >>> print(result) 5/4 >>> result = fraction_2.multiply(fraction_1) >>> print(result) 3/8
You are definitely going to need two attributes, one for the
numerator
, and another for the
denominator
.
Your operators should behave just like the operators do for other data types: They should not modify either of the current fractions. Instead, they should return a new fraction, which is the result of the operation.
__str__
method,
so you can just use the print
function to print out
your fractions.
Multiplication is easy, just multiply the numerators together, and the denominators together.
Your add function is going to have to compute a common denominator. The easiest way to accomplish this is by multiplying the two denominators together.
This means you need to multiply the numerator of the current fraction by the other fractions denominator (and vice versa) to make sure the value of each fraction does not change. Then you just add the new numerators together.
If you have written your methods correctly, you should be able
to override the built-in operators by simply changing the name
of the methods to __add__
and __mul__
:
>>> result = fraction_1 + fraction_2 >>> print(result) 5/4 >>> result = fraction_1 * fraction_2 >>> print(result) 3/8
This Fraction class is not quite complete! It at least needs subtraction and division operations. You should be able to very easily add these two operations in, if you are sneaky about it.
Keep in mind that subtraction might result in negative fractions. It is fine to have negative fractions, but to get full functionality of a Fraction object, we need to make sure we handle that gracefully. See the next challenge for how to handle that.
The way the fraction class is currently set up, you can easily get a
fraction 2/4
. This probably makes the math majors in
the room cringe, but is technically a valid fraction. It just is
not in its most reduced form.
Let's define the reduced form of a fraction as follows:
Write a function that converts the current fraction to its most reduced form. You can then fix all of your operators with one call to this new method.
If you have not completed Pong (the class version, without challenges), finish it up today. You will see the prompt below. If you have completed Pong, work on Sudoku. You can complete Pong with scores and game over conditions later.
You are going to create two different classes for the Pong
game: Ball
and Paddle
. The Ball class
should contain all of the data and functionality required for create
a ball that bounces around the screen. The Paddle class should
contain all of the data and the functionality required to create a
single paddle.
Notice that you are only writing a single Paddle class. Even though you only have one class, you can create multiple objects which are instances of the Paddle class. You should structure your Paddle class so that it can represent a Paddle at an arbitrary x location in the window.
You are also going to write all of your paddle event listeners in the Paddle class. However, you are only going to bind the once necessary for each player. This way, you can easily change which player is in control of which paddle, by simply changing which function gets bound when you run your program.
The Ball class should have an update
function. This
function should be responsible for moving the ball to its new
location, as well as handling collisions with the walls and the
paddles. You will have to call this function from your main loop.
Think about what values we have been using from the global namespace to represent these entities. You should be able to remove most of the global variables with this new setup.
Your Paddle constructor should take the starting_x location, the starting_y location, and the canvas the Paddle should be drawn on. These should be stored in class attributes , so they can be used in other methods within the Paddle class.
You will also want to store the Id that gets returned from
the create_rectangle
method as an attribute of
the class, so you can use it in your event handlers. You
probably want to do the same thing with your Ball class as well.
Your current event handlers should just need to be copied and pasted into your Paddle class. You will need to update some of the variable identifiers, so they use the class attributes instead of pulling information out of the global namespace.
When you bind the event handlers in the main portion of your program, you can simply do the following:
paddle_1 = Paddle(PADDLE_1_STARTING_X, WINDOW_HEIGHT // 2, canvas) canvas.bind("<B1-Motion>", paddle_1.handle_mouse_motion) paddle_2 = Paddle(PADDLE_2_STARTING_X, WINDOW_HEIGHT // 2, canvas) canvas.bind("<Key>", paddle_2.handle_keys)
Your Ball constructor should take the same information as the paddle. You will also want to initialize attributes for the X and Y components of the ball's velocity.
Your Ball class should also have a method
called detect_paddle_collision(self, paddle)
,
which takes a reference to a paddle object. This function
should return True if the ball's current location
collides with the specified paddle.
The Ball class' update function should take a list of paddles which it needs to interact with. This is very important for use with detect_paddle_collision.
When you have finished, create a tar file of your lab5
directory. To create a tar file, execute the following commands:
cd ~/cs170/labs tar czvf lab5.tgz lab5/
To submit your activity, go to cseval.roanoke.edu. You should
see an available assignment called Lab Assignment 5
.
Make sure you include a header listing the authors of the file.