TD06 : Implémentation du Jeu (V1)

Maintenant que nous avons programmé les tests de la bibliothèque game, nous allons nous concentrer sur l'implémentation de cette bibliothèque, et plus précisément de l'implémentation dans les fichier game.c et game_aux.c de toutes les fonctions spécifiées respectivement dans game.h et game_aux.h.

La description de ces fonctions est également disponible dans la documentation officielle du jeu, générée avec l'outil Doxygen.

Préambule

Au cours de ce travail, gardez à l’esprit cette citation de Donald Knuth :

Premature optimization is the root of all evil (or at least most of it) in programming.

Ou en version longue :

There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.

Gardez à l’esprit également que votre code doit être maintenable, et qu’il devra s’adapter aux caprices du client (V2) !

Comme vous rentrez dans une phase de codage plus intensive, il devient important d’utiliser un environnement de développement plus ergonomique. Parmi les fonctionnalités qui vous feront gagner le plus de temps, on trouve : la mise en forme automatique de votre code, la vérification de votre code à la volée (ou linter), la complétion automatique, l’intégration d’un déboggeur, l’intégration de Git, ... Un IDE comme VS Code offre toutes ces fonctionnalités. En outre, il est impératif d'apprendre à vous servir efficacement des outils de debogage.

Exercice 0 : avant d'attaquer une nouvelle version du code

Avant d'attaquer une nouvelle version du code (la version v1), il vous est demandé de créer dans votre dépôt Git une branche v0 pour marquer cette version dans l'historique de Git. Ainsi, on utilisera la branche principale (main) pour développer la nouvelle version du code.

$ git branch v0             # création de la branche locale v0
$ git push -u origin v0     # publication de la branche v0 --> origin/v0

Nota Bene : Attention, la création de la branche v0 et sa publication ne doit être fait que par un seul utilisateur. Les autres utilisateurs se contenteront de récupérer la branche, comme ceci...

$ git pull                 # on synchronise le dépôt Git (y compris les branches distantes)
$ git branch -a            # on affiche toutes les branches (locales et distantes)
$ git switch v0            # si besoin, on bascule sur la branche locale v0
$ git switch main          # on revient dans la branche principale

Notez qu'il est également possible de continuer à améliorer vos tests en travaillant explicitement dans la branche v0. Ajoutons par exemple un nouveau test dans la branche v0 :

$ git switch v0
$ git add ...
$ git commit -m "add new test"
$ git push

Ce faisant, vous avez ajouté un nouveau test dans la branche v0, mais qui n'est pas pour l'instant disponible dans la branche principale. Afin de rappatrier les modifications apportées dans la branche v0 vers la branche principale, il va être nécessaire de réaliser une fusion de branches avec la commande git merge.

$ git switch main     # je me positionne dans la branche main
$ git merge v0        # je fusionne la branche v0 dans main
$ git log             # on voit maintenant le commit du test dans la branche main (en local uniquement)
$ git push            # on publie ce nouveau commit vers origin/main

Attention, néanmoins aux conflits qui peuvent survenir lors de la fusion des deux branches, si des modifications portent sur des portions identiques du code !

Exercice 1 : une coquille vide pour game.c

Ce travail débute par une phase de réflexion sur la définition de la structure de données struct game_s, qui sera au coeur de votre projet. Cette phase là est importante, car elle va grandement conditionner la suite de votre travail en équipe.

Nota Bene : Pour rappel, le type struct game_s est un type opaque, qui doit être défini dans votre fichier game.c, mais dont la définition n'est pas exposée dans game.h. Cette structure est uniquement manipulée dans l'interface game.h par l'intermédiaire d'un pointeur (types game ou cgame). L'utilisation du type cgame (pointeur constant) dans un fonction (comme par exemple game_get_xxxx()) indique au compilateur (et au programmeur) que la fonction ne va/doit pas modifier les champs de la structure, mais juste les consulter. A l'inverse, les fonctions (comme par exemple game_set_xxxx()) utilisent un pointeur de type game car elles modifient l'état de la structure.

Pour ce premier rendu en équipe, on vous demande :

  • de supprimer le fichier libgame.a de votre dépôt GitLab et d'ajouter en remplacement les fichiers game.c et game_aux.c, qui vont servir à générer cette bibliothèque ;
  • de mettre à jour votre fichier CMakeLists.txt ;
  • de définir dans game.c votre structure struct game_s ;
  • de mettre en place dans game.c et game_aux.c une coquille vide pour toutes les fonctions de game.h et game_aux.h respectivement ;
  • d'ajouter un fichier .gitignore afin d'ignorer dans Git le répertoire build, ainsi que différents les fichiers produits par CMake ou Make ;
  • d'ajouter un fichier .clang-format pour gérer automatiquement une mise en forme cohérente de votre code au sein de l'équipe (voir les consignes en Annexe) ;
  • d'ajouter un fichier README.md avec un description minimale du projet et le nom des auteurs ;
  • d'ajouter un nouveau jalon V1 dans GitLab et d'y associer un nouveau ticket pour chaque fonction à implémenter, sans oublier de vous répartir ces tickets ;
  • d'effectuer le rendu initial sur Moodle (en équipe).

A ce niveau, votre projet doit compiler sans warnings ni erreurs la bibliothèque game ainsi que tous les exécutables (game_text, vos tests). Bien évidemment, cette version ne doit pas (encore) passer vos tests !

Exercice 2 : implémentation de game.c

Une fois tous les tickets ajoutés dans GitLab et répartis entre tous les membres de l'équipe, commencez au plus tôt à travailler sur ce rendu, qui vous demandera pas mal d'effort ! En aucun, vous ne devez modifier les fichiers game.h ou game_aux.h.

Idéalement, votre projet CMake doit compiler sans erreurs ni warnings les programmes suivants :

  • la bibliothèque libgame.a ,
  • l'exécutable game_text,
  • l'ensemble de vos tests.

Ces programmes doivent fonctionner correctement. En particulier, votre implémentation de la bibliothèque game (fonctions de game.h et game_aux.h) doit passer tous vos tests, ainsi que tous les tests des enseignants. De plus, votre code ne doit comporter aucune fuite mémoire (ou memory leak). L'outil Valgrind, qui est intégré au framework CMake, permet de détecter les fuites mémoires dans vos tests de la manière suivante :

make                        # compilation
make test                   # exécution normale des tests
make ExperimentalMemCheck   # exécution des tests avec Valgrind

Nota Bene : Ne pas oublier d'ajouter include(CTest) dans votre fichier CMakeLists.txt pour disposer de la cible ExperimentalMemCheck.

Par ailleurs, on demande de produire un code mis en forme correctement (indentation, nommage des variables, ...) avec un style de programmation uniforme sur tout le projet. De manière générale, il est demandé de respecter des conventions de codage cohérentes, afin de faciliter la lisibilité de votre code. Afin d'éviter d'inutiles conflits das Git, il est nécessaire que tous les programmeurs d'une même équipe respectent ces conventions ! En outre, votre code devra être suffisament commenté. On évitera aussi toute redondance de code inutile.

  • Effectuez votre rendu final en équipe sur Moodle.