Source: https://www.flsouthern.edu/news/recent/2020/students/computer-science-faculty-students-stay-social-thr.aspx
CSC 2280: Intro Programming
(Fall 2024)

Syllabus

LMS

Teachers

Assignments
Assignments


Other Pages

Project 12:
There are Methods in this Madness


Assigned: Fri Nov 22 2024
Due: 11:59:00 PM on Tue Dec 03 2024
Team Size: 1
Language: Python
Out of: 57 points


In this project, you'll use methods to make classes more useable.

Part 0, 3 points: Create another version of your print_pokemon function, except this time make it a method in the Pokemon class. Then you will be able to do this:

>>> gary = create_pokemon('Gyarados', 130, ['Water', 'Flying'], 83)
>>> gary.print_pokemon()
Pokemon: Gyarados (130) HP: 83/83
In order to do this, you'll need to make sure to add it indented underneath your Pokemon class. This is the right idea:
class Pokemon(object):
    '''Models a Pokemon.
    attributes: name, number, types, hit_points, max_hp.'''
    
    def print_pokemon(...
The following example won't work, because you've ended the Pokemon class before adding the method:
class Pokemon(object):
    '''Models a Pokemon.
    attributes: name, number, types, hit_points, max_hp.'''
    
#outside the class
def has_full_hp(pokemon):
    .
    .
    .
    #no longer in the Pokemon class
    def print_pokemon(...
I do recommend starting by copy-pasting your code from the non-method function into the correct place. (You need to keep the old function working!) Also, remember that you should only create each class once in your code, so make sure to use the Pokemon class you created previously! Tester.

Part 1, 3 points: That name doesn't really make much sense! It's a method in the class, so let's get rid of the second part of the function name. Change the name of the method (make a copy) to print. This will also need to be inside the class, so either above or below the prior method you wrote. This makes more sense:

>>> gary = create_pokemon('Gyarados', 130, ['Water', 'Flying'], 83)
>>> gary.print()
Pokemon: Gyarados (130) HP: 83/83
Tester.

Part 2, 3 points: Methodize has_full_hp as well.

>>> gary = create_pokemon('Gyarados', 130, ['Water', 'Flying'], 83)
>>> gary.has_full_hp()
True
Tester.

Part 3, 5 points: Keep going! Methodize spray_potion

>>> gary = create_pokemon('Gyarados', 130, ['Water', 'Flying'], 83)
>>> gary.hit_points = 35
>>> spray_potion(gary)
>>> gary.print()
Pokemon: Gyarados (130) HP: 55/83
Tester.

Part 4, 2 points: Let's turn create_pokemon into a method. Unfortunately, it doesn't quite make sense, since we can't make something into a method if it doesn't exist yet. We can do something really close, though. Let's create a method, initialize that we can use like this:

>>> vandal = Pokemon()
>>> vandal.initialize('Jigglypuff', 39, ['Normal', 'Fairy'], 44)
>>> vandal.print()
Pokemon: Jigglypuff (39) HP: 44/44
In order to make this work, you'll need to make three changes to the function:
  • Use the regular steps to make it a method, and
  • Get rid of the first line where you create a new Pokemon object. You won't be able to do that anymore.
  • Finally, this is no longer a fruitful method, but a modifier, so there's no need for the return statement at the end.
Tester.

Part 5, 3 points: Okay, python has a special method, __init__, that is supposed to do exactly what our initialize does. Make a copy of that method with this name. A method with this name is called a constructor. We need this to work when no parameters are provided (otherwise our old functions will break) so include default parameters in your header. (I used Bulbasaur.) If you don't do this, you will find that the old testers will break on your code (as will mine). Please let me know if you don't remember how to set default parameters. Tester.

Part 6, 0 points: There's a reason we changed the name. Now you should be create Pokemon on one line like this:

>>> vandal = Pokemon('Jigglypuff', 39, ['Normal', 'Fairy'], 44)
This happens because python calls special methods in indirect ways. Here, whenever you create a Pokemon object with Pokemon(...), python looks to see if a constructor exists. If so, it calls that after it creates the object.

Part 7, 4 points: Let's create a class to model coordinates on the Earth! Create a GeographicCoordinate class, with __init__ and print methods. Once you have those written, they should work like this:

>>> lh_latitude = GeographicCoordinate('N', 28, 1, 26)
>>> lh_longitude = GeographicCoordinate('W', 81, 56, 39)
>>> lh_latitude.print()
28°1'26"N
lh_longitude.print()
81°56'39"W
To get that ° character in Python, you can use: \N{DEGREE SIGN} . Tester.

Part 8, 3 points: What would be more useful than a print method? How about a fruitful method that returns a string? Then we could print that string out or use it in another function or method. This is such a useful thing that python has another special method for it: __str__. The __str__ method always takes exactly one parameter (self). It should look very similar to print, except you're building and returning a string instead of printing. Write one for the Pokemon class. Once you get it working, you can invoke it directly, or you can call it indirectly with the str function, like this:

>>> vandal = Pokemon('Jigglypuff', 39, ['Normal', 'Fairy'], 44)
>>> str(vandal)
Pokemon: Jigglypuff (39) HP: 44/44
>>> print(str(vandal))
Pokemon: Jigglypuff (39) HP: 44/44
In fact, print automatically calls str on it's parameters, so you can simplify that last line:
>>> print(vandal)
Pokemon: Jigglypuff (39) HP: 44/44
Tester.

Part 9, 3 points: Create a __str__ method for GeographicCoordinate. It should work like this:

>>> lh_latitude = GeographicCoordinate('N', 28, 1, 26)
>>> lh_longitude = GeographicCoordinate('W', 81, 56, 39)
>>> str(lh_latitude)
'28°1’26"N'
>>> print(lh_longitude)
81°56'39"W
In order to get that ° character in python, you can use \u00b0, which will show up as that character if used in a string. Tester.

Part 10, 3 points: If we want to model a single location on the globe, that will consist of two coordinates. Create a new class, Location, complete with __init__ and __str__ methods. It should work like this:

>>> lh_latitude = GeographicCoordinate('N', 28, 1, 26)
>>> lh_longitude = GeographicCoordinate('W', 81, 56, 39)
>>> lh_location = Location(lh_latitude, lh_longitude)
>>> print(lh_location)
(28°1’26"N, 81°56'39"W)
Tester.

Part 11, 3 points: Let's create a class to model a geographic feature (even though we don't have many of them in Florida). Create a Mountain class that works like this:

>>> britton_lat = GeographicCoordinate('N', 30, 59, 18)
>>> britton_long = GeographicCoordinate('W', 86, 16, 55)
>>> britton_location = Location(britton_lat, britton_long)
>>> britton_hill = Mountain('Britton Hill', 105, britton_location)
>>> print(britton_hill)
Britton Hill (105m tall @(30°59’18"N, 86°16'55"W))
Tester.

Part 12, 3 points: Let's add another method to our Mountain class. Add a method taller_than, which takes another Mountain parameter and returns whether the subject is taller than the other mountain.

>>> britton_lat = GeographicCoordinate('N', 30, 59, 18)
>>> britton_long = GeographicCoordinate('W', 86, 16, 55)
>>> britton_location = Location(britton_lat, britton_long)
>>> britton_hill = Mountain('Britton Hill', 105, britton_location)
>>> print(britton_hill)
Britton Hill (105m tall @(30°59’18"N, 86°16'55"W))
>>> mw_latitude = GeographicCoordinate('N', 44, 16, 14)
>>> mw_longitude = GeographicCoordinate('W', 71, 18, 12)
>>> mw_location = Location(mw_latitude, mw_longitude)
>>> washington = Mountain('Mount Washington', 1916, mw_location)
>>> britton_hill.taller_than(washington)
False
Tester.

Part 13, 2 points: Start a new class, Point and create the __init__ method so that you can create points like this:

>>> p = Point(4, 12)
The attributes of a Point should be named x and y. Although I won't be grading it, I recommend creating a __str__ method. Tester.

Part 14, 2 points: Add the __eq__ method to your Point class. It should correctly test whether two points are equivalent. Once this special method is written, it gets automatically invoked when testing equality with double-equals.

>>> spot = Point(3, 4)
>>> another_spot = Point(4, 3)
>>> location = Point(3, 4)
>>> spot == another_spot
False
>>> spot == location
True
Tester.

Part 15, 3 points: Write a new function (not a method, so this won't be inside the Point class) midpoint(point_a, point_b) that returns a new point directly between the two parameters.

>>> origin = Point(0, 0)
>>> out_there = Point(7, -6)
>>> middle = midpoint(origin, out_there)
>>> print('middle:', middle)
middle: Point at (3.5, -3.0)
(You'll need an __str__ method to get that last line printed out nicely like that.)Tester.

Part 16, 2 points: Add a new class, Triangle which has an __init__ that works like this:

>>> corner_a = Point(6, 6)
>>> corner_b = Point(10, 6)
>>> corner_c = Point(10, 9)
>>> triangle = Triangle(corner_a, corner_b, corner_c)
Tester.

Part 17, 3 points: Add the __eq__ method to your Triangle class. Triangles don't really are about the order their points are in when determining equivalency, and neither should you! For example:

>>> corner_a = Point(6, 6)
>>> corner_b = Point(10, 6)
>>> corner_c = Point(10, 9)
>>> t_0 = Triangle(corner_a, corner_b, corner_c)
>>> t_1 = Triangle(corner_b, corner_a, corner_c)
>>> t_0 == t_1
True
>>> 
Tester.

Part 18, 1 points: Add a new method to your Triangle class, is_right_triangle, which returns a boolean indicating whether the subject is a right triangle. To get this to work, I wrote a separate function, distance_between(point_a, point_b) and then checked the three distances in a way similar to a function we wrote long ago. (Alternatively, instead of distance_between you could write a Point method: point.distance_to(other). Which do you think is more useful?) Being able to build a solution to a complicated problem from solutions to subproblems is extremely useful in programming. Tester.

Part 19, 0 points: There's some tricky math and logic in the Triangle class, but sometimes classes are very simple. Let's create a class to represent one side of a DNA double-helix, also known as an Oligo (short for a strand of Oligodeoxyribonucleotides). Start a new class, Oligo. Your class will keep track of a single field: a string where each character is one of A, C, T, or G, the four DNA bases. Write the constructor that takes a single String parameter, and assigns it to the field. You should first check that the parameter doesn't consist of any other characters. If there's an incorrect character, assign your field to the default value, "AGTG". (Thanks to Margaret Goodman for teaching me all the appropriate terminology for this project!)

Part 20, 2 points: Add the __eq__ method to your Oligo class. Order does matter here; the pair of oligos should be equivalent only if their base strings are the same. (This one is very easy.)Tester.

Part 21, 2 points: Add a new method, get_complement, which takes no parameters and returns a new Oligo object which contains the complement sequence. The complement for each individual base is defined pairwise: A and T are complements of each other, and C and G are complements. Additionally, we assume our sequences always run from the 5' to the 3' end, so the complement needs to be in "reverse" order. For example, the complement of AGTG is CACT, not TCAC. Make sure your method returns an Oligo, not just a string!Tester.

Part 22, 2 points: Add a new method, is_complement(self, other), which returns whether itself and other are complementary Oligo sequences.

>>> oligo_a = Oligo('ATCG')
>>> oligo_b = Oligo('ATGC')
>>> oligo_c = Oligo('CGAT')
>>> oligo_a.is_complement(oligo_b)
False
>>> oligo_a.is_complement(oligo_c)
True
This should always return True:
>>> oligo = Oligo(anything)
>>> oligo.is_complement(oligo.get_complement())
Tester.

Submitting your Project:

Make sure all your code is in a file labelled with your user name (everything before the @ in your school email address) followed by _projects.py all in snake_case. (For example, my file name would be: kburke_projects.py.) It's very important to name your file correctly in order for me to grade it. Make sure your code runs, then upload it to the project on Canvas. (Don't submit code that doesn't run; you won't earn any points!) Your code should include solutions to all non-zero-point problems from Project 0 onwards. If there is already a file up on Canvas, delete that before uploading the new version or make sure your new file replaces that. (Sometimes Canvas adds a number after the file name. Don't worry about that, because it's something (freaking annoying) you don't have control of. I have a script that automatically deletes that.)