Buts : Simuler certains comportements génériques des bactéries (évolution au cours du temps, consommation de nutriments, fin de vie etc.)
Il s'agit maintenant de reprendre et compléter la classe Bacterium ébauchée précédemment.
Les bactéries pourront se décliner dans notre projet sous différentes formes.
Nous considérerons cependant que toutes les bactéries quelles qu'elles soient :
sont dessinables (méthode drawOn) et simulables (méthode update) et sont des corps circulaires.
ont une couleur (de type MutableColor).
ont une direction de déplacement (un vecteur unitaire modélisé grâce au type Vec2d).
peuvent être dans un état d'abstinence (dans ce cas elle ne consomment pas de nutriments).
sont dotées d'un niveau d'énergie (de type Quantity).
ont un ensemble de paramètres (numériques) mutables.
peuvent se déplacer mais nous ne savons pas définir cette fonctionnalité de façon générique pour une bactérie quelconque. La méthode de déplacement permettra de calculer les nouvelles position et direction de la bactérie, après écoulement d'un pas de temps dt (passé en paramètre de la méthode gérant le déplacement : void move(sf::Time dt));
sont dotées d'une fonctionnalité permettant de savoir si elles sont mortes (niveau d'énergie inférieur ou égal à zéro).
std::map<string, double> notes; // string = type de la clé et double = type de la valeur notes["informatique"] = 5.5; // on associe à la clé "informatique", la valeur 5.5 // la clé (indice généralisé) est le nom du cours et 5.5 la valeur associée à cette clé.On peut donc représenter l'ensemble des paramètres mutables comme une std::map<string, MutableNumber> ce qui permettra d'associer à une clé (comme "speed"), une valeur numérique mutable.
Le constructeur d'une Bacterium doit pouvoir initialiser son niveau d'énergie, sa position, sa direction, son rayon et sa couleur à partir de valeurs passées en paramètre. Par défaut la bactérie ne sera pas en état d'abstinence et son ensemble de paramètres mutables vide.
[Question Q3.9] : Au vu de ce qui précède, comment proposez-vous de modéliser la classe Bacterium (héritage, attributs, méthodes , ...) ?
[Question Q3.10] : Quelles méthodes parmi celles suggérées pour une Bacterium devront vraisemblablement être virtuelles/virtuelles pures ?
Répondez à ces questions dans votre fichier REPONSES et complétez le code de la classe Bacterium (dans la mesure de ce que vous pouvez faire à ce stade) conformément à vos réponses.
Pour que les bactéries puissent effectivement intégrer l'environnement de simulation, il faut qu'elles fassent partie d'une CultureDish (et d'un Lab). Avant de poursuivre, inspirez-vous de ce que vous avez fait pour les nutriments pour compléter les classes Lab et CultureDish afin de permettre l'ajout de bactéries (toujours à la boîte courante).
Nous allons dans ce qui suit, nous intéresser à la mise en oeuvre des deux méthodes fondamentales de la classe Bacterium que sont drawOn (dessin) et update (évolution au cours du temps).
La mise en oeuvre des algorithmes associés nécessite l'utilisation de propriétés paramétrables de la bactérie comme : quelle quantité maximale de nutriments elle peut consommer à la fois ou combien de temps attend-elle pour consommer à nouveau des nutriments si elle vient de se nourrir, etc.?
Commençons donc à nous intéresser à l'accès à ces propriétés.
Comme pour les nutriments ces propriétés vont varier d'un type de bactérie à l'autre (une bactérie à flagelle unique peut consommer une quantité maximale de nutriments différente d'une bactérie à grappin par exemple).
A l'image de ce que nous avons fait pour les nutriments, il est donc intéressant de définir une méthode j::Value& getConfig() retournant de façon polymorphique les propriétés spécifiques à chaque sous-type de bactérie (getConfig() des bactéries à flagelle unique retournerait getAppConfig()["monotrichous"];, celle des bactéries à grappins getAppConfig()["twitching motility bacterium"]; etc.)
Supposons que b soit de type Bacterium*, b->getConfig() doit alors s'adapter automatiquement à la nature réelle de l'instance pointée par b (si b est un pointeur sur une bactérie à flagelle unique, b->getConfig() retournera getAppConfig()["monotrichous"]).
A partir de la valeur retournée par getConfig(), il devient alors possible d'accéder à des propriétés spécifiques: b->getConfig()["meal"]["max"].toDouble(); donnerait la quantité maximale de nourriture consommable par la bactérie à flagelle unique si b est un pointeur sur une bactérie à flagelle unique, et il retournerait la quantité maximale de nourriture consommable par la bactérie à grappin si b est un pointeur sur une bactérie à grappin.
[Question Q3.11] : La méthode getConfig est-elle virtuelle pure selon vous ? Répondez à cette question en justifiant dans votre fichier REPONSES.
Codez la méthode getConfig (vous pouvez anticiper une classe MonotrichousBacterium dans laquelle cette méthode serait redéfinie de façon appropriée).
Vous pourrez ensuite utiliser getConfig, au gré des besoins, pour doter la classe Bacterium de «getter utilitaires» tel que par exemple un getter retournant l'énergie dépensée à chaque pas de déplacement (associée à ["energy"]["consumption factor"]).
Une bactérie quelque qu'elle soit, pourra être dessinée de façon très basique comme un cercle.
auto const circle = buildCircle(position, rayon, couleur);
target.draw(circle);
où position est la position du centre du cercle (en l'occurence ce sera la position de la bactérie), rayon son rayon et couleur, sa couleur.
[Question Q3.12] : Comment retrouvez-vous la couleur SFML de la bactérie à partir de son attribut de type MutableColor ? Répondez à cette question dans votre fichier REPONSES.
Codez la méthode Bacterium::drawOn conformément à la description ci-dessus.
Inspirez-vous également de ce que vous avez fait pour les nutriments pour permettre l'affichage des bactéries et de leur énergie en mode «debug».
La méthode update(sf::Time dt) d'une Bacterium va mettre en oeuvre l'algorithme suivant :
[Question Q3.13] : Le fait qu'aucune méthode de déplacement concrète n'existe encore est-elle un frein à l'écriture de la méthode update ? Répondez à cette question en justifiant dans votre fichier REPONSES.
Programmez une méthode bool Laboratory::doesCollideWithDish(CircularBody const& body) retournant true si body est en collision avec la boîte de culture du Lab et false sinon.
Utilisez cette méthode pour coder l'étape 2 de l'algorithme ci-dessus.
Programmez une méthode void Laboratory::checkCollidingNutriment(Bacterium* bacterium) permettant à une bactérie donnée de savoir avec quel Nutriment* de sa boîte elle est en collision (S'il y a plusieurs candidats, le premier trouvé fera l'affaire!).
[Question Q3.14] : La classe CultureDish ne donne pas d'accès à sa collection de nutriments, comment procéder dans ce cas pour coder checkCollidingNutriment ? Répondez à cette question dans votre fichier REPONSES.
[Question Q3.15] : Pourquoi choisit-on ici de coder une méthode checkCollidingNutriment plutôt qu'une méthode retournant un Nutriment* en collision ? Répondez à cette question dans votre fichier REPONSES.
Utilisez la méthode checkCollidingNutriment pour coder l'étape 3 de l'algorithme ci-dessus.
La bactérie ne consommera de nourriture que si elle n'est pas en état d'abstinence et qu'il s'est écoulé le temps d'attente nécessaire avant sa dernière consommation de nourriture (introduisez les getters nécessaires pour l'accès à ces données de façon succincte).
Si elle consomme du nutriment, la bactérie prélève au plus la quantité maximale qu'elle peut consommer (ou qui est disponible). Le niveau d'énergie de la bactérie croît de la quantité de nutriments consommée.
Codez la méthode bacterium::update conformément à la description ci-dessus.
[Question Q3.16] : Quelle méthode de la classe CultureDish doit être modifiée pour permettre la simulation de l'évolution des bactéries ? Comment doit-elle être modifiée ? Répondez à ces questions dans votre fichier REPONSES et codez les éventuelles modifications suggérées.
Les bactéries en se déplaçant vont perdre de l'énergie. Il se peut donc qu'au terme d'un cycle de simulation, la bactérie atteigne une énergie nulle et doive donc mourir et disparaître de la boîte de culture.
De même, si une source de nutriments a été entièrement consommée par les bactéries, elle doit aussi disparaître de la boîte.
[Question Q3.17] Que devez-vous modifier et dans quelle classe pour faire en sorte que les bactéries et sources de nutriments de votre simulation meurent/disparaissent si leur énergie/quantité devient nulle ?
vect.erase(std::remove(vect.begin(), vect.end(), nullptr), vect.end());c'est une tournure qui vous sera expliquée dans un de nos prochains cours.
Mettez en oeuvre ces modifications dans votre programme.
Dotez enfin la classe Bacterium d'une méthode consumeEnergy(Quantity qt) décrémentant le niveau d'énergie de la bactérie de la quantité qt.
Le test fourni src/Tests/GraphicalTest/BacteriaTest.cpp permet de tester une partie de vos nouveaux développements (consommation de nutriments, test de collision avec les nutriments et disparition des bactéries/nutriments de niveau d'énergie/quantité nuls).
Ouvrez ce fichier et examinez-le. Vous verrez qu'un type de bactéries factices, héritant de Bacterium, y est défini. Cette classe redéfinit de façon minimale et concrète la méthode move de sorte à ce qu'elle fasse simplement perdre de l'énergie à la bactérie à chaque déplacement.
Lancez le test au moyen de la cible bacteriaTest. Vous aurez pris soin de la décommenter dans le fichier CMakelists.txt et d'avoir intégré ce nouveau matériel au moyen de Build > Run CMake. Avant de lancer le test, configurez la simulation pour :
puis créez une bactérie sur une source de nutriments au moyen de la touche 'M'. Vous devriez alors voir le niveau d'énergie de la bactérie croître et la source de nutriments s'amoindrir au fur et à mesure que la bactérie se nourrit. Au bout d'un moment, la bactérie aura tout consommé, comme elle ne peut pas encore se déplacer, elle va juste perdre de l'énergie à chaque cycle de simulation puis .. inexorablement mourir :'(
Si tout fonctionne correctement, la bactérie doit disparaître, ainsi que la source de nutriments entièrement consommée :
Jouez aussi sur le paramètre [simple bacterium]["meal"]["delay"] pour vérifier que votre bactérie ne consomme rien si le temps écoulé entre deux prises de nutriments n'est pas suffisamment important : augmenter ce paramètre doit induire des temps d'attente plus longs entre deux consommations successives de nutriments.