Purpose : to practice more with basic class design and implementation and work with inheritance .
In this part of the lab, you’ll work with basic class design and implementation to modify the bear-fish simulation.
Step 1: Setup ¶
To get started with this lab exercise do the following things.
Download the starter code .
Extract the ZIP file to a folder named bear_fish (or similar) in your class OneDrive (or similar).
Read the README.txt file. This is a common way of describing a program
that spans over several files.
Run the bearfishsimulation.py file and see that it works.
Step 2: Speed Up ¶
The code runs really slowly! This is because screen refreshes are
performed after every modification is made by a turtle. By disabling
screen refreshing until all the work is done and then painting the
screen. we will eliminate the delays that result from the screen
furiously trying to update the screen from every turtle change. We can
accomplish this by turning "tracing" off and then turning it back on
after all the current updates are complete.
Do the following:
Study the code to find where liveALittle() is implemented in the World
class. Then, in that code, add a call to turn tracing off at the
beginning of the method, and turn it back on at the end. You can see
examples of how to turn it on or off in another place in the code. Add a
comment to the code here indicating why this is being done.
Run the simulation and see if it runs the same, but much faster.
Notice that the main simulation depends on several different constants,
defined as local variables at the top of the mainSimulation() method.
Do this...
Move those constants up to the top of the file (after any imports) and
rename them to use all capital letters and underscores as is fitting for
constants. E.g., change worldWidth to be WIDTH.
Run the code to make sure it still works.
Step 4: Refactor to use inheritance ¶
Open up bear.py and fish.py. Notice how much code is identical, or nearly identical, between the two classes! We can use inheritance to clean up this code.
Create a new file, organism.py.
In organism.py, create an Organism class with a constructor but no other methods (yet! ). The constructor should take one parameter besides self: the shape image (which will be "Bear.gif" or "Fish.gif" etc.)
Modify the Bear class to inherit from Organism. (You will need to import the Organism class just like it imports Fish.)
At the beginning of the Bear constructor, add a call to the parent constructor. You may use either super().__init__("Bear.gif") or Organism.__init__(self, "Bear.gif"); make sure you understand what it is doing.
Move the first block of code (until the blank line) from the Bear constructor to the Organism constructor, changing "Bear.gif" as appropriate. (This would also be a good time to add a comment describing what that block of code does. )
Save all the modified files and run the main simulation. Fix any errors that arise (e.g., you may need to add an import statement.)
Now, do the same thing for the Fish class.
Continue moving code that’s in common between the constructors to the parent constructor.
Save everything and make sure everything still works.
Continue with other methods that are shared in common.
Celebrate the removal of redundant code! But be careful not to overdo it : some of the methods are different, because, well, Bears and Fish are different.
Step 5: Adding some functionality ¶
Let’s keep track of how many bears and fish there are in the world.
Here’s one strategy:
In the World class, add two additional instance variables, bearCount and
fishCount. (Remember we define and initialize instance variables in the
class constructor.)
Also, add methods to increment and decrement the number of bears and the number of fish. You might name them incBears, decBears, incFish, etc. They are setter methods, so they don’t return anything.
You will have to add code to call incBears(), decBears(), etc., at the
places where bears or fish are added or die (Hint: look in methods in
the Bear and Fish classes for where addThing() and delThing() are
called. These are the places you should call the incBears(), decBears(),
incFish(), or decFish() calls).
Now add a method showCounts() to the World class. This method just
prints the count of bears and fish currently in the simulation.
Then, add a call to showCounts() in the simulation after each
liveALittle() call is done in the main driver code.
Run the code and watch the numbers of bears and fish go up and down.
Step 6: Tune the simulation ¶
Once you have all this working, try changing the size of the world to be
25x25. Then, try changing the number of steps the world simulates to be
5000 or 10000.
What I see is that all the fish always die out, but the bears live on
-- even after the fish are gone. Why is this? Can you fix it?
Do this…
Look in bear.py at liveALittle(). You’ll see two magic numbers
there: 10 and 8. Create a CONSTANT at the top of the file for each of
those numbers and replace those numbers in the code with the constants.
Run your code to see if it still works.
Now, try changing the number of ticks it takes for a bear to breed or
starve. See if you can come up with numbers that cause the bears to live
only when there are fish available
You might want to add print statements to see every time a bear is born
and when a bear dies.
A much more realistic scenario is that when a Bear is starting to
starve, it does not breed. To implement this, we need to define a new
constant -- VERY_HUNGRY_TICKS. Then, in tryToBreed(), we need to check
if the bear’s starveTicks > VERY_HUNGRY_TICKS. If so, return
immediately -- no need to check if the bear can breed.
Implement this, and then set the constant you use for checking when a
bear dies of starvation to be a bigger value -- like 20. Set
VERY_HUNGRY_TICKS to some number just below the breed ticks constant.
Run the program and see if we see that bears die off when there aren’t
enough fish, but continue to breed when there are fish.
Adjust your constants until you find values that result in bears and
fish (sometimes) living on until the end of your world comes.
Step 7: Add a Plant Class ¶
Create a Plant class that is similar to the Fish class. You
should already have Plant.gif for it.
Come up with rules for a Plant: plants don’t move, but they do
spread. How quickly? How long do plants live?
You may encounter an error because turtles don’t know about Plant.gif. So:
Find where the World constructor tells the turtle Screen to addshape for the other shapes. Add a call for Plant.gif accordingly.
Now, let’s make fish eat too:
Modify the Fish class so that the liveALittle() method allows fish to
eat plants. Add a starveTick counter to the Fish class. Populate the
World randomly with plants when the simulation starts, and add some
“overcrowding” mechanism if necessary. See if you can find a setup where
Bear, Fish, and Plants all survive for a long time.
Step 8 (optional): Additional clean-up ¶
Can you find:
The same list literal repeated many times? Could you write it just once?
The same lines of code inside and outside of a while loop? Remove this redundancy by switching the loop to use while True.
The same neighbor-checking logic in several different methods? Extract this logic out into one method that gets called several times.
Any code that could use some documentation?
Submit your lab ¶
Create a new ZIP file with your completed project code and upload it to Moodle.
If you don’t know how to do make a ZIP file, here are a few references:
https://www.wikihow.com/Make-a-Zip-File , https://www.digitaltrends.com/computing/how-to-zip-a-file-in-windows-10/
We will grade this exercise according to the following criteria:
16 points total
5 points for code correctly cleaned up (Organism base class, turning tracing off and on)
2 points for adding new variables -- bearCount, fishCount, and the
supporting methods and method calls.
2 points for adding VERY_HUNGRY_TICKS and getting that working.
1 points: for updating README.txt with authors etc.
5 points for adding plant.py and incorporating into the simulation
1 point for finding settings where Bears, Fish, and Plants all
survive (sometimes) to the end of the simulation.