Project: Step 3.1
Specialized and automatically generated nutrients

Disclaimer: the automatically generated English translation is provided only for convenience and it may contain wording flaws. The original French document must be taken as reference!

Goals: Distinguish between nutrient sources and enable their automatic generation.

Updates to inheritance links

Before introducing different types of nutrients, let’s briefly review the existing design in light of the new concepts covered in class: namely, inheritance and polymorphism.

We’ll cover this in detail later in the semester, but in C++, a class can inherit from multiple superclasses. The principle of inheritance itself remains fundamentally the same. To specify multiple inheritance (here, a class of objects that are drawable and evolve over time), simply list the superclasses separated by commas:
class MyClass : public Drawable, public Updatable
(the class MyClass inherits from Drawable and Updatable)

[Question Q3.1 ]: The classes provided in the Interface directory, namely Drawable and Updatable, provide two polymorphic methods: drawOn and update. Which classes in your current design would benefit from inheriting from these subclasses? What advantages does this bring to the design? Answer these questions in your REPONSES file.

Make the suggested changes.

For the rest of your design, be sure to have all classes for which it is relevant inherit from Drawable and Updatable. Of course, it is not essential to inherit from both systematically.

Virtual destructors

You recently learned that in a polymorphic hierarchy, it is advisable to implement destructors as virtual... keep that in mind!

Nutrient hierarchy

We now want to ensure that the Nutrient class specializes into two subclasses: NutrientA and NutrientB.

For now, these two subclasses will differ only in their graphical display (the drawing methods remain the same; only the texture changes).

You are now asked to code the nutrient class hierarchy, taking into account the following constraints:

  1. At least one of the constructors of the nutrient subclasses must have the same parameters (and in the same order) as that of the superclass.
  2. The drawing and updating methods of the nutrient subclasses remain identical, in their implementation, to what was coded in the Nutrient class. However, they must be able to be redefined later in the subclasses and thus act polymorphically.

    [Question Q3.2]: What does this constraint imply for the definitions of drawOn and update? Answer this question in your REPONSES file and make the suggested changes.

  3. Configuration data related to a Nutrient (super-class) can now be viewed as default data: if you open the configuration file app.json, you will see that there are more specific entries for the nutrient subtypes "nutrient A" or "nutrient B". If the default values don’t really serve a purpose, which is the assumption we’ll make here, we must ensure that the getConfig method is not explicitly defined at the Nutrient class level. Instead, it will be defined in the subclasses, where it must return the parameters specific to the subclass NutrientA or the subclass NutrientB.

[Question Q3.3]: How should the getConfig method be coded in the class hierarchy to satisfy this constraint? Answer this question in your REPONSES file and make the suggested modifications/code changes.

[Question Q3.4]: What allows the graphical display to use different textures (the colors for NutrientA and NutrientB are not the same) and the growth to depend on different conditions, without modifying the drawOn and update methods in the subclasses? Answer this question in your REPONSES file.

The target used to compile and run this part is nutrientTest. You must uncomment it when the time comes in the CmakeLists.txt. Note that Ctrl-F allows you to search within the QtCreator editor (to find where nutrientTest is located, for example).

Test 5: Nutrients (differentiation)

The NutrientTest test has been adapted for this step: 'Shift N' generates NutrientA and the 'N' key generates NutrientB.

Run this test as you did in the previous step, create two types of nutrients, and then "play" with the temperature conditions; the two types of nutrients should then grow under different temperature conditions:

Automatic Nutrient Generation

The NutrientGenerator class

It may be more practical and realistic to allow nutrient sources to be generated automatically at regular intervals. For simplicity, you can assume that automatic generation occurs only in the current box (this allows you to observe this process in each box from the moment it starts).

Create a class NutrientGenerator featuring a counter of type sf::Time, measuring the time elapsed since the previous generation of a nutrient source. The default constructor will initialize this time to sf::Time::Zero.

To update this counter over time, the NutrientGenerator class must have a method update(sf::Time dt) that manages the evolution of its instances at each time step dt.

The update method will implement the following algorithm:

  1. increment the counter by dt;
  2. if its value exceeds the threshold sf::seconds(getAppConfig()["generator"]["nutriment"]["delay"].toDouble())
    1. reset it to zero;
    2. call the function bernoulli (defined in Random/Random.[hpp][cpp]) passing it as a parameter the probability of generating a nutrient of type A (use getAppConfig()["generator"]["nutrient"]["prob"].toDouble() as the value of this probability). If this call returns 1, a NutrientA will be generated; otherwise, a NutrientB;
    3. Add this nutrient source to the current box by placing it randomly according to a normal distribution with a mean of environment_size/2 and a variance of environment_size/4 * environment_size/4

Finally, add an attribute of type NutrientGenerator to your Laboratory class, which will allow you to generate nutrient sources in the current culture dish.

[Question Q3.5] What modification must be made in Laboratory and in which method to allow the generator to effectively generate nutrient sources in its current culture dish? Answer these questions in your REPONSES file.

Finally, add a reset method to the NutrientGenerator class to reset its timer to sf::Time::Zero.

The reset method in Laboratory will call this method to start from scratch when you want to begin another simulation.

Test 6: Automatic generation of two types of nutrients

The application's configuration file app.json allows you to adjust the pause time between two spontaneous nutrient generations (["generator"]["nutrient"]["delay"]). For example, set this value to 2.5. Then run the nutrientTest target; you should see nutrient sources (sometimes blue, sometimes yellow) spontaneously appear in the environment, as shown in the video below:

By adjusting the temperature of the plate, you should have been able to observe different growth thresholds for the two types of nutrients. If you did everything correctly, you should be able to influence the probability of generating one type of nutrient over another. This is determined by the parameter ["generator"]["nutrient"]["prob"]: with a probability of 0, you should only get "blue" nutrients, and at the other extreme—that is, 1—only "yellow" nutrients. You should also be able to see nutrient sources generated at varying intervals by adjusting the parameter ["generator"]["nutrient"]["delay"].

[Question Q3.6] What changes do you need to make to the program to cap the number of nutrient sources at a value that can be configured via the .json files? Answer these questions in your REPONSES file and implement this specification. Verify using the previous test that it works as intended.


Back to the project description (Part 3) Next module (part 3.2)