Projet : étape 2.1
Animalerie

Buts: Mettre en place le lieu de vie des futurs hamsters

Préambule

Il s'agit lors de cette étape d'aboutir à un premier programme graphique nous permettant de visualiser le «laboratoire» simulé (classe Lab) et les différentes entités qui y évoluent au cours du temps (élevage de hamsters, sources de nourriture etc.).

Notre programme va désormais avoir pour composant essentiel une classe Application; le "noyau de simulation" capable de faire du graphisme et de gérer des événements, fourni dans src/Application.[hpp/cpp].

La classe Application joue le même rôle que la classe du même nom fournie dans le corrigé avancé du mini-projet graphique. Plus précisément, cette classe dispose  : C'est par le biais de l'attribut de type Lab que ce fait lien entre le noyau de simulation fourni et ce que vous codez. La classe Application dispose à ce propos d'une méthode handleEvent (ligne 520 de Application.cpp) qui permet à la simulation de réagir à des touches du clavier: par exemple la touche Esc permet de mettre fin à la simulation (fermeture de la fenêtre). Prenez note de l'existence de cette méthode pour la suite.

Paramètres de simulation

Comme évoqué lors de l'étape précédente, les paramètres conditionnant la simulation sont fournis dans des fichiers res/appX.json.

Chaque test que vous lancerez, graphique ou pas, pourra être paramétré par le fichier de configuration souhaité, par exemple, en ligne de commande  :

./labTest app.json
permettra de lancer la cible labTest en utilisant app.json comme fichier de configuration.
Dans QTCreator vous pouvez passer les arguments à votre programme (app.json dans l'exemple ci-dessus) en procédant tel qu'indiqué ici: https://iccsv.epfl.ch/series-prog/install/installation-tutorial.php#commandLineArgs_in_qtcreator.
Si l'on ne met rien comme argument, le fichier .json utilisé sera celui dont le nom correspond à la constante DEFAULT_CFG définie dans Utility/Constants.hpp. Pour cette étape cette valeur par défaut est app.json mais libre à vous de le changer.
La plupart des paramètres ont été anticipés, mais libre à vous d'en rajouter par la suite dans Config.[hpp][cpp] et les fichiers .json.

Pourquoi pas de simples constantes ?

L'inconvénient d'utiliser de simples constantes globales est qu'à chaque fois que l'on veut modifier leur valeur, il faut recompiler et relancer le programme. L'utilisation des fichiers .json permettra de changer des valeurs de paramètres pendant que la simulation se déroule ce qui facilitera certaines observations. Nous y reviendrons dès que le monde simulé sera «habité» :-)

Il s'agit maintenant de commencer à modéliser le lieu de vie de nos futurs hamsters. Une classe Lab (pour laboratoire) est un choix plutôt naturel ici. Nous partirons de l'hypothèse qu'un Lab contient forcément un certain nombre de cages (classe Cage) dans lesquelles seront élevés les hamsters. C'est à la mise en oeuvre de cette première classe que nous allons nous atteler dans un premier temps.

Vous coderez toutes les classes suggérées ci-dessous dans le répertoire src/Env.

La classe Cage

Losrque vous serez prêts à lancer votre première compilation, référez vous aux indications sous "Test 3" pour voir quelle cible décommenter.
Une cage sera caractérisée par :
  • la position de son centre (un Vec2d);
  • sa largeur;
  • sa hauteur;
  • quatre murs de même épaisseur;
  • l'épaisseur des murs.
Image: cage du laboratoire

Un mur sera caractérisé par une paire de Vec2d représentant respectivement la position de son coin inférieur droit, et celle de son coin supérieur gauche :

Image: représentation d'un mur

Vous définirez l'alias de type suivant :

typedef std::pair <Vec2d, Vec2d> Wall; //bottom right corner, top left corner

Pour simplifier les calculs, prenez des murs de mêmes tailles positionnés comme suit les uns par rapport aux autres :

Image: positionnement des murs
Vous coderez la classe Cage dans les fichiers src/Env/Cage.[hpp][cpp] et définirez l'alias de type Wall dans Cage.hpp avant le prototype de la classe Cage.

Vous doterez la classe Cage des méthodes utilitaires suivantes :

La classe Lab

Le laboratoire sera pour le moment simplement caractérisé par l'ensemble des cages qu'il contient. Sa largeur et sa hauteur valent toutes deux getAppConfig().simulation_lab_size. Vous couvrirez complètement sa surface de cages comme dans la figure ci-dessous :

Image: surface couverte de cages
Codez l'ensemble des cages comme un ensemble de Cage* si vous souhaitez être conforme aux tests fournis. Vous êtes libre de la structure de données à adopter mais il doit être à tout moment possible de connaître le nombre de cages par rangées (au travers d'un getter, voir plus bas).

Les méthodes suivantes sont à prévoir pour la classe Lab  :

Libre à vous par la suite d'ajouter toute autre méthode vous semblant utile.

[Question Q2.1] Quelle précaution faut il prendre lors de la destruction des cages pour éviter les fuites de mémoire ?

[Question Q2.2] On souhaite ne pas permettre la copie d'un Lab. Quelle(s) solution proposez-vous pour satisfaire cette contrainte? Pourquoi le respect de cette contrainte est-il souhaitable ?

Réfléchissez à ces questions et répondez-y dans votre fichier REPONSES. Apportez les modifications nécessaires à votre code.

Dessin d'une cage

Revenons donc maintenant à la méthode Lab::drawOn. Cette dernière doit maintenant dessiner chacune des cages, ce qui présuppose qu'une cage doit être dessinable (disposer d'une méthode de dessin spécifique).

Notez que vous disposez d'une méthode utilitaire buildRectange dans Utility.hpp permettant de dessiner un rectangle SFML texturé.

Un mur pourra donc être dessiné comme suit:

sf::RectangleShape rectangle = buildRectangle(coordTopLeft, coordBottomRight, texture);
target.draw(rectangle);

En guise de texture vous pourrez prendre la texture configurable dans le fichier app.json (sous l'étiquette ["lab"]["fence"]) et qui est accessible par la tournure :

				   
&getAppTexture(getAppConfig().simulation_lab_fence)

[Question Q2.3] Dans quelles classes les lignes de code relative au dessin d'une cage doivent elle apparaître selon vous ? Justifiez vos choix dans votre fichier REPONSES.

Test 3 : Fonctionnalités de Cage

Pour tester le travail fait jusqu'ici, vous utiliserez pour commencer un test non graphique. Ce test fourni dans src/Tests/UnitTests/CageTest.cpp.

Le fichier CMakeLists.txt fourni permet de lancer la compilation du test par le biais de la cible cageTest. N'oubliez pas de décommenter cette cible dans le fichier CMakeLists.txt (lignes 94, 95 et les lignes 136, 137) et d'exécuter Build >Run Cmake lorsque vous êtes prêt à compiler/tester.

Une exécution correcte de votre programme devrait produire la sortie suivante :

===============================================================================
All tests passed (50 assertions in 4 test cases)

Test 4 : Un laboratoire plein de cages

Pour tester le travail fait jusqu'ici, vous utiliserez le test fourni dans src/Tests/GraphicalTests/LabTest.cpp. La classe LabTest hérite de Application. Elle a de ce fait comme attribut un objet de type Lab sur lequel seront invoquées en boucles les méthodes de dessin et de mise à jour que vous avez ébauchée. Les touches R et Shift-R du clavier (gérée par Application::handleEvent) se traduit en fait par un appel à la méthode Lab::reset que vous venez de programmer.

La classe LabTest définit aussi dans sa méthode onEvent des contrôles plus spécifiques pour ajouter un hamster et des granulés dont il pourra plus tard se nourrir. :

Les appels aux méthodes à venir sont commentés pour le moment.

Le fichier CMakeLists.txt fourni permet de lancer la compilation du test par le biais de la cible labTest. N'oubliez pas de décommenter cette cible dans le fichier CMakeLists.txt (lignes 100, 101 et lignes les lignes 142, 143) et d'exécuter Build >Run Cmake lorsque vous êtes prêt à tester.

Vous devriez voir s'afficher ceci pour commencer :

Image: labo initial

car le fichier app.json est configuré de sorte à avoir un laboratoire ne contenant qu'une seule cage. Le nombre de cages par rangée devrait d'ailleurs s'afficher et valoir la valeur par défaut (1) (voir ce qui indiqué par la flèche rouge).

Contrôle interactif du nombre de cages

Le noyau de simulation fourni prévoit aussi que certains éléments essentiels soient contrôlables par des touches, typiquement:

Si vous ouvrez le fichier fourni Application.hpp, vous constaterez (ligne 160) qu'un type énuméré modélise ces contrôles. La méthode Application::handleEvent (ligne 520 de Application.cpp) est programmée de sorte à ce que la touche Tab permette de basculer d'un contrôle à l'autre et une fois un contrôle donné choisi, les touches 8 et 9 permettent de faire varier les valeurs du paramètre contrôlé. Ici, si le paramètre contrôlé est le nombre de cages par rangées, les touches 8 et 9 appeleront respectivement vos méthodes Lab::removeCageFromRow et Lab::addCageToRow pour faire varier le contrôle «CAGE» (nombre de cages par lignes).

Vérifiez que:

← Ici le fichier app.json de base est utilisé, les touches 8 et 9 sont chacune appuyées 5 fois
(la dernière fois est inopérante car les critères sur le nombre de cages ne sont alors plus respectés)

Vous pouvez aussi jouer avec les valeurs du fichier app.json. Vérifiez alors que:

Notez qu'il est parfois pratique de pouvoir retrouver les valeurs des paramètres tels qu'initialement fournis dans l'archive. Pour vous en assurer vous pouvez en remplacer le contenu de appX.json par celui de même nom fourni dans res/orig/ (ce répertoire contient une sauvegarde de l'état des fichiers de configuration tels que fournis au départ).

Aller au module suivant (partie 2.2)
Retour à l'énoncé principal (partie 2)