Goal: Set up the environment in which the bacteria will grow.
The aim of this stage is to produce an initial graphical programme that allows us to visualise culture boxes and place nutrient sources within them that can grow depending on the temperature.
We will now proceed to code the CultureDish and Laboratory classes.
The bacteria we will be simulating will be confined within a culture dish. The conditions inside this dish, such as temperature and nutrient levels, will determine how the bacteria living there develop.
A CultureDish class is required, and here are some attributes that would naturally be included:
Build > Run Cmake
in QtCreator, if you want it to be correctly taken into account during the next compilation.
To model a set (of bacteria or nutrient sources), you can use a dynamic array and store pointers to the objects in it. Remember that a dynamic array can be traversed by iterating over a set of values :
for (const auto& bacterie : lesBacteries)ou
for (auto& bacterie : lesBacteries)
The following methods should be defined for the CultureDish class :
//implement the bacteria behaviour here:or
//code to add nutrients here:to remind you of the task to be coded later
Upon creation, a culture dish will have a temperature set to the value returned by the expression getAppConfig()["culture dish"]["temperature"]["default"].toDouble() which will be explained in the next step (the inclusion of Config.hpp is necessary).
[Question Q2.1] In order to represent the culture dish graphically and to define its boundary – for example, to prevent bacteria from escaping – we want to treat the dish as a circular boundary (which will, of course, have a position within the two-dimensional environment used for our simulations). How can we use the CircularBoundary class to model this ? Answer this question in your REPONSES file, then provide the CultureDish class with a constructor that takes a position (a Vec2d) and a radius (double) as parameters.
[Question Q2.2] Which methods do you think it would be sensible to declare as const? Answer this question, justifying your choices, in your REPONSES file.
[Question Q2.3] We do not want to allow the copying or assignment of a CultureDish. What solution(s) would you propose to satisfy this constraint? Think about it, answer this question in your REPONSES file and implement your code accordingly.
[Question Q2.4] The question Q2.3 suggests that each simulation will focus on dishes, each containing its own set of bacteria and nutrients. Furthermore, in the context of this project, it makes little sense to keep bacteria alive without linking them to a dish. A CultureDish can therefore be considered responsible for the lifespan of the bacteria and nutrients that will be created in the simulation. How does this affect the destruction of a culture dish ? Can destruction be carried out using an existing method of the CultureDish class. Answer these questions in your REPONSES then code the CultureDishdestructor.
Now that our culture dish has a radius and a position, we can return to the CultureDish::drawOn method to make it do something. Complete the code for this method so that it draws the outline of the dish.
auto border = buildAnnulus(position, rayon, couleur, epaisseur); target.draw(border);The buildAnnulus function is available in Utility.[hpp][cpp]
Before we can test these new developments, we will need to take our design a step further, in the direction suggested in the preamble.
The Laboratory class will model the platform on which culture dishes are placed. The basic idea is that only one culture dish will be visible at a time, and that the user will subsequently be able to switch between dishes using control keys.
To begin with, a Laboratory will have as its main characteristics a set of CultureDish (vector), as well as the index of the current dish : the one being visible. It must not be possible to copy it.
For this particular class, the main methods are specified in a fairly detailed and precise manner. The aim is to ensure compatibility with the provided simulation engine. Remember that it is via a Laboratory-type attribute that the link is established between your code and the simulation engine (Application). You will therefore need to provide the following methods :
[Question Q2.5] At present, updating a Laboratory simply involves updating its culture boxes, and drawing it means drawing the current index box, as there is no point in drawing what cannot be seen. How would you propose to code the body of the Lab::drawOn and Lab::update methods? Answer this question in your REPONSES file.
Next, provide your Laboratory class with a constructor that positions the current index cell in the centre of the graphics window associated with the Laboratory, with a diameter occupying 95% of the width of that window. This constructor will provide the Laboratory with a fixed number of boxes, and the current index will point to the first box (the one with index zero). The number of boxes is specified using the function getShortConfig().culture_dishes_number (requiring the inclusion of Config.hpp). This function will be explained in detail in the next step; for now, it is sufficient to know that it returns the value of the constant defined on line 78 of Config.hpp, i.e. 3. This value can be changed as you wish.
To test your code, a graphical application is provided in src/Tests/GraphicalTests/NutrientTest.[hpp][cpp].
The provided CMakeLists.txt file allows you to run the test compilation using the nutrientTest target
Set up the 'R' key. If you have coded everything correctly so far, running this test should allow you to see the cultivation box (white ring) appear in the part of the graphical interface reserved for this purpose :
If you open the file Application.cpp, you will see that the necessary mechanisms are already in place. For example, the methods nextDish and previousDish are indeed called by the method handleEvent. Other parameters besides the current dish can also be controlled via the interface; the temperature of the current dish, for example.
When you run nutrientTest, the items displayed in the menu at the top right are the controllable parameters, and you can switch between them using the Tab key or the Q key. The parameter displayed in red is the one that can be adjusted via the graphical interface. The explanatory banner at the bottom right indicates that the 'PgUp' or X keys, as well as the 'PgDn' /Y keys, allow you to increase or decrease the value of the selected parameter (the one in red).
Use these keys to navigate to the Dish id parameter. Then check that your nextDish and previousDish methods have been coded correctly. For each possible identifier value, an empty box should be displayed.