Ants Vs. SomeBees
- Due: Friday 10/23 @ 11:59pm
- Points: 25
- Download: ants.zip
To receive credit, you must solve each problem and then complete a short checkoff interview about your solution. You may use Preceptor for the interview or come to office hours to be interviewed by a member of the course staff. Staff will be doing in-person checkoffs for up to 3 business days after the assignment deadline (including any approved extension). If a checkoff happens more than 3 business days after the regular deadline, staff may ask to confirm your extended deadline via Flextensions. You will submit a Provenance bundle that includes a record of how you used VS Code, including interactions with Preceptor. Please do not use AI tools other than Preceptor for this assignment.
The bees are coming!
Create a better soldier
With inherit-ants.
Important submission note: For full credit:
- Submit with Phase 1 complete by Friday 10/16, worth 1 pt.
- Submit with Phase 1 and Phase 2 complete by Wednesday 10/21, worth 1 pt.
- Submit the complete project by Friday 10/23.
Try to attempt the problems in order, including running the tests for each problem as you progress, since some later problems will depend on earlier problems in their implementation.
You may complete the project with a partner.
You can get 1 bonus point by submitting the entire project by Thursday 10/22. You can receive extensions on the project deadline and checkpoint deadline, but not on the early deadline, unless you're a DSP student with an accommodation for assignment extensions.
Introduction
In this project, you will create a tower defense game called Ants Vs. SomeBees. You populate your colony with the bravest ants you can muster. Your ants must protect their queen from the evil bees invading your territory. Irritate the bees enough by throwing leaves at them, and they will be vanquished. Fail to pester the airborne intruders adequately, and your queen will succumb to the bees' wrath. This game is inspired by PopCap Games' Plants Vs. Zombies.
This project uses an object-oriented programming paradigm, focusing on material from Chapter 2.5 of Composing Programs. The project also involves understanding, extending, and testing a large program.
Download starter files
To get started, download all of the project code as a zip archive.
Below is a list of all the files you will see in the archive once unzipped.
For the project, you'll only be making changes to ants.py.
ants.py: The game logic of Ants Vs. SomeBeesants_plans.py: The details of each difficulty levelgui.py: A graphical user interface (GUI) for Ants Vs. SomeBeesucb.py: Utility functions for CS 61Atests: The pytest-grader test suitepyproject.tomlanduv.lock: The GUI's dependencies (Flask), installed automatically the first time you runuv run python gui.pystatic: A directory of images and files used bygui.pytemplates: A directory of HTML templates used bygui.py
You may notice some files other than the ones listed above tooβthose are needed for the test runner and portions of the GUI work. Please do not modify any files other than ants.py.
The Game
A game of Ants Vs. SomeBees consists of a series of turns. In each turn, new
bees may enter the ant colony. Then, new ants are placed to defend their colony.
Finally, all insects (ants, then bees) take individual actions. Bees either try
to move toward the end of the tunnel or sting ants in their way. Ants perform a
different action depending on their type, such as collecting more food or
throwing leaves at the bees. The game ends either when a bee reaches the end of
the tunnel (ants lose), the bees destroy a QueenAnt if it exists (ants lose),
or the entire bee fleet has been vanquished (ants win).
Core concepts
The Colony. This is where the game takes place. The colony consists of
several Places that are chained together to form tunnels through which the
bees travel. The colony also has some quantity of food which can be expended in
order to place an ant in a tunnel.
Places. A place links to another place to form a tunnel. The player can put a single ant into each place. However, there can be many bees in a single place.
The Hive. This is the place where bees originate. Bees exit the beehive to enter the ant colony.
Ants. The player places an ant into the colony by selecting from the
available ant types at the top of the screen.
Each type of ant takes a different action and requires a different
amount of colony food to place. The two most basic ant types are the HarvesterAnt,
which adds one food to the colony during each turn, and the ThrowerAnt, which
throws a leaf at a bee each turn. You will be implementing many more!
Bees. Each turn, a bee either advances to the next place in the tunnel if no ant is in its way, or it stings the ant in its way. Bees win when at least one bee reaches the end of a tunnel. In addition to the orange bees, there are yellow wasps that do double damage and a green boss bee that is quite difficult to vanquish.
Core classes
The concepts described above each have a corresponding class that encapsulates the logic for that concept. Here is a summary of the main classes involved in this game:
GameState: Represents the colony and some state information about the game, including how much food is available, how much time has elapsed, where theAntHomeBaseis, and all thePlaces in the game.Place: Represents a single place that holds insects. At most oneAntcan be in a single place, but there can be manyBees in a single place.Placeobjects have anexitto the left and anentranceto the right, which are also places. Bees travel through a tunnel by moving to eachPlace'sexitto reach the nextPlace.Hive: Represents the place whereBees start out (on the right of the tunnel).AntHomeBase: Represents the placeAnts are defending (on the left of the tunnel). IfBees get here, they win :(Insect: A base class forAntandBee. Each insect has ahealthattribute representing its remaining health and aplaceattribute representing thePlacewhere it is currently located. During each turn, every activeInsectin the game performs itsaction.Ant: Represents ants. EachAntsubclass has special attributes or a specialactionthat distinguish it from otherAnttypes. For example, aHarvesterAntgets food for the colony and aThrowerAntattacksBees. Each ant type also has afood_costattribute that indicates how much it costs to deploy one unit of that type of ant.Bee: Represents bees. Each turn, a bee moves to theexitof its currentPlaceif thePlaceis notblockedby an ant. Otherwise, it stings the ant occupying its currentPlace.
Game Layout
Below is a visualization of a GameState.
To help visualize how all the classes fit together, here is a diagram of all of the classes and their inheritance relationships.
Phase 1: Basic gameplay
In the first phase you will complete the implementation that will allow for
basic gameplay with the two basic Ants: the HarvesterAnt and the
ThrowerAnt.
Problem 1 (1 pt)
Part A: Currently, there is no cost
for placing any type of Ant, and so there is no challenge to the
game. The base class Ant has a food_cost of
zero. Override this class attribute for HarvesterAnt and ThrowerAnt
according to the "Food Cost" column in the table below.
| Class | Food Cost | Initial Health |
HarvesterAnt |
2 | 1 |
ThrowerAnt |
3 | 1 |
Part B: Now that placing an Ant costs food, we need to be able to gather more food!
To fix this issue, implement the HarvesterAnt class. A HarvesterAnt is a
type of Ant that increases gamestate.food by 1 as its action.
python3 -m pytest -k q01 --unlockUnlocking Examples
>>> from ants import *
>>> from ants_plans import *
>>> Ant.food_cost
______
>>> HarvesterAnt.food_cost
______
>>> ThrowerAnt.food_cost
______
>>> beehive = Hive(make_test_assault_plan())
>>> gamestate = GameState(beehive, ant_types(), dry_layout, (1, 9))
>>> gamestate.food = 4
>>> harvester = HarvesterAnt()
>>> gamestate.food
______
>>> harvester.action(gamestate) # Executing harvester's action method. This symbolizes the ant taking one turn.
>>> gamestate.food
______
>>> harvester.action(gamestate) # Harvester's action is executed again.
>>> gamestate.food
______
python3 -m pytest -k q01Problem 2 (1 pt)
In this problem, you'll complete Place.__init__ by adding code that tracks entrances. Right
now, a Place keeps track only of its exit. We would like a Place to keep
track of its entrance as well. A Place needs to track only one entrance.
Tracking entrances will be useful when an Ant needs to see what Bees are in
front of it in the tunnel.
However, simply passing an entrance to a Place constructor will be
problematic; we would need to have both the exit and the entrance before
creating a Place! (It's a chicken or the
egg
problem.) To get around this problem, we will keep track of entrances in the
following way instead. Place.__init__ should use this logic:
- A newly created
Placealways starts with itsentranceset toNone. - If the
Placehas anexit, then theexit'sentranceis set to thatPlace.
Hint: Remember that when the
__init__method is called, the first parameter,self, is bound to the newly created object.
Hint: Try drawing out two
Places next to each other if things get confusing. In the GUI, a place'sentranceis to its right while theexitis to its left.
Hint: Remember that
Places are not stored in a list, so you can't index into anything to access them. This means that you can't do something likecolony[index + 1]to access an adjacentPlace. How can you move from one place to another?
python3 -m pytest -k q02 --unlockUnlocking Examples
>>> from ants import *
>>> from ants_plans import *
>>> beehive, layout = Hive(make_test_assault_plan()), dry_layout
>>> dimensions = (1, 3)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> place0 = Place('place_0')
>>> print(place0.exit)
______
>>> print(place0.entrance)
______
>>> place1 = Place('place_1', place0)
>>> place1.exit is place0
______
>>> place0.entrance is place1
______
python3 -m pytest -k q02Problem 3 (2 pt)
In order for a ThrowerAnt to throw a leaf, it must know which Bee to hit.
The provided implementation of the nearest_bee method in the ThrowerAnt
class only allows ThrowerAnts to hit Bees in the same Place. Your job is to fix it
so that a ThrowerAnt will throw_at the nearest Bee in front of it that is not still in the Hive.
This includes Bees that are in the same Place as a ThrowerAnt
Hint: All
Places have anis_hiveattribute, which is set toTruewhen that place is theHiveandFalseotherwise.
Change nearest_bee so that it returns a random Bee from the nearest Place that
contains Bees. Your implementation should follow this logic:
- Start from the
ThrowerAnt's currentPlace. - If the
Placecontains one or moreBees, return a random one. Otherwise, check the nextPlacein front of it (stored as the currentPlace's entrance). - Repeat this process until a
Beeis found and returned. If noBeeis available to attack, returnNone. - Ensure that
Bees in theHiveare never returned bynearest_bee.
Hint:: The
random_beefunction provided inants.pyreturns a randomBeefrom a list ofBees orNoneif the list is empty.
Hint:: As a reminder, if there are no
Bees present at aPlace, then thebeesattribute of thatPlaceinstance will be an empty list.
Hint:: Having trouble visualizing the test cases? Try drawing them out on paper! The sample diagram provided in Game Layout shows the first test case for this problem.
python3 -m pytest -k q03 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> thrower = ThrowerAnt()
>>> ant_place = gamestate.places["tunnel_0_0"]
>>> ant_place.add_insect(thrower)
>>> near_bee = Bee(2) # A Bee with 2 health
>>> far_bee = Bee(3) # A Bee with 3 health
>>> hive_bee = Bee(4) # A Bee with 4 health
>>> hive_place = gamestate.beehive
>>> hive_place.is_hive # Check if this place is the Hive
______
>>> hive_place.add_insect(hive_bee)
>>> thrower.nearest_bee() is hive_bee # Bees in the Hive can never be attacked
______
>>> near_place = gamestate.places['tunnel_0_3']
>>> far_place = gamestate.places['tunnel_0_6']
>>> near_place.is_hive # Check if this place is the Hive
______
>>> near_place.add_insect(near_bee)
>>> far_place.add_insect(far_bee)
>>> nearest_bee = thrower.nearest_bee()
>>> thrower.place is ant_place # Don't change self.place!
______
>>> nearest_bee is far_bee
______
>>> nearest_bee is near_bee
______
>>> nearest_bee.health
______
>>> thrower.action(gamestate) # Attack! ThrowerAnts do 1 damage
>>> near_bee.health
______
>>> far_bee.health
______
>>> thrower.place is ant_place # Don't change self.place!
______
python3 -m pytest -k q03Playing the game
After implementing nearest_bee, a ThrowerAnt should be able to throw_at a
Bee in front of it that is not still in the Hive.
Now you're ready to try what you've built. To start a graphical game, run:
uv run python gui.py
The first time you run this, uv will automatically download and install the
GUI's dependencies (Flask); later runs start immediately. After you start the
graphical version, the game is (usually) available at http://127.0.0.1:31415/.
The game has several options that you will use throughout the project,
which you can view with uv run python gui.py --help.
usage: gui.py [-h] [-d DIFFICULTY] [-w] [--food FOOD]
Play Ants vs. SomeBees
optional arguments:
-h, --help show this help message and exit
-d DIFFICULTY sets difficulty of game (test/easy/normal/hard/extra-hard)
-w, --water loads a full layout with water
--food FOOD number of food to start with when testing
You can refresh the webpage to restart the game, but if you changed your code,
you need to terminate gui.py and run it again. To terminate gui.py, you can
hit Ctrl + C on the terminal.
You cannot have multiple tabs of this same Ants GUI open simultaneously or they will all error.
Checkpoint 1 Submission
Check to make sure that you completed all the problems in Phase 1:
python3 -m pytest --scoreWhen you run pytest, you'll still see that some tests are locked because you haven't completed the whole project yet. You'll get full credit for the checkpoint if you correctly complete all the problems up to this point.
Once you are satisfied, submit the checkpoint: run Provenance: Prepare Submission Bundle from the VS Code command palette and upload the zip it creates to the Ants Checkpoint 1 assignment on Gradescope. Be sure to submit before the checkpoint deadline of Friday 10/16. For a refresher on how to create and submit your zip with Provenance, refer to Lab 00.
Each partner must submit the version of the project that they typed to Gradescope.
Congratulations! You have finished Phase 1 of this project!
Phase 2: More Ants!
Now that you've implemented basic gameplay with two types of Ants, let's
add some flavor to the ways ants can attack bees. In this problem and on, you'll be
implementing several different Ants with different attack strategies.
After you implement each Ant subclass in these sections, you'll need to set its
implemented class attribute to True so that that type of ant will show up in the
GUI. Feel free to try out the game with each new ant to test the functionality!
With all following ants from now on, try uv run python gui.py to play against a
full swarm of bees in a multi-tunnel layout and try -d hard or
-d extra-hard if you want a real challenge! If the bees are too numerous to
vanquish, you might need to create some new ants.
Problem 4 (2 pt)
A ThrowerAnt is a powerful threat to the bees, but it has a high food_cost.
In this problem, you'll implement two subclasses of ThrowerAnt that are less
costly but have constraints on the distance they can throw:
-
The
LongThrowercan onlythrow_ataBeethat is found after following at least 5entrancetransitions. In other words, it cannot hitBees that are in the samePlaceas it or in the first 4Places in front of it. If there are twoBees, one too close to theLongThrowerand the other within its range, theLongThrowershould only throw at the fartherBee, which is within its range, instead of trying to hit the closerBee. -
The
ShortThrowercan onlythrow_ataBeethat is found after following at most 3entrancetransitions. In other words, it cannot throw at anyBees further than 3Places in front of it.
Neither of these specialized throwers can throw_at a Bee that is exactly 4
Places away.
| Class | Food Cost | Initial Health |
ShortThrower |
2 | 1 |
LongThrower |
2 | 1 |
To implement these new throwing ants, your ShortThrower and LongThrower classes
should inherit the nearest_bee method from the base ThrowerAnt class.
Next, modify the nearest_bee method in ThrowerAnt to reference the min_reach and max_reach attributes, and only return a Bee from a Place if that Place is within range of the provided bounds. This is necessary because the range of Places in which the ShortThrower and LongThrower ants can throw_at a Bee is limited by a max_reach and a min_reach, respectively.
Make sure to give these min_reach and max_reach attributes appropriate
values in the ThrowerAnt class so that the behavior of ThrowerAnt is
unchanged. Then, implement the subclasses LongThrower and ShortThrower
with appropriately constrained ranges.
You should not need to repeat any code between ThrowerAnt,
ShortThrower, and LongThrower.
Important: Make sure your class attributes are called
max_reachandmin_reachThe tests directly reference these attribute names, and will error if you use another name for these attributes.
Hint:
float('inf')returns an infinite positive value represented as a float that can be compared with other numbers.
Hint:
min_reachandmax_reachshould mark an inclusive range.
Don't forget to set the implemented class attribute of LongThrower and
ShortThrower to True.
python3 -m pytest -k q04 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> ShortThrower.food_cost
______
>>> LongThrower.food_cost
______
>>> short_t = ShortThrower()
>>> long_t = LongThrower()
>>> short_t.health
______
>>> long_t.health
______
After writing code, test your implementation (rerun the tests for 03 to make sure they still work):
python3 -m pytest -k q03python3 -m pytest -k q04π©π½βπ»π¨πΏβπ» Pair programming?
Remember to alternate between driver and navigator roles. The driver controls the keyboard; the navigator watches, asks questions, and suggests ideas.
Problem 5 (3 pt)
Implement the FireAnt, which does damage when it receives damage. Specifically,
if it is damaged by amount health units, it does a damage of
amount to all Bees in its place (this is called reflected damage).
If it dies, it does an additional amount of damage, as specified by its damage attribute,
to all the Bees in its place. The default value for the damage attribute in the FireAnt class is 3.
To implement this, override FireAnt's reduce_health method.
Your overriden method should call the reduce_health method inherited from
the superclass (Ant) which inherits from its superclass Insect to reduce the current FireAnt instance's health.
Calling the inherited reduce_health method on a FireAnt instance reduces
the insect's health by the given amount and removes the insect from its
place if its health reaches zero or lower.
Hint: Do not call
self.reduce_health, or you'll end up stuck in a recursive loop. (Can you see why?)
However, your method needs to also include the reflective damage logic:
- Determine the reflective damage amount:
start with the
amountinflicted on theFireAnt, and then adddamageif the ant'shealthhas dropped to or below 0. - For each
Beein the place, damage them with the total reflective damage amount by calling its appropriatereduce_healthmethod.
Important: Remember that when any
Antloses all its health, it is removed from itsPlace, so pay careful attention to the order of your logic inreduce_health.
| Class | Food Cost | Initial Health |
FireAnt |
5 | 3 |
Important: Damaging a bee may cause it to be removed from its place; when an insect dies, it is removed from its current place. If you iterate over a list, but change the contents of that list at the same time, you may not visit all the elements. This can be prevented by making a copy of the list. You can either use a list slice, or use the built-in
listfunction to make sure the original list is not affected.
>>> s = [1,2,3,4]
>>> s[:]
[1, 2, 3, 4]
>>> list(s)
[1, 2, 3, 4]
>>> (s[:] is not s) and (list(s) is not s)
True
Once you've finished implementing the FireAnt, give it a class attribute
implemented with the value True.
Note: Even though you are overriding the superclass's
reduce_healthfunction (Ant.reduce_health), you can still use this method in your implementation by calling it.
python3 -m pytest -k q05 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> fire = FireAnt()
>>> FireAnt.food_cost
______
>>> fire.health
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> place = gamestate.places['tunnel_0_4']
>>> fire = FireAnt(health=1)
>>> place.add_insect(fire) # Add a FireAnt with 1 health
>>> place.add_insect(Bee(3)) # Add a Bee with 3 health
>>> place.add_insect(Bee(5)) # Add a Bee with 5 health
>>> len(place.bees) # How many bees are there?
______
>>> place.bees[0].action(gamestate) # The first Bee attacks FireAnt
>>> fire.health
______
>>> fire.place is None
______
>>> len(place.bees) # How many bees are left?
______
>>> place.bees[0].health # What is the health of the remaining Bee?
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> place = gamestate.places['tunnel_0_4']
>>> ant = FireAnt(health=1) # Create a FireAnt with 1 health
>>> place.add_insect(ant) # Add a FireAnt to place
>>> ant.place is place
______
>>> place.remove_insect(ant) # Remove FireAnt from place
>>> ant.place is place # Is the ant's place still that place?
______
python3 -m pytest -k q05You can also test your program by playing a game or two! A FireAnt should
destroy all co-located Bees when it is stung. To start a game with ten food
(for easy testing):
uv run python gui.py --food 10
Problem 6 (1 pt)
We are going to add some protection to our glorious home base by implementing
the WallAnt, an ant that does nothing each turn. A WallAnt is
useful because it has a large health value.
| Class | Food Cost | Initial Health |
WallAnt |
4 | 4 |
Unlike with previous ants, we have not provided you with a class statement.
Implement the WallAnt class from scratch. Give it a class attribute name
with the value 'Wall' (so that the graphics work) and a class attribute
implemented with the value True (so that you can use it in a game).
Hint:: Make sure you implement the
__init__method too so theWallAntstarts off with the appropriate amount ofhealth!
python3 -m pytest -k q06Problem 7 (3 pt)
Implement the HungryAnt, which will select a random Bee from its Place and
deal damage to the Bee equal to the Bee's health, eating it whole. After
eating a Bee, a HungryAnt must spend 3 turns chewing before being able to
eat again. While the HungryAnt is chewing, it is not able to eat (deal damage
to) any Bees. If there is no Bee in its place available to eat, the
HungryAnt will do nothing.
We have not provided you with a class header.
Implement the HungryAnt class from scratch. Give it a class attribute name
with the value 'Hungry' (so that the graphics work) and a class attribute
implemented with the value True (so that you can use it in a game).
Hint: When a
Beeis eaten, itshealthshould be reduced by itshealth.
| Class | Food Cost | Initial Health |
HungryAnt |
4 | 1 |
Give HungryAnt a chew_cooldown class attribute that stores the number of
turns that it will take a HungryAnt to chew (set to 3). Also, give each
HungryAnt an instance attribute cooldown that counts the number of turns
it has left to chew, initialized to 0, since it hasn't eaten anything at the
beginning. You can also think of cooldown as the number of turns until a
HungryAnt can eat another Bee.
Implement the action method of the HungryAnt: First, check if it is chewing; if
so, decrement its cooldown. Otherwise, eat a random Bee in its
Place by reducing the Bee's health to 0. Make sure to set the cooldown
when a Bee is eaten!
Hint:: Other than the
actionmethod, make sure you implement the__init__method too in order to define any instance variables and make sure thatHungryAntstarts off with the appropriate amount ofhealth!
python3 -m pytest -k q07 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> hungry = HungryAnt()
>>> HungryAnt.food_cost
______
>>> hungry.health
______
>>> hungry.chew_cooldown
______
>>> hungry.cooldown
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> hungry = HungryAnt()
>>> super_bee, wimpy_bee = Bee(1000), Bee(1)
>>> place = gamestate.places["tunnel_0_0"]
>>> place.add_insect(hungry)
>>> place.add_insect(super_bee)
>>> hungry.action(gamestate) # super_bee is no match for HungryAnt!
>>> super_bee.health
______
>>> place.add_insect(wimpy_bee)
>>> for _ in range(3):
... hungry.action(gamestate) # chewing...not eating
>>> wimpy_bee.health
______
>>> hungry.action(gamestate) # back to eating!
>>> wimpy_bee.health
______
python3 -m pytest -k q07π©π½βπ»π¨πΏβπ» Pair programming?
This would be a good time to switch roles. Switching roles makes sure that you both benefit from the learning experience of being in each role.
Problem 8 (3 pt)
Right now, our ants are quite frail. We'd like to provide a way to help them
last longer against the onslaught of the bees. Enter the ProtectorAnt.
| Class | Food Cost | Initial Health |
ProtectorAnt |
4 | 2 |
To more easily implement the ProtectorAnt, we will break up this problem into 3 subparts.
In each part, we will making changes in either the ContainerAnt class, Ant
class, or ProtectorAnt class.
Note: We have separated out Question 8 into three different subparts. We recommend going through the unlocking test for each subpart before writing any code for it. You will be tested on each subpart, with each subpart worth one point, for a total of three points for the entire question.
Problem 8a
First, we will define and work in a ContainerAnt parent class that we will later use for our ProtectorAnt.
A ProtectorAnt differs from a normal ant because it is a ContainerAnt; it can
contain another ant and protect it, all in one Place. When a Bee stings the
ant in a Place where one ant contains another, only the container is
damaged. The ant inside the container can still perform its original action.
If the container perishes, the contained ant still remains in the place (and
can then be damaged).
Each ContainerAnt has an instance attribute ant_contained that stores the ant it
contains. This ant, ant_contained, initially starts off as None to indicate that there is no ant being
stored yet. Implement the store_ant method so that it sets the ContainerAnt's
ant_contained instance attribute to the ant argument passed in. Then implement the
ContainerAnt's action method. This method will ensure that if our ContainerAnt currently contains an ant, ant_contained's action is performed.
In addition, to ensure that a container and its contained ant can both occupy a Place at
the same time (a maximum of two ants per Place), but only if exactly one is a
container, we can create an can_contain method.
There is already an Ant.can_contain method, but it always returns False.
Override the method can_contain in ContainerAnt so that it takes an ant other as an
argument and returns True if:
- This
ContainerAntdoes not already contain another ant. - The other ant is not a container.
Hint: You may find the
is_containerattribute that eachAnthas useful for checking if a specificAntis a container.
python3 -m pytest -k q08a --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> gamestate = GameState(beehive, ant_types(), layout, (1, 9))
>>> container = ContainerAnt(1)
>>> container2 = ContainerAnt(2)
>>> container3 = ContainerAnt(3)
>>> throw_long = LongThrower(1)
>>> container.can_contain(container2)
______
>>> container3.can_contain(throw_long)
______
>>> container = ContainerAnt(2)
>>> friend = HungryAnt()
>>> container.ant_contained is None
______
>>> container.store_ant(friend)
>>> container.ant_contained is friend
______
>>> container = ContainerAnt(2)
>>> container.ant_contained is not None
______
>>> friend = HungryAnt()
>>> container.store_ant(friend)
>>> container.ant_contained is friend
______
>>> place = gamestate.places["tunnel_0_0"]
>>> place.add_insect(container)
>>> friend.place = place
>>> bee = Bee(3)
>>> place.add_insect(bee)
>>> container.action(gamestate) # Container holds a HungryAnt that loves to eat!
>>> bee.health
______
>>> container.can_contain(FireAnt()) # Container already holds another ant!
______
python3 -m pytest -k q08aProblem 8b
Next, we will be working in the Ant class.
Modify Ant.add_to to allow a container and its contained ant
to occupy the same place according to the following rules:
- If the
Antoriginally occupying a placecan_containtheAntbeing added, then bothAnts occupy the place and the originalAntcontains theAntbeing added. - If the
Antbeing addedcan_containtheAntoriginally in the space, then bothAnts occupy the place and theAntbeing added contains the originalAnt. - If neither
Antcan_containthe other, raise the sameAssertionErroras before (the one already present in the starter code).
Important: If there are two
Ants in a specificPlace, theantattribute of thePlaceinstance should refer to the container ant, and the container ant should contain the non-container ant.
Hint:: You should also take advantage of the
can_containmethod you wrote and avoid repeating code.
Note: If you're getting an "unreachable code" warning for
Ant.add_tovia the VSCode Pylance extension, it's fine to ignore this specific warning as the code is actually run (the warning in this case is inaccurate).
python3 -m pytest -k q08bProblem 8c
Finally, we can work on implementing our ProtectorAnt class.
Add a ProtectorAnt.__init__ that sets the initial amount of health for the ProtectorAnt.
We do not need to create an action method here since the ProtectorAnt class inherits it from the ContainerAnt class.
Also note that the ProtectorAnt does not do any damage.
Once you've finished implementing the ProtectorAnt,
give it a class attribute implemented with the value True.
python3 -m pytest -k q08c --unlockUnlocking Examples
>>> from ants import *
>>> protector = ProtectorAnt()
>>> ProtectorAnt.food_cost
______
>>> protector.health
______
python3 -m pytest -k q08cProblem 9 (2 pt)
The ProtectorAnt provides great defense, but they say the best defense is a
good offense. The TankAnt is a ContainerAnt that protects an ant in its place
and also deals 1 damage to all Bees in its Place each turn. Like any ContainerAnt, a TankAnt allows the ant that it contains to perform its action each turn.
| Class | Food Cost | Initial Health |
TankAnt |
6 | 2 |
We have not provided you with a class header.
Implement the TankAnt class from scratch. Give it a class attribute name
with the value 'Tank' (so that the graphics work) and a class attribute
implemented with the value True (so that you can use it in a game).
You should not need to modify any code outside of the TankAnt class. If you
find yourself needing to make changes elsewhere, look for a way to write your
code for the previous question such that it applies not just to ProtectorAnt
and TankAnt objects, but to ContainerAnts in general.
Hint:: The only methods you need to override from
TankAnt's parent class are__init__andaction.
Hint:: Like with
FireAnt, it is possible that damaging a bee will cause it to be removed from its place.
python3 -m pytest -k q09 --unlockUnlocking Examples
>>> from ants_plans import *
>>> from ants import *
>>> beehive, layout = Hive(make_test_assault_plan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> TankAnt.food_cost
______
>>> TankAnt.damage
______
>>> tank = TankAnt()
>>> tank.health
______
python3 -m pytest -k q09Checkpoint 2 Submission
Check to make sure that you completed all the problems in Phase 1 and Phase 2:
python3 -m pytest --scoreWhen you run pytest, you'll still see that some tests are locked because you haven't completed the whole project yet. You'll get full credit for the checkpoint if you correctly complete all the problems up to this point.
Once you are satisfied, submit the checkpoint: run Provenance: Prepare Submission Bundle from the VS Code command palette and upload the zip it creates to the Ants Checkpoint 2 assignment on Gradescope. Be sure to submit before the checkpoint deadline of Wednesday 10/21. For a refresher on how to create and submit your zip with Provenance, refer to Lab 00.
Each partner must submit the version of the project that they typed to Gradescope.
Congratulations! You have finished Phase 1 and Phase 2 of this project!
Phase 3: Water and Might
In the final phase, you're going to add one last kick to the game by introducing a new type of place and new ants that are able to occupy this place. One of these ants is the most important ant of them all: the queen of the colony!
Problem 10 (1 pt)
Let's add water to the colony! Currently there are only two types of places, the
Hive and a basic Place. To make things more interesting, we're going to
create a new type of Place called Water.
Only an Insect that is waterproof can be placed in Water. In order
to determine whether an Insect is waterproof, add a new class attribute to the
Insect class named is_waterproof that is set to False. Since Bees can fly,
their is_waterproof attribute is True, overriding the inherited value.
Now, implement the add_insect method for Water. First, add the Insect to
the Place regardless of whether it is waterproof. Then, if the Insect is not
waterproof, reduce the Insect's health to 0. Do not repeat code from elsewhere
in the program. Instead, use methods that have already been defined.
python3 -m pytest -k q10 --unlockUnlocking Examples
>>> from ants import *
>>> from ants_plans import *
>>> beehive, layout = Hive(make_test_assault_plan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> test_bee = Bee(1000000)
>>> test_bee.is_waterproof = False # Make Bee non-waterproof
>>> test_water = Water('Water Test2')
>>> test_water.add_insect(test_bee)
>>> test_bee.health
______
>>> len(test_water.bees)
______
>>> test_bee = Bee(1)
>>> test_water = Water('Water Test3')
>>> test_water.add_insect(test_bee)
>>> test_bee.health
______
>>> test_bee in test_water.bees
______
python3 -m pytest -k q10Once you've finished this problem, play a game that includes water. To access
the wet_layout, which includes water, add the --water option (or -w for
short) when you start the game.
uv run python gui.py --water
π©π½βπ»π¨πΏβπ» Pair programming?
Remember to alternate between driver and navigator roles. The driver controls the keyboard; the navigator watches, asks questions, and suggests ideas.
Problem 11 (2 pt)
Currently there are no ants that can be placed on Water. Implement the
ScubaThrower, which is a subclass of ThrowerAnt that is more costly and
waterproof, but otherwise identical to its base class. A ScubaThrower should
not lose its health when placed in Water.
| Class | Food Cost | Initial Health |
ScubaThrower |
6 | 1 |
We have not provided you with a class header. Implement the ScubaThrower
class from scratch. Give it a class attribute name with the value 'Scuba'
(so that the graphics work) and remember to set the class attribute
implemented with the value True (so that you can use it in a game).
python3 -m pytest -k q11 --unlockUnlocking Examples
>>> from ants import *
>>> scuba = ScubaThrower()
>>> ScubaThrower.food_cost
______
>>> scuba.health
______
>>> scuba.name
______
>>> scuba.is_waterproof
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), wet_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> water = gamestate.places["water_0_2"]
>>> ant = ScubaThrower()
>>> water.add_insect(ant)
>>> ant.place is water
______
>>> ant.health
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), wet_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> water = gamestate.places["water_0_2"]
>>> ant = ThrowerAnt()
>>> ant.is_waterproof
______
>>> water.add_insect(ant)
>>> ant.place is water
______
>>> ant.health
______
python3 -m pytest -k q11Problem 12 (2 pt)
Finally, implement the QueenAnt. A queen is a ThrowerAnt that inspires her
fellow ants through her bravery. In addition to the standard ThrowerAnt
action, a QueenAnt doubles the damage of all the ants behind her in her tunnel each time she
performs an action. However, once an ant's damage has been doubled, it cannot be
doubled again. Try to think of a way to keep track of whether an ant's damage has already been doubled (Hint: Use an instance attribute!)
Note: The reflected damage of a
FireAntshould not be doubled, only the extra damage it deals when its health is reduced to 0.
| Class | Food Cost | Initial Health |
QueenAnt |
7 | 1 |
However, with great power comes great responsibility. If a QueenAnt ever has its
health reduced to 0, the ants lose. You will need to override
Insect.reduce_health in QueenAnt and call ants_lose() in that case in
order to signal to the simulator that the game is over. (The ants also still
lose if any bee reaches the end of a tunnel.)
Hint: For doubling the damage of all ants behind her, you may fill out the
doublemethod defined in theAntclass, then call it from theQueenAntclass.
Hint: When doubling the ants' damage, keep in mind that there can be more than one ant in a
Place, like in the case of container ants storing another.
Hint: Remember that
QueenAnt'sreduce_healthmethod adds the additional task of callingants_lose()to the superclass'sreduce_healthmethod. How can we make sure we still do everything from the superclass's method without repeating code?
Hint: You can find each
Placein a tunnel behind aQueenAntby starting at the queen'splace.exitand then repeatedly moving back to the previousPlace'sexit. Theexitof aPlaceat the end of a tunnel isNone.
python3 -m pytest -k q12 --unlockUnlocking Examples
>>> from ants import *
>>> beehive = Hive(AssaultPlan())
>>> dimensions = (2, 9)
>>> gamestate = GameState(beehive, ant_types(), dry_layout, dimensions, food=100)
>>> QueenAnt.food_cost
______
>>> queen = QueenAnt()
>>> queen.health
______
python3 -m pytest -k q12Project Submission
Run pytest on all problems to make sure all tests are unlocked and pass:
python3 -m pytestYou can also check your score on each part of the project:
python3 -m pytest --scoreOnce you are satisfied, submit this assignment: run Provenance: Prepare Submission Bundle from the VS Code command palette and upload the zip it creates to Gradescope. For a refresher on how to do this, refer to Lab 00.
Each partner must submit the version of the project that they typed to Gradescope.
You are now done with the project! If you haven't yet, you should try playing the game!
uv run python gui.py [-h] [-d DIFFICULTY] [-w] [--food FOOD]
Extra Challenges (Optional)
Note: These problems are optional and are not worth any points.
During Office Hours and Project Parties, the staff will prioritize helping students with required questions. We will not be offering help with this question unless the queue is empty.
Implement two final thrower ants that do zero damage, but instead apply a temporary effect
on the action method of a Bee instance that they throw_at.
This "status" lasts for a certain number of turns, after which it ceases to take effect.
Problem EC 1 (0 pt)
We will be implementing a new ant, SlowThrower, which inherits from ThrowerAnt.
SlowThrower throws sticky syrup at a bee, slowing it for 5 turns. When a bee is slowed, it does its regular Bee action when gamestate.time is even, and takes no action (does not move or sting) otherwise. If a bee is hit by syrup while it is already slowed, it is slowed for 5 turns starting from the most recent time it is hit by syrup. That is, if a bee is hit by syrup, takes 2 turns, and is hit by syrup again, it will now be slowed for 5 turns after the second time it is hit by syrup. So it will have been slowed for 7 turns total (not 10!).
| Class | Food Cost | Initial Health |
SlowThrower |
6 | 1 |
In order to complete the implementations of this SlowThrower, you will
need to set its class attributes appropriately and implement the throw_at method in
SlowThrower.
Important Restriction: You may not modify any code outside the SlowThrower class for this problem. That means you may not modify the Bee.action method directly. Our tests will check for this.
Hint: Take a look at
SlowThrower's parent class,ThrowerAnt.ThrowerAnt'sactionmethod callsthrow_at, which is what you should be overriding inSlowThrower. What is passed into thetargetparameter inSlowThrower'sthrow_atfunction and why? What istarget.actionreferring to?
Implementation Hint: Assign
target.actionto a new function that conditionally callsBee.action. You can create and use an instance attribute to track how many more turns the bee will be slowed. Once the slowing effect is over,Bee.actionshould be called every turn again.
python3 -m pytest -k qEC1 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> slow = SlowThrower()
>>> SlowThrower.food_cost
______
>>> slow.health
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> slow = SlowThrower()
>>> bee = Bee(3)
>>> gamestate.places["tunnel_0_0"].add_insect(slow)
>>> gamestate.places["tunnel_0_5"].add_insect(bee)
>>> slow.action(gamestate) # slow throws syrup at bee
>>> gamestate.time = 1
>>> bee.action(gamestate) # slow causes slowness when gamestate.time is odd. bee should take no action (moving or stinging)
>>> bee.place.name
______
>>> gamestate.time += 1
>>> bee.action(gamestate) # bee is not affected when gamestate.time is even. bee should take its regular action
>>> bee.place.name
______
>>> for _ in range(5):
... gamestate.time += 1
... bee.action(gamestate)
>>> bee.place.name # hint: remember that the slow effect wears off after 5 turns. consider how many turns the bee was already affected before the for loop starts.
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> slow = SlowThrower()
>>> bee = Bee(3)
>>> gamestate.places["tunnel_0_0"].add_insect(slow)
>>> gamestate.places["tunnel_0_5"].add_insect(bee)
>>> slow.action(gamestate) # slow throws syrup at bee
>>> gamestate.time = 1
>>> bee.action(gamestate) # slow causes slowness when gamestate.time is odd. bee should take no action (moving or stinging)
>>> bee.place.name
______
>>> gamestate.time += 1
>>> bee.action(gamestate) # bee is not affected when gamestate.time is even. bee should take its regular action
>>> bee.place.name
______
>>> slow.action(gamestate) # slow throws syrup again. slow's effects will take place for 5 more turns
>>> for _ in range(5):
... gamestate.time += 1
... bee.action(gamestate)
>>> bee.place.name
______
You can run some provided tests, but they are not exhaustive:
python3 -m pytest -k qEC1Make sure to test your code! Your code should be able to apply multiple statuses on a target; each new status applies to the current (possibly previously affected) action method of the bee.
Problem EC 2 (0 pt)
You must implement Problem EC 1 (SlowThrower) correctly in order to pass the tests for this problem. We will be implementing a new ant,
ScaryThrower, which inherits fromThrowerAnt.
ScaryThrower intimidates a nearby bee, causing it to back away instead of advancing.
Here are some rules to keep in mind:
- If the bee is already right next to the Hive and cannot go back further, it should not move. To check if a bee is next to the Hive, you might find the
is_hiveinstance attribute ofPlaceuseful. - Bees remain scared until they have tried to back away twice. So, the back away effect lasts two turns.
- Bees cannot try to back away if they are slowed and
gamestate.timeis odd. This would be a turn they're frozen by SlowThrower! - Once a bee has been scared once, it can't be scared ever again.
| Class | Food Cost | Initial Health |
ScaryThrower |
6 | 1 |
In order to complete the implementation of this ScaryThrower, you will
need to set its class attributes appropriately and implement the scare method in
Bee, which applies the scared status on a particular bee. You may also have to edit some
other methods of Bee such as action.
python3 -m pytest -k qEC2 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> slow = SlowThrower()
>>> scary = ScaryThrower()
>>> SlowThrower.food_cost
______
>>> ScaryThrower.food_cost
______
>>> slow.health
______
>>> scary.health
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> slow = SlowThrower()
>>> bee = Bee(3)
>>> gamestate.places["tunnel_0_0"].add_insect(slow)
>>> gamestate.places["tunnel_0_5"].add_insect(bee)
>>> slow.action(gamestate)
>>> gamestate.time = 1
>>> bee.action(gamestate)
>>> bee.place.name
______
>>> gamestate.time += 1
>>> bee.action(gamestate)
>>> bee.place.name
______
>>> for _ in range(3):
... gamestate.time += 1
... bee.action(gamestate)
>>> bee.place.name
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> scary = ScaryThrower()
>>> bee = Bee(3)
>>> gamestate.places["tunnel_0_0"].add_insect(scary)
>>> gamestate.places["tunnel_0_4"].add_insect(bee)
>>> scary.action(gamestate)
>>> bee.action(gamestate)
>>> bee.place.name # ScaryThrower should scare for two turns (first turn)
______
>>> bee.action(gamestate)
>>> bee.place.name # ScaryThrower should scare for two turns (second turn)
______
>>> bee.action(gamestate)
>>> bee.place.name # ScaryThrower effects have worn off
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> scary = ScaryThrower()
>>> harvester = HarvesterAnt()
>>> bee = Bee(3)
>>> gamestate.places["tunnel_0_0"].add_insect(scary)
>>> gamestate.places["tunnel_0_4"].add_insect(bee)
>>> gamestate.places["tunnel_0_5"].add_insect(harvester)
>>> scary.action(gamestate)
>>> bee.action(gamestate)
>>> bee.place.name # ScaryThrower should scare for two turns
______
>>> harvester.health
______
>>> bee.action(gamestate)
>>> harvester.health
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> ScaryThrower.implemented
______
>>> SlowThrower.implemented
______
python3 -m pytest -k qEC2Make sure to test your code! Your code should be able to apply multiple statuses on a target; each new status applies to the current (possibly previously affected) action method of the bee.
Problem EC 3 (0 pt)
Implement the NinjaAnt, which damages all Bees that pass by, but can never
be stung.
| Class | Food Cost | Initial Health |
NinjaAnt |
5 | 1 |
A NinjaAnt does not block the path of a Bee that flies by. To implement
this behavior, first modify the Ant class to include a new class attribute
blocks_path that is set to True, then override the value of blocks_path to
False in the NinjaAnt class.
Second, modify the Bee's method blocked to return False if either
there is no Ant in the Bee's place or if there is an Ant, but
its blocks_path attribute is False. Now Bees will just fly past
NinjaAnts.
Finally, we want to make the NinjaAnt damage all Bees that fly past.
Implement the action method in NinjaAnt to reduce the health of all Bees
in the same place as the NinjaAnt by its damage attribute. Similar to
the FireAnt, you must iterate over a potentially changing list of bees.
Hint: Having trouble visualizing the test cases? Try drawing them out on paper! See the example in Game Layout for help.
python3 -m pytest -k qEC3 --unlockUnlocking Examples
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> ninja = NinjaAnt()
>>> ninja.health
______
>>> NinjaAnt.food_cost
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> NinjaAnt.blocks_path
______
>>> HungryAnt.blocks_path
______
>>> FireAnt.blocks_path
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> p0 = gamestate.places["tunnel_0_0"]
>>> p1 = gamestate.places["tunnel_0_1"] # p0 is p1's exit
>>> bee = Bee(2)
>>> ninja = NinjaAnt()
>>> thrower = ThrowerAnt()
>>> p0.add_insect(thrower) # Add ThrowerAnt to p0
>>> p1.add_insect(bee)
>>> p1.add_insect(ninja) # Add the Bee and NinjaAnt to p1
>>> bee.action(gamestate)
>>> bee.place is ninja.place # Did NinjaAnt block the Bee from moving?
______
>>> bee.place is p0
______
>>> ninja.health
______
>>> bee.action(gamestate)
>>> bee.place is p0 # Did ThrowerAnt block the Bee from moving?
______
>>> from ants import *
>>> beehive, layout = Hive(AssaultPlan()), dry_layout
>>> dimensions = (1, 9)
>>> gamestate = GameState(beehive, ant_types(), layout, dimensions)
>>> p0 = gamestate.places["tunnel_0_0"]
>>> p1 = gamestate.places["tunnel_0_1"] # p0 is p1's exit
>>> bee = Bee(2)
>>> ninja_fire = FireAnt(1)
>>> ninja_fire.blocks_path = False
>>> thrower = ThrowerAnt()
>>> p0.add_insect(thrower) # Add ThrowerAnt to p0
>>> p1.add_insect(bee)
>>> p1.add_insect(ninja_fire) # Add the Bee and FireAnt to p1
>>> bee.action(gamestate)
>>> bee.place is ninja_fire.place # Did the "ninjaish" FireAnt block the Bee from moving?
______
>>> bee.place is p0
______
>>> ninja_fire.health
______
>>> bee.action(gamestate)
>>> bee.place is p0 # Did ThrowerAnt block the Bee from moving?
______
python3 -m pytest -k qEC3For a challenge, try to win a game using only HarvesterAnt
and NinjaAnt.
Problem EC 4 (0 pt)
We've been developing this ant for a long time in secret. It's so dangerous
that we had to lock it in the super hidden underground vault, but we
finally think it is ready to test out on the field. In this problem, you'll be
implementing the final ant -- LaserAnt, a ThrowerAnt with a twist.
| Class | Food Cost | Initial Health |
LaserAnt |
10 | 1 |
The LaserAnt shoots out a powerful laser, damaging all that dare
to stand in its path. Both Bees and Ants, of all types, are at
risk of being damaged by LaserAnt. When a LaserAnt takes its action,
it will damage all Insects in its place (excluding itself, but including its
container if it has one) and the Places in front of it, excluding the Hive.
If that were it, LaserAnt would be too powerful for us to contain.
The LaserAnt has a base damage of 2. But, LaserAnt's laser comes with
some quirks. The laser is weakened by 0.25 each place it travels away from
LaserAnt's place. Additionally, LaserAnt has limited battery.
Each time LaserAnt actually damages an Insect, its
laser's total damage goes down by 0.0625 (1/16). This reduction is immediate so if there are
two Bees in front of LaserAnt and on the same tile, it will do less damage to the second Bee.
If LaserAnt's damage becomes negative due to these restrictions,
it simply does 0 damage instead.
The exact order in which things are damaged within a turn does not matter.
In order to complete the implementation of this ultimate ant, read
through the LaserAnt class, set the class attributes appropriately,
and implement the following two functions:
insects_in_frontis an instance method, called by theactionmethod, that returns a dictionary where each key is anInsectand each corresponding value is the distance (in places) that thatInsectis away fromLaserAnt. The dictionary should include allInsectson the same place or in front of theLaserAnt, excludingLaserAntitself.calculate_damageis an instance method that takes indistance, the distance that an insect is away from theLaserAntinstance. It returns the damage that theLaserAntinstance should afflict based on:- The
distanceaway from theLaserAntinstance that anInsectis. - The number of
Insects that thisLaserAnthas already damaged, stored in theinsects_shotinstance attribute.
- The
In addition to implementing the methods above, you may need to modify, add,
or use class or instance attributes in the LaserAnt class as needed.
Important: If an insect's health is unaffected, its health should remain as a whole number (integer), as it was when the insect was initially created.
Note: There are no unlocking tests for this question.
python3 -m pytest -k qEC4Acknowledgments: Tom Magrino and Eric Tzeng developed this project with John DeNero. Many others have contributed to the project as well!
The artwork was drawn by Alana Tran, Andrew Huang, Emilee Chen, Jessie Salas, Jingyi Li, Katherine Xu, Meena Vempaty, Michelle Chang, and Ryan Davis.