Projet : étape 1
Contours circulaires

But: Implémentation d'une classe de base représentant des contours circulaires.

Concepts nécessaires: classes d'objets, constructeurs/destructeurs, surcharge d'opérateurs.

Ces concepts sont expliqués dans les cours 16 à 19. Les exercices de la série 19 contiennent du matériel proche de ce que vous devez programmer.

Fichiers utiles: partie1.zip

Indications pour la rédaction du fichier REPONSES: voir le forum.


Mise en place

Les indications ci-dessous présupposent que vous avez créé le répertoire cpp/projet. Adaptez les commandes à l'endroit où se trouve le projet chez vous.

Copiez l'archive fournie partie1.zip dans votre répertoire cpp/projet et décompressez-la à l'aide de la commande unzip partie1.zip (tapez man unzip dans un terminal pour plus de détails sur cette commande). Si vous travaillez sur vos propres machines:

Dans QtCreator, configurez ensuite le projet en suivant scrupuleusement les instructions données ici : il s'agit d'ouvrir le fichier CMakeLists.txt du répertoire src/ et des instructions qui suivent en mettant le dossier build au même niveau que les dossiers src ou res. Une fois le projet configuré, vous devriez pouvoir le compiler et l'exécuter. L'exécution doit produire la sortie suivante dans la fenêtre «Application output»:
	===============================================================================
	All tests passed (60 assertions in 9 test cases)
      
La signification de cette exécution vous sera expliquée un peu plus bas.

L'archive fournie contient :

Pour augmenter la modularité de votre projet, le prototypage des méthodes et la déclaration des attributs devront se trouver dans un fichier avec l'extension .hpp tandis que leurs définitions se trouveront dans un fichier avec l'extension .cpp.

À propos de la compilation, quelques indications utiles :
  1. Comment éviter les inclusions multiples :

    Les classes que vous allez programmer dans le projet vont se combiner de façon relativement complexe et vous allez parfois devoir inclure de nombreux fichiers .hpp existants au début d'un nouveau fichier .hpp. Si des fichiers ont été inclus de façon redondante dans ces différents .hpp, le compilateur réagira par un message d'erreur. Pour éviter cette situation, il est nécessaire de mettre au début de chaque fichier monfichier.hpp la directive :
    #pragma once
    
  2. Convention de nommage pour les fichiers :

    Les fichiers porteront les mêmes noms que les classes qu'ils contiennent : par exemple, les fichiers Lab.[hpp][cpp] contiendront le code relatif à la classe Lab.

    Il ne s'agit bien sûr que d'une convention.

    L'environnement des salles de TP à la particularité d'être insensible à la casse : Vec2d.hpp sera donc interprété comme vec2d.hpp (commençant par un petit "v"). Inclure le fichier vec2d.hpp sera donc interprété comme correct par le compilateur, même si votre fichier s'appelle Vec2d.hpp. Ce n'est pas le cas dans un environnement Unix classique. Faites donc attention à la cohérence car votre projet doit pouvoir s'exécuter partout.
  3. Compilation dans un terminal (Linux, MacOs):
    Le matériel fourni est intégrable à QtCreator dans lequel vous pouvez compiler et exécuter. Sous Linux et MacOs, vous pouvez aussi parfaitement l'utiliser en ligne de commande comme indiqué dans ici.

Modules à programmer

Cette partie du projet ne comporte qu'un seul module (volet).

Il s'agit ici de mettre en pratique vos premières connaissances de l'orienté-objet en programmant une classe utilitaire représentant des contours circulaires dans un espace à deux dimensions.

L'idée est que les entités du programme (bactéries, sources de nutriments etc.) vont évoluer dans un espace à deux dimensions.

Ces entités seront amenées à se rencontrer et il faudra pouvoir tester ce fait de façon simple : par exemple, si une bacterie rencontre une source de nutriments elle va pouvoir en consommer. Il est donc important de pouvoir tester que la rencontre a lieu.

Tester la collision/rencontre de deux objets peut être très complexe selon leurs formes effectives. Nous allons dans le cadre de ce projet, utiliser une simplification consistant à approximer la forme de toute entité au moyen d'un contours circulaire. Les tests de collision se feront alors simplement par rapport à ces contours, comme l'illustre l'image ci-dessous :

item Le test de collision entre une bactérie et une source de nutriments
est simplifiée en approximant chacune de ces entités
au moyen d'un contour circulaire (cercles en rouge).

La classe CircularBoundary qu'il vous est demandé de programmer permet de représenter les contours circulaires utilisés pour les tests de collisions.

Vous allez être amené à créer une classe et à la doter de plusieurs méthodes. Pour tester ces méthodes, du matériel est fourni. Il est décrit dans la section sur les «Programmes de test». Nous vous invitons à prendre connaissance de ce matériel dès que vous codez vos premières méthodes et à essayer de l'utiliser pour tester votre code au fur et à mesure que vous progressez.

Un objet de type CircularBoundary est caractérisé par  :

Vous doterez la classe CircularBoundary des membres suivants :

[Question Q1.2] Quelle surcharge choisissez-vous pour les opérateurs qu'il vous a été demandé de coder (interne ou externe) et comment justifiez-vous ce choix ? Réfléchissez et répondez à cette question dans votre fichier REPONSES.

[Question Q1.3] Quels arguments de méthodes, parmi celles qu'il vous a été demandé de coder ci-dessus, vous semble t-il judicieux de passer par référence constante ? Répondez à cette question dans votre fichier REPONSES.

[Question Q1.4] Quelles méthodes parmi celles que l'on vous a demandé de coder vous semble t-il judicieux de déclarer comme const ? Répondez à cette question dans votre fichier REPONSES.

Les tests à utiliser pour valider votre implémentation de la classe CircularBoundary sont décrits dans la section «Programmes de test» et en particulier dans la section «Test 1».

Programmes de test

Pour tester cette partie du projet vous disposez :

  1. de la cible circularBoundaryTest définie dans le fichier partie1/src/CMakeLists.txt et qui fait appel à plusieurs fichiers;

  2. Cette cible permet de lancer le programme de test tests/UnitTests/CircularBoundaryTest.cpp. Ce programme fait appel aux différentes fonctionnalités que vous avez codées dans la classe CircularBoundary pour contrôler qu'elles sont correctes.

Jetez petit coup d'oeil au fichier CMakeLists.txt pour voir comment est définie cette cible:

  add_executable(circularBoundaryTest ${TEST_DIR}/UnitTests/CircularBoundaryTest.cpp ${CB_SOURCES} ${CACHE_SOURCES})
  configure_sfml_executable(circularBoundaryTest ....)
permettant de lancer tous les tests CircularBoundaryTest.

Afin de comprendre le principe de fonctionnement de ces tests, ouvrez le fichier src/tests/UnitTests/CircularBoundaryTest.cpp pour l'examiner.

Ce programme de test utilise un framework particulier, appelé Catch, permettant de faire ce que l'on appelle dans le jargon de la programmation des tests unitaires. Il s'agit en fait d'écrire des scénarios de tests pour différentes fonctionnalités codées.

Nous vous expliquons ci-dessous le principe de fonctionnement de ces tests.

Principe de fonctionnement des tests unitaires (non graphiques)

Le programme de test fourni dans CircularBoundaryTest.cpp définit un scénario (« test case ») permettant de tester les fonctionnalités liées à la détection de collision en faisant appel aux méthodes que l'on vous a demandé de coder dans la classe CircularBoundary.

Le scénario contient un certain nombre de messages (étiquetés par les mots-clés GIVEN, THEN ou AND_THEN) qui vont décrire les conditions de déroulement des différents tests effectués et leur résultats. Ces messages ne s'afficheront qu'en cas d'échec d'un test.

Si tous les tests d'un scénario se déroulent correctement, vous verrez simplement s'afficher un message ressemblant à ceci :

===============================================================================
All tests passed 

Les tests consistent à vérifier un certain nombre d'assertions exprimées au moyen de fonctionnalités telles que CHECK et CHECK_FALSE.

Examinons par exemple le premier test du scénario (lignes 35 à 50).

Ce test commence par construire deux contours circulaires identiques (b1 et b2 de position (1,1) et de rayon 2), ils sont supposés être en collision si votre code est correct (c'est ce qui est affiché au moyen du THEN). Pour tester que votre code fonctionne correctement pour ce cas, il faut procéder à quatre CHECK :

  1. que b1 est en collision avec b2;
  2. que b2 est en collision avec b1;
  3. que b1 & b2 retourne true;
  4. et que b2 & b1 retourne true.

Vous l'aurez compris, un CHECK réussit si la condition qui lui est passée en argument retourne true (de façon analogue un CHECK_FALSE réussit si la condition qui lui est passée en argument retourne false).

Supposons que votre méthode isColliding soit mal codée, le lancement du programme de test CircularBoundaryTest.cpp affichera :

-------------------------------------------------------------------------------
Scenario: Collision/IsInside with CircularBoundary
     Given: Two identical bodies
      Then: they collide
-------------------------------------------------------------------------------
src/Tests/UnitTests/CircularBoundaryTest.cpp:26
...............................................................................

src/Tests/UnitTests/CircularBoundaryTest.cpp:37: FAILED:
  CHECK( b1.isColliding(b2) )
with expansion:
  false

src/Tests/UnitTests/CircularBoundaryTest.cpp:38: FAILED:
  CHECK( b2.isColliding(b1) )
with expansion:
  false

Le test affiche donc le nom associé au scénario, les messages associés à GIVEN et THEN pour indiquer ce que l'on a en entrée ("Two identical bodies") et ce qui est supposé être vrai dans ce cas ("they collide") puis la liste des assertions échouées et leurs lignes.

Le lancement de la cible CircularBoundaryTest devrait produire la sortie suivante :

CircularBoundary: position = (1, 1), radius = 2
===============================================================================
All tests passed (42 assertions in 1 test case)
  

Note: dans QtCreator cette sortie est visible dans la fenêtre «Application output»

Ceci indique que 42 assertions (CHECK ou CHECK_FALSE ) ont été lancées avec succès dans le cadre d'un seul scénario ("1 test case").

Notez que dans le cas où les fonctionnalités testées ne sont pas présentes dans votre code, le lancement du test provoquera des erreurs de compilation.

Par exemple, supposez que isColliding (prenant en argument un CircularBoundary) n'est pas codée chez vous, vous aurez alors un message d'erreur tel que

error: no matching function for call to 'Body::isColliding(Body&)'
             CHECK(b1.isColliding(b2));
Si vous voulez tester les méthodes au fur et à mesure que vous les codez, ce qui est recommandé, il vous suffit de commenter les CHECK ou CHECK_FALSE qui font appel à des méthodes non encore codées.

Test 1 : Fonctionnalités de CircularBoundary

Les tests sont numérotés globalement dans tout le projet. Il s'agit ici du premier test du projet. Chaque test est un élément noté.

Le lancement de la cible CircularBoundaryTest devrait produire la sortie suivante :

CircularBoundary: position = (1, 1), radius = 2
===============================================================================
All tests passed (42 assertions in 1 test case)

Le message CircularBoundary: position = (1, 1), radius = 2 doit en outre s'afficher pour montrer que l'exécution de la ligne 44 s'exécute correctement (c'est à dire que votre surcharge de l'opérateur << fait bien ce que l'on attend d'elle).


Retour au document principal