Projet : étape 3.1
Nutriments différenciés et générés automatiquement

Buts : Différencier les sources de nutriments et permettre leur génération automatique.

Retouches aux liens d'héritage

Avant d'introduire différents types de nutriments, nous allons revoir un peu la conception existante à la lumière des nouvelles notions vues en cours : à savoir, l'héritage et le polymorphisme.

Nous allons le voir en détail un peu plus tard dans le semestre, mais en C++ il est possible qu'une classe hérite de plusieurs super-classes. Le principe quant à l'héritage lui-même reste fondamentalement le même. Pour spécifier un héritage multiple (ici une classe d'objets dessinable et évoluant au cours du temps), il suffit de lister les héritages en les séparant par des virgules :
class MaClasse : public Drawable, public Updatable
(la classe MaClasse hérite de Drawable et de Updatable)

[Question Q3.1 ] : les classes fournies dans le répertoire Interface, à savoir Drawable et Updatable, fournissent deux méthodes polymorphiques drawOn et update. Quelles classes de votre conception actuelle serait-il bon de faire hériter de ces sous-classes ? Quel plus cela amène t-il à la conception ? Répondez à ces questions dans votre fichier REPONSES.

Faites les modifications ainsi suggérées.

Pour le reste de votre conception, vous resterez attentif à faire hériter de Drawable et de Updatable, toutes les classes pour lesquelles il est pertinent de le faire. Il n'est bien sûr pas indispensable d'hériter systématiquement des deux.

Destructeurs virtuels

Vous avez appris récemment que dans une hiérarchie polymorphique il était conseillé de programmer les destructeurs comme vituels... pensez-y!

Hiérarchie de nutriments

On souhaite maintenant faire en sorte que la classe Nutrient se spécialise en deux sous-classes NutrientA et NutrientB.

Pour l'heure, ces deux sous-classes n'auront d'autre distinction que leur affichage graphique (les modalités de dessin restent les mêmes, seule la texture change).

Il vous est demandé maintenant de coder la hiérarchie de classes des nutriments en tenant compte des contraintes suivantes :

  1. Au moins un des constructeurs des sous-classes de nutriments doit avoir les mêmes paramètres (et dans le même ordre) que celui de la super-classe.
  2. Les méthodes de dessin et de mise à jour des sous-classes de nutriments restent identiques, dans leurs modalités, à ce qui a été codé dans la classe Nutrient. Elle doivent cependant pouvoir être redéfinies plus tard dans les sous-classes et pouvoir alors agir de façon polymorphique.

    [Question Q3.2] : Qu'implique cette contrainte au niveau des définitions de drawOn et update. Répondez à cette question dans votre fichier REPONSES et procédez aux modifications suggérées.

  3. Les données de configuration liées à un Nutrient (tout court) peuvent désormais être vue comme des données par défaut : si vous ouvrez le fichier de configuration app.json, vous verrez qu'il y a des entrées plus spécifiques pour les sous types de nutriments ["nutrients"]["A"] ou le ["nutrients"] ["B"]. Si les valeurs par défaut n'ont pas vraiment de raison d'être, ce que nous choisirons ici comme hypothèse, il faut faire en sorte que la méthode getConfig ne soit pas définie concrètement au niveau de la classe Nutrient. Elle le sera plutôt dans les sous-classes, où il faudra qu'elle permette de retourner les paramètres spécifiques à la sous-classe NutrientA ou à la sous-classe NutrientB.

[Question Q3.3] : Comment doit-être codée la méthode getConfig dans la hiérarchie de classes pour satisfaire cette contrainte ? Répondez à cette question dans votre fichier REPONSES et procédez aux modifications/codages suggérés.

[Question Q3.4] : Qu'est-ce qui fait que sans modifier les méthodes drawOn et update dans les sous-classes, l'affichage graphique peut faire usage de textures différentes (les couleurs pour NutrientA et NutrientB ne sont pas les mêmes) et la croissance dépend de conditions différentes ? Répondez à cette question dans votre fichier REPONSES.

La cible permettant de compiler et lancer cette partie est nutrientTest. Il faut la décommenter le moment venu dans le CmakeLists.txt. Notez que Ctrl-F permet de faire une recherche dans l'éditeur de QtCreator (pour chercher où se trouve nutrientTest par exemple).

Test 5 : Nutriments (différenciation)

Le test NutrientTest a été adapté pour cette étape : 'Shift N' permet de générer des NutrientA et la touche 'N' des NutrientB.

Lancez ce test comme vous l'avez fait à l'étape précédente, créez deux types de nutriments et "jouez" ensuite avec les conditions de températures, les deux types de nutriments doivent alors croître dans des conditions de température différentes :

Génération automatique des nutriments

La classe NutrientGenerator

Il peut être plus pratique et réaliste de permettre la génération automatique, à des espaces de temps réguliers, des sources nutriments. Par simplification, vous pouvez considérer que la génération automatique ne se fait que dans la boite courante (cela permet d'observer ce processus dans chaque boite, depuis son démarrage).

Créez une classe NutrientGenerator caractérisée par un compteur de type sf::Time, mesurant le temps écoulé depuis la précédente génération d'une source de nutriment. Le constructeur par défaut initialisera ce temps à sf::Time::Zero.

Pour faire évoluer ce compteur au cours du temps, il faut que la classe NutrientGenerator dispose d'une méthode update(sf::Time dt) gérant l'évolution de ses instances à chaque écoulement de pas de temps dt.

La méthode update va mettre en oeuvre l'algorithme suivant :

  1. augmenter le compteur de dt;
  2. si sa valeur dépasse le seuil sf::seconds(getAppConfig()["generator"]["nutrient"]["delay"].toDouble())
    1. le remettre à zéro;
    2. invoquez la fonction bernoulli (définie dans Random/Random.[hpp][cpp]) en lui passant en paramètre la probabilité de générer une nutriment de type A (prenez getAppConfig()["generator"]["nutrient"]["prob"].toDouble() comme valeur de cette probabilité). Si cet appel retourne 1 c'est un NutrientA qui sera généré sinon un NutrientB;
    3. ajouter à la boite courante cette source de nutriment en la plaçant aléatoirement selon une loi de distribution normale de centre taille_environnement/2 et de variance taille_environnement/4 * taille_environnement/4

Pour finir, ajoutez à votre classe Laboratory un attribut de type NutrientGenerator qui va permettre d'y générer des sources de nutriments dans la boite courante.

[Question Q3.5] Quelle modification doit être faite dans Laboratory et dans quelle méthode pour permettre au générateur d'effectivement générer des sources de nutriments dans la boite de culture courante ? Répondez à ces questions dans votre fichier REPONSES.

Pour finir ajoutez à la classe NutrientGenerator une méthode reset permettant de réinitialiser son compteur de temps à sf::Time::Zero.

Le reset de Laboratory fera appel à cette méthode pour repartir de zéro lorsque l'on souhaite recommencer une autre simulation.

Test 6 : Génération automatique de deux types de nutriments

Le fichier de configuration de l'application app.json permet de jouer sur le temps de pause à observer entre deux générations spontanées de nutriment (["generator"]["nutrient"]["delay"]). Attribuez, par exemple, la valeur 2.5 à cette donnée. Lancez ensuite la cible nutrientTest; vous devriez voir apparaître spontanément des sources de nutriments (tantôt bleues, tantôt jaunes) dans l'environnement, comme dans la vidéo ci-dessous :

En jouant sur la température de la boite de culture, vous deviez pouvoir observer des conditions limites de croissance différentes pour les deux types de nutriments. Si vous avez fait les choses correctement, vous pourrez influer sur la probabilité de générer un type de nutriment plutôt qu'un autre. C'est donc le paramètre ["generator"]["nutrient"]["prob"] qui conditionne cela : pour une probabilité à 0 vous ne devriez avoir que des "bleues" et pour l'autre extrême, c'est-à-dire 1, que des "jaunes". Vous devriez aussi pouvoir voir se générer les sources de nutriments à intervalles de temps plus ou moins rapprochés en jouant sur le paramètre ["generator"]["nutrient"]["delay"].

[Question Q3.6] Quelles modifications devez-vous apporter au programme pour faire plafonner le nombre de sources de nutriments à une valeur paramétrable via les fichiers .json? Répondez à ces questions dans votre fichier REPONSES et implémentez cette spécification. Vérifiez au moyen du test précédent que cela fonctionne comme souhaité.


Retour à l'énoncé du projet (partie 3) Module suivant (partie 3.2)