COMP 204 – Assignment #4 Due date: November 18, 23:59
• Submit one Python program, which should contain all your functions. The file should be called ecosystem_simulator.py
• Submit the image generated for question 9, named Q9_plot.pdf .
• You may submit your program more than once, in which case the latest submission will be
graded.
• Write your name and student ID at the top of the program
• For each question, complete the relevant functions and methods. Do not change the names
of the class, functions, methods, or arguments. You can create additional
functions/methods if needed.
• For each question, your function will be automatically tested on a variety of test cases,
which will account for 75% of the mark.
• For each question, 25% of the mark will be assigned by the TA based on (i) Re-use of
functions as appropriate; (ii) appropriate naming of variables; (iii) commenting of your
program; (iv) simplicity of your program (simpler = better).
• Important notes:
o This assignment focuses on function re-use and object-oriented programming. You
will be writing several functions and using some classes. Some of those functions should call other functions in order to re-use the code already written. Make sure to re-use your functions as much as needed!
o The question statements include hints as to how many lines of code are needed to answer each question. These are merely suggestions, and you will not be penalized for using more/fewer lines. If you find yourself coding much more lines than what it suggested, it may be a hint that you are overcomplicating the problem.
Background
In this assignment, you will write a Python program that simulates the evolution of a simple ecosystem. In doing so, you will also reinforce your understanding of object-oriented programming. Let us start by describing the simulation we are aiming to program. Our goal is to simulate an African ecosystem. The ecosystem consists of a list of animals that live on an L x L grid, where each cell represents a 1 km by 1 km square. Each cell is either occupied by a single animal, or is empty. We will consider only two species: zebras and lions. The Figure below illustrates a possible state of the ecosystem (with L = 10).
0123456789 0
1 2 3 4 5 6 7 8 9
Z
Z
Z
L
Z
Z
Z
Z
L
Z
Z
The ecosystem evolves over time. We will assume that we model its evolution in time steps of one month. This means that the ecosystem will be in one state at month 0, then in another state at month 1, then another in month 2, etc. The parameters of the simulation (described below) are not very realistic; they’ve been chosen to make the problem interesting and not too hard.
Parameters of the simulation:
1. The size of the grid: L=10
2. Duration of the simulation: 20 months
3. Maximum age of lion: If a lion makes it to 18 months, it dies of old age
4. Maximum age of zebra: If a zebra makes it to 7 months, it dies of old age
5. Death by starvation of lions: If a lion has not eaten in 6 months, it dies of starvation
6. Death by starvation of zebras: Zebras never die of starvation, because we assume there’s always
grass for them to eat.
7. Reproduction:
a. Lions reproduce at age exactly 7 and 14 months only
b. Zebras reproduce at age exactly 3 and 6 months only
State of the system: A list of animals, where each animal belongs to a certain species, is located at a certain position, has a certain age, and a certain time since last meal.
Update of the state from one month to the next:
Each month, the following events take place, in that order:
1) Aging: Make all animals grow older by one month
2) Hunger: For all animals, increase by one month the time since the last meal
3) Death of old animals: Remove ALL animals that have exceeded their life span from the
simulation (see parameters above). Animals that die are simply removed from the list of
animals. IMPORTANT: See comment below about removing objects from a list.
4) Death of malnourished animals: Remove ALL animals that have starved (see parameters
above). IMPORTANT: See comment below about removing objects from a list.
5) Moves: Each animal that is still alive tries to move randomly to one of the (up to) 8 cells that surround it horizontally, vertically, or diagonally. If the animal is near the edge/corner or the grid, it is only allowed to move to a valid cell (within the grid). When an animal tries to move to
a cell that is already occupied by another animal, three things can happen:
• If the animal that is moving is a lion and it moves to a cell that contains a zebra, it eats the
zebra and replaces it in that cell. The zebra is then removed from the simulation.
• If the animal that is moving is a zebra and it moves to a cell that contains a lion, it is eaten
by the lion and is removed from the simulation.
• If a lion tries to move to a cell occupied by another lion, or if a zebra tries to move to a
cell occupied by another zebra, the move is forbidden so the animal remains where it
was. The animal does not try to find another cell to move to.
6) Reproduction: If an animal reaches the time of reproduction (see above), a new animal of the
same species is born and is placed in one of the empty neighboring cells from the parent (randomly chosen). If there is no empty neighboring cell, the animal fails to reproduce. Note: We are assuming here that each individual animal is capable of having a baby by itself (e.g. cloning, like bacteria do); there is no need for a second animal of the same species to be in the vicinity.
Important notes:
• The 6 steps above will need to be executed in successive stages where each stage has an effect on all animals (e.g. have a piece of code to age all animals by one month, then a piece of code to
remove all animals that have starved, etc…). DO NOT make one animal age, check if they died of hunger/old age, move, etc… and move on to the next animal; this will give you the incorrect result.
• For each of the 6 steps above, the animals should be considered in a specific order: left-to-right and top-to-bottom.
Assignment
Download ecosystem_simulator.py and tester.py from MyCourses.
The file ecosystem_simulator.py file contains a class called “Animal” and several functions that you will need to complete. The tester.py file contains code that calls the different functions. The program also contains the initialize_population function that you will use to initialize the ecosystem and start your simulation.
Download expected_output.txt from MyCourses.
This file contains examples of correct output for each question. This output is produced by the tester.py program.
Important notes:
• To make it possible for our TAs to import your code within their grading system:
o All your code should either be in a function, a class, or a method
o Make sure the file you submit is called ecosystem_simulator.py (not something like
ecosystem_simulator(1).py or anything else).
Question 1. (10 points) [8-20 lines of code] Write the function
list_neighbors(current_row, current_col, grid_size), which takes as input the row index and column index of a position in the grid and returns a list of all neighboring positions in the grid. This list should include 8 tuples in cases where current_row and current_col are not close to an edge of the grid. Otherwise, it would contain 5 tuples (if next to an edge) or 3 tuples (if close to a corner). This list’s tuples can be in any order.
Question 2. (10 points) [8-20 lines of code] Write the function
random_neighbor(current_row, current_col,grid_size, only_empty=False, animals=[])
that returns a randomly selected neighbour of the current position (current_row, current_col).
If keyword argument only_empty is True, then only empty neighboring cells should be considered, i.e. your function should only return a neighbouring cell that is not already occupied by an animal. If all neighboring cells contain animals, this function should return None.
If keyword argument only_empty is False, then your function should pick randomly among the neighbors, irrespective of whether an animal occupies the cell or not.
Your function should make use of the list_neighbors( ) function and the my_random_choice( ) function. Question 3. (5 points) [2-5 lines of code] Write the Animal class method can_eat(self, other), that
returns True if self can eat other, and False otherwise. Remember that only lions can eat zebras.
Question 4. (5 points) [2-5 lines of code] Write the Animal class method time_passes(self), which increments by one the age and time_since_last_meal of self.
Question 5. (5 points) [4-6 lines of code] Write the Animal class method dies_of_old_age(self), which returns True if the animal is in age of dying, and false otherwise. The function does not actually “kill” the animal; it just returns True or False.
Question 6. (5 points) [4-6 lines of code] Write the Animal class method dies_of_hunger(self), which returns True if the animal is supposed to die of malnourishment, and False otherwise. The function does not actually “kill” the animal; it just returns True or False.
Question 7. (5 points) [4-6 lines of code] Write the Animal class method will_reproduce(self), which returns True if the animal is supposed to reproduce this month (depending on their age), and False otherwise. The function does not actually “reproduce” the animal; it just returns True or False.
Question 8. (40 points) [~100 lines of code] Write the function one_step(all_animals, grid_size) that performs one step (i.e. one month) of the simulation, starting from a given list of animals. The function should modify the all_animals list to reflect changes in the animals’ positions. It should also return the list of all events (described by Strings) that took place during that time step. Example of events would be:
Lion dies of old age at position 7 2
Lion dies of hunger at position 3 5
Zebra moves from 1 4 to empty 0 3
Zebra moves from 0 2 to 0 1 and is eaten by Lion Lion moves from 8 3 to 8 4 and eats Zebra
Birth of a Zebra at 1 4
In order to get results identical to the expected output, you will need to carry out each step of the simulation in the order shown below (refer to the Background section for more details):
1) Execute the time_passes() function on all animals
2) Remove animals that die of old age (as determined by the die_of_old_age methods).
IMPORTANT: See comment below about removing objects from a list.
3) Remove animals that die of hunger (as determined by the die_of_hunger methods).
IMPORTANT: See comment below about removing objects from a list.
4) Move each animal that remains to a random neighboring cell (generated by the random_neighbor function). If the move results in one animal eating another, remove the eaten animal from the list. Remember that when an animal eats another, its time_since_last_meal attribute is reset to 0.
5) Generate births for the animals that have reached the right age.
Question 9. (15 points) [5-10 lines of code] Modify the run_whole_simulation(grid_size) function so that it generates the figure shown here. The image should not be shown to screen, but should instead be saved in a file whose name is provided as argument to the method.
Submit on CodePost the image generated by your code, called Q9_plot.pdf.
Important notes: The simulation involves animals making random moves to neighboring cells, and babies being placed at randomly chosen neighboring positions from their parent.
In the Python’s random module, there is a function that we will use for that after your program is finished and debugged:
random.choice(my_choices), where my_choices is a list of objects, from which one is randomly selected. You can find out more about it here: https://docs.python.org/3/library/random.html
However this randomness means that different runs of the same program will probably produce different results. This makes debugging and grading difficult, and makes it hard for me to tell you what the expected output is. So, in order to alleviate this difficulty, you will use for development and testing a non-random selection function, which always selects the top-most, left-most element in the list. This function is already written for you as my_random_choice( ).
Super important: For your submission, use the original non-random my_random_choice() function.
Important note about removing objects from a list: At many points in your one_step() function, you will need to iterate through the elements of the all_animals list and remove some elements (those that die from old age, hunger, or because they are eaten). Doing so is trickier than it looks. Consider for example the small example below:
L=[1,2,3,4,5]
for x in L:
print(x)
if x==3:
L.remove(2)
You may expect the program to print 1 2 3 4 5, but it actually prints 1 2 3 5. That’s because removing elements from a list we are currently iterating over messes up the for loop. To avoid that, your program should instead do something like this:
will_die = []
for a in all_animals:
if #ANIMAL_WILL_DIE#:
will_die.append(a)
for a in will_die:
all_animals.remove(a)
For fun, not graded, nothing to submit.
When you are happy with your program and are ready to switch to actual random moves, comment out the line “return min(choices, key=getKey)“ of the my_random_choice() function, and uncomment the line located below it.
Now, you will get a different result every time you run your simulation. Change the duration of the simulation to 500 steps, and look at the image file that gets generated. You’ll see different kinds of things happening. Most of the times, the population of zebras will oscillate widely (approximately from 5 to 90), over time periods of 50-100 months. This will be accompanied by similar oscillation of the lion population (from 1 to 20). Notice how the peaks of zebra population is followed by an increase in lion population, which causes a rapid drop in zebra population, which eventually causes a drop in lion population. And the cycle starts over! Until, by bad luck, either
• All the lions die, which can happen more or less quickly, and then the zebras living happy
• All the zebras die or are eaten, and then the lions die of hunger shortly thereafter
At the end of this document, I show some of the results I’ve obtained. You will not get exactly the same thing, but you should find similar kinds of results.
Super important, again: For your submission, use the original non-random my_random_choice() function. If you’ve switched to the random version for this section, make sure to switch back before submitting!