Goals : Simulate certain general behaviours of bacteria (changes over time, nutrient consumption, end of life, etc.)
We now need to resume work on and complete the Bacterium class that we started earlier.
Bacteria may take various forms in our project.
However, we will consider that all bacteria, whatever they may be:
can be drawn (using the drawOn method) and simulated (using the update method) and are circular bodies.
have a colour (of type MutableColor).
have a direction of movement (a unit vector modelled using the Vec2d type).
may be in a state of abstinence (in which case they do not consume nutrients).
have an energy level (of type Quantity).
have a set of mutable (numeric) parameters.
can move but we do not know how to define this functionality in a generic way for any given bacterium. The movement method will calculate the bacterium's new position and direction after a time step of dt (passed as a parameter to the method handling movement: void move(sf::Time dt));
have a feature that allows us to determine if they are dead (energy level less than or equal to zero).
std::map<string, double> grades; // string = key type and double = value type grades["informatic"] = 5.5; // we associate to the "informatic" key the 5.5 value // the key (generalized index) is the class name and 5.5 the value associated to this key.We can therefore represent the set of mutable parameters as a std::map<string, MutableNumber>, which allows us to associate a mutable numeric value with a key (such as "speed").
The constructor of a Bacterium must be able to initialize its energy level, position, direction, radius, and color based on values passed as parameters. By default, the bacterium will not be in a state of abstinence and its set of mutable parameters will be empty.
[Question Q3.9] : Given the above, how do you propose to model the Bacterium class (inheritance, attributes, methods, ...) ?
[Question Q3.10] : Which of the methods suggested for a Bacterium are likely to be virtual or pure virtual ?
Answer these questions in your REPONSES file and complete the code for the Bacterium class (to the extent that you can at this stage) in accordance with your answers.
For bacteria to effectively enter the simulation environment, they must be part of a CultureDish (and a Lab). Before continuing, use what you did for the nutrients as a guide to complete the Lab and CultureDish classes so that bacteria can be added (always to the current culture dish).
In the following, we will focus on implementing the two fundamental methods of the Bacterium class: drawOn (drawing) and update (evolution over time).
Implementing the associated algorithms requires using configurable properties of the bacterium, such as: what is the maximum amount of nutrients it can consume at once, or how long does it wait before consuming nutrients again if it has just fed, etc.?
Let’s begin by looking at how to access these properties.
As with nutrients, these properties will vary from one type of bacterium to another (a single-flagellated bacterium may consume a different maximum amount of nutrients than a hook-shaped bacterium, for example).
Just as we did for nutrients, it is therefore useful to define a method j::Value& getConfig() that polymorphically returns the properties specific to each subtype of bacterium (getConfig() for single-flagellated bacteria would return getAppConfig()["monotrichous"];, while that for twitching bacteria would return getAppConfig()["twitching motility bacterium"];, etc.)
Suppose that b is of type Bacterium*; in that case, b->getConfig() must automatically adapt to the actual nature of the instance pointed to by b (if b is a pointer to a single-flagellated bacterium, b->getConfig() will return getAppConfig()["monotrichous"]).
Based on the value returned by getConfig(), it then becomes possible to access specific properties: b->getConfig()["meal"]["max"].toDouble(); would return the maximum amount of food that can be consumed by the single-flagellated bacterium if b is a pointer to a single-flagellated bacterium, and it would return the maximum amount of food that can be consumed by the hook-bearing bacterium if b is a pointer to a hook-bearing bacterium.
[Question Q3.11] : In your opinion, is the method getConfig a pure virtual method ? Answer this question and provide your reasoning in your REPONSES file. Implement the getConfig method (you can anticipate a MonotrichousBacterium class in which this method would be appropriately overridden).
You can then use getConfig, as needed, to provide the Bacterium class with "utility getters", such as a getter that returns the energy expended at each movement step (associated with ["energy"]["consumption factor"]).
Any bacterium can be drawn in a very basic way as a circle.
auto const circle = buildCircle(position, rayon, couleur);
target.draw(circle);
where position is the position of the circle’s center (in this case, it will be the position of the bacterium), rayon is its radius, and couleur is its color.
[Question Q3.12] : How do you retrieve the SFML color of the bacterium from its MutableColor attribute? Answer this question in your REPONSES file.
Implement the Bacterium::drawOn method according to the description above.
Also draw inspiration from what you did for the nutrients to enable the display of bacteria and their energy in "debug" mode.
The method update(sf::Time dt) of a Bacterium will implement the following algorithm :
[Question Q3.13] : Is the fact that no concrete movement method exists yet an obstacle to writing the update method ? Answer this question and provide your reasoning in your REPONSES file.
Implement a method bool Laboratory::doesCollideWithDish(CircularBody const& body) that returns true if body collides with the Lab culture dish, and false otherwise.
Use this method to implement step 2 of the algorithm above.
Implement a method void Laboratory::checkCollidingNutriment(Bacterium* bacterium) that allows a given bacterium to determine which Nutriment* in its dish it is colliding with (If there are multiple candidates, the first one found will suffice!).
[Question Q3.14] : The CultureDish class does not provide access to its collection of nutrients; how should you proceed in this case to implement checkCollidingNutriment ? Answer this question in your REPONSES file.
[Question Q3.15] : Why is a method named checkCollidingNutriment implemented here instead of a method that returns a Nutriment* in collision ? Answer this question in your REPONSES file.
Use the method checkCollidingNutriment to implement step 3 of the algorithm above.
The bacterium will only consume food if it is not in a state of abstinence and the required waiting time has elapsed since its last food consumption (implement the necessary getters to access this data concisely).
If it consumes nutrients, the bacterium takes at most the maximum amount it can consume (or that is available). The bacterium's energy level increases by the amount of nutrients consumed.
Implement the bacterium::update method according to the description above.
[Question Q3.16] : Which method of the CultureDish class needs to be modified to allow for the simulation of bacterial growth ? How should it be modified? Answer these questions in your REPONSES file and code any suggested modifications.
As bacteria move, they lose energy. It is therefore possible that at the end of a simulation cycle, a bacterium may reach zero energy and thus die and disappear from the culture dish.
Similarly, if a nutrient source has been entirely consumed by the bacteria, it must also disappear from the dish.
[Question Q3.17] What do you need to modify, and in which class, to ensure that the bacteria and nutrient sources in your simulation die/disappear if their energy/quantity becomes zero ?
vect.erase(std::remove(vect.begin(), vect.end(), nullptr), vect.end());This syntax will be explained in one of our upcoming lessons.
Implement these changes in your program.
Finally, add a method consumeEnergy(Quantity qt) to the Bacterium class that decrements the bacterium’s energy level by the quantity qt.
The test provided in src/Tests/GraphicalTest/BacteriaTest.cpp allows you to test some of your new developments (nutrient consumption, collision testing with nutrients, and the disappearance of bacteria/nutrients with zero energy level or quantity).
Open this file and examine it. You will see that a type of dummy bacteria, inheriting from Bacterium, is defined there. This class minimally and concretely redefines the move method so that it simply causes the bacteria to lose energy with each movement.
Run the test using the target bacteriaTest. You will have taken care to uncomment it in the file CMakelists.txt and to have integrated this new material using Build > Run CMake. Before running the test, configure the simulation to :
Then create a bacterium on a nutrient source using the 'M' key. You should then see the bacterium's energy level rise and the nutrient source deplete as the bacterium feeds. After a while, the bacterium will have consumed everything. Since it cannot move yet, it will simply lose energy with each simulation cycle and then... inevitably die :'(
If everything is working correctly, the bacterium should disappear, along with the fully consumed nutrient source :
You can also adjust the parameter [simple bacterium]["meal"]["delay"] to verify that your bacterium does not consume anything if the time between two nutrient intakes is not long enough : increasing this parameter should result in longer waiting times between successive nutrient intakes.