Welcome To Florida Sign
CSC 2280: Intro Programming
(Fall 2024)

Syllabus

LMS

Teachers

Assignments
Assignments


Other Pages

Project 13:
Roll the Bones


Assigned: Wed Dec 04 2024
Due: 11:59:00 PM on Fri Dec 06 2024
Team Size: 1
Language: Python
Out of: 16 points


In this project, you'll implement classes with inheritance and polymorphism to automatically use the correct methods.

Part 0, 1 points: Create a new class, Volcano, that inherits from Mountain. The basic methods should allow the class to work like this:

>>> fuji_location = Location(GeographicCoordinate('N', 35, 21, 29), GeographicCoordinate('E', 138, 42, 52))
>>> fuji = Volcano('Mount Fuji', 3776, fuji_location)
>> print(fuji)
Mount Fuji (3776.24m tall @(35°21'29"N, 138°43'52"E))
>>> kilauea_loc = Location(GeographicCoordinate('N', 19, 25, 16), GeographicCoordinate('W', 155, 17, 12))
>>> kilauea = Volcano('Kilauea', 1247, kilauea_loc)
>>> print(kilauea)
Kilauea (1247m tall @(19°25'16"N, 155°17'12"W))
Tester.

Part 1, 1 points: Create a new class, Car, with __init__ and __str__ methods so that it works like this:

>>> lamborghini = Car(1987, 'red', 'countach')
>>> print(lamborghini)
red 1987 countach
Tester.

Part 2, 1 points: Add a method to the Car class, get_age, which takes a parameter indicating the current year and returns the number of years old the car is. For example:

>>> lamborghini = Car(1987, 'red', 'countach')
>>> lamborghini.get_age(2022)
35
Tester.

Part 3, 0 points: Create two new subclasses of Car, Coupe and Sedan, which model two- and four-door cars respectively. Add a get_num_doors method to each of them that returns the correct number of doors each of those has. You don't need to add new constructors for them, because they inherit the Car constructor:

>>> honda = Coupe(1997, 'silver', 'civic')
>>> honda.get_num_doors()
2

Part 4, 2 points: Let's add the capability of modeling cars with hatchbacks. One way to do this would be to modify the constructor to include another parameter. Instead of doing that, let's add another method to the Car class, set_has_hatch, which takes a boolean parameter indicating whether the car has a hatchback. For example:

>>> honda = Coupe(1997, 'silver', 'civic')
>>> honda.set_has_hatch(True)
You can test whether it's working by checking whatever field you're using. (I used has_hatch, but you can name yours anything.)
>>> honda = Coupe(1997, 'silver', 'civic')
>>> honda.has_hatch
False
>>> honda.set_has_hatch(True)
>>> honda.has_hatch
True
This counts as a door, so you also need to modify get_num_doors to take the hatchback into account (in both subclasses):
>>> honda = Coupe(1997, 'silver', 'civic')
>>> honda.set_has_hatch(True)
>>> honda.get_num_doors()
3
You'll also need to modify the car constructor to make the default hatchback setting (it should be false).
>>> honda = Coupe(1997, 'silver', 'civic')
>>> honda.get_num_doors()
2
>>> honda.set_has_hatch(True)
>>> honda.get_num_doors()
3
Tester.

Part 5, 0 points: Sedans can also have hatchbacks. Do the same for the Sedan class.

Part 6, 1 points: Let's return to our volcanoes for a bit. The is_active field isn't all that elegant. Let's improve our implementation by adding another layer of subclasses. This time we'll introduce two new classes that inherit from Volcano:ActiveVolcano and DormantVolcano. These should work in this way:

>>> loa_location = Location(GeographicCoordinate('N', 19, 28, 46.3), GeographicCoordinate('W', 155, 36, 9.6))
>>> loa = ActiveVolcano('Mauna Loa', 4169, loa_location)
>>> print(loa)
Mauna Loa (4169m tall @(19°28'46.3"N, 155°36'9.6"W)) is an active volcano
Tester.

Part 7, 1 points: Now do the same for DormantVolcano:

>>> kea_location = Location(GeographicCoordinate('N', 19, 49, 14), GeographicCoordinate('W', 155, 28, 5))
>>> kea = DormantVolcano('Mauna Kea', 4207, kea_location)
>>> print(kea)
Mauna Kea (4207m tall @(19°49'14"N, 155°28'5"W)) is a dormant volcano
Tester.

Part 8, 1 points: Now we can write two super simple methods, is_active, one for each of the two new classes. These will be completely easy and with them you will no longer have to include the is_active field from above. Here's how it should work:

>>> loa_location = Location(GeographicCoordinate('N', 19, 28, 46.3), GeographicCoordinate('W', 155, 36, 9.6))
>>> loa = ActiveVolcano('Mauna Loa', 4169, loa_location)
>>> kea_location = Location(GeographicCoordinate('N', 19, 49, 14), GeographicCoordinate('W', 155, 28, 5))
>>> kea = DormantVolcano('Mauna Kea', 4207, kea_location)
>>> kea.is_active()
False
>>> loa.is_active()
True
Tester.

Part 9, 2 points: Let's combine objects and lists and write a function, get_active, that takes a list of volcanoes and returns a list containing only those which are active.

>>> loa_location = Location(GeographicCoordinate('N', 19, 28, 46.3), GeographicCoordinate('W', 155, 36, 9.6))
>>> loa = ActiveVolcano('Mauna Loa', 4169, loa_location)
>>> kea_location = Location(GeographicCoordinate('N', 19, 49, 14), GeographicCoordinate('W', 155, 28, 5))
>>> kea = DormantVolcano('Mauna Kea', 4207, kea_location)
>>> fuji_location = Location(GeographicCoordinate('N', 35, 21, 29), GeographicCoordinate('E', 138, 42, 52))
>>> fuji = DormantVolcano('Mount Fuji', 3776, fuji_location)
>>> kilauea_loc = Location(GeographicCoordinate('N', 19, 25, 16), GeographicCoordinate('W', 155, 17, 12))
>>> kilauea = ActiveVolcano('Kilauea', 1247, kilauea_loc)
>>> volcanoes = [loa, kea, kilauea, fuji]
>>> active = get_active(volcanoes)
>>> for volcano in active:
        print(volcano)
Mauna Loa (4169m tall @(19°28'46.3"N, 155°36'9.6"W)) is an active volcano
Kilauea (1247m tall @(19°25'16"N, 155°17'12"W) is an active volcano
Tester.

Part 10, 1 points: Let's do another one that combines lists and objects! Write a function, highest_active, that takes a list of volcanoes and returns the highest volcano that is active. If there are no active volcanoes, it should return None.

>>> loa_location = Location(GeographicCoordinate('N', 19, 28, 46.3), GeographicCoordinate('W', 155, 36, 9.6))
>>> loa = ActiveVolcano('Mauna Loa', 4169, loa_location)
>>> kea_location = Location(GeographicCoordinate('N', 19, 49, 14), GeographicCoordinate('W', 155, 28, 5))
>>> kea = DormantVolcano('Mauna Kea', 4207, kea_location)
>>> fuji_location = Location(GeographicCoordinate('N', 35, 21, 29), GeographicCoordinate('E', 138, 42, 52))
>>> fuji = DormantVolcano('Mount Fuji', 3776, fuji_location)
>>> kilauea_loc = Location(GeographicCoordinate('N', 19, 25, 16), GeographicCoordinate('W', 155, 17, 12))
>>> kilauea = ActiveVolcano('Kilauea', 1247, kilauea_loc)
>>> volcanoes = [kea, kilauea, fuji]
>>> big = highest_active(volcanoes)
>>> print(big)
Kilauea (1247m tall @(19°25'16"N, 155°17'12"W) is an active volcano
Tester.

Part 11, 0 points: Start a new class, Die, which will model a six-sided die. It should have no attributes, but it should have an __init__ method, which will do nothing right now. (You can use the command pass to do nothing.)

Part 12, 1 points: Add a __str__ method to the class, which should just return: "A basic six-sided die.". Tester.

Part 13, 1 points: Add a method to the class, roll which returns a random integer between 1 and 6, inclusive. You might want to check out the Python random package. Tester.

Part 14, 0 points: You should now be able to do things like this:

>>> die = Die()
>>> die.roll()
3
>>> die.roll()
6
>>> die.roll()
5

Part 15, 0 points: Update the constructor to add an attribute (field): self.sides, which is set to be the list of integers from 1 to 6.

Part 16, 0 points: Modify your roll method so that it uses the random.choice method instead of just generating a random number. (Hint: use the field you just created.)

Part 17, 1 points: Create a new class, TwentySidedDie which will model a die with 20 sides. This class should be declared as a subclass of Die. In the __init__ method, give it an appropriate list for the field self.sides. Tester.

Part 18, 0 points: Add an appropriate __str__ method.

Part 19, 1 points: What happens if you try to call the roll method on a 20-sided die? Try it out! It should work because your class inherits all of the Die class's methods. If you're getting an error here, it's because you didn't tell Python about that subclass relation. Tester.

Part 20, 0 points: Physical dice are a bit limited in the number of sides they can have, but we are not as limited with our virtual dice! Create another subclass of Die, AnySidedDie which takes a parameter in the constructor describing the maximum number of sides the die has. Save that parameter as another field, and use it in the constructor to define the sides field (again, as a list). Add an appropriate __str__ method.

Part 21, 1 points: Try out the roll method here! You should be able to do something like this:

>>> die = AnySidedDie(300)
>>> die.roll()
42
>>> die.roll()
271
>>> die.roll()
215
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.)