Structure des TPs
Les travaux pratiques (TPs) couvrent les sujets suivants :
- TP 1 : Premier programme « Hello World ! »
- TP 2 : Programmer une classe Robot
- TP 3 : Modéliser une usine contenant des robots
- TD 1 : Exercice de conception — Modéliser la structure d'une usine robotisée
- TP 4 : Coder en Java le modèle d’usine robotisée : vue structurelle
- TP 5 : Visualiser le modèle d'usine robotisée grâce aux interfaces
- TP 6 : Simuler le modèle d'usine robotisée grâce au MVC
- TP 7 : Sauvegarder le modèle d'usine robotisée
- TP 8 : Eviter les obstacles
Ressources : Liens utiles et téléchargements
TP 1 : Premier programme «Hello World !»
Au cours de ce TP, nous allons introduire l’environnement de développement intégré (IDE pour Integrated Development Environment) nommé Eclipse. Nous verrons comment créer et exécuter un programme Java simple de type « Hello World » avec Eclipse. Ensuite, nous verrons comment se servir du terminal pour faire la même chose afin de comprendre comment on peut compiler et exécuter un programme en Java sans l’aide d’un IDE.
Premier contact avec Eclipse
Lancer Eclipse à partir des menus de votre environnement Linux ou Windows, ou lancer l’exécutable nommé eclipse situé dans le répertoire où vous avez extrait l’archive téléchargée du site Eclipse.
Explorateur et Projet
Dans la fenêtre principale d’Eclipse qui s’affiche, identifier un panneau nommé Package Explorer tel que représenté ci-dessous :

A l’intérieur de l’explorateur de packages, cliquer sur Create a Java Project. Une fenêtre apparaît (voir ci-dessous) qui vous demande d’entrer le nom du projet. Ecrire tp01 dans le champ Nom du projet.
Vérifier également que l’option Create module-info.java file file au bas de la fenêtre n’est pas sélectionnée (la décocher si besoin). La notion de module, que nous n’utiliserons pas pour ce cours, sers à améliorer la modularité des applications Java, en particulier celles de très grande taille. Ne pas modifier les autres options de la fenêtre et cliquer sur le bouton Finish.

Le projet créé (tp01) apparaît dans le panneau Packages Explorer (voir ci-dessous).

Cliquer sur le petit triangle (>) à gauche du nom du projet. Le triangle se tourne vers le bas (∨) et le contenu du projet est dévoilé (voir ci-dessous).

On voit apparaître le dossier src (abréviation de source) destiné à recevoir le code source Java de vos programmes ainsi que le dossier JRE System Library qui contient l’ensemble des bibliothèques que Java met à votre disposition. En cliquant sur le triangle (>) à gauche du dossier JRE System Library, on voit apparaître la liste de ces bibliothèques (voir ci-dessous).

Ces bibliothèques sont des ensembles de classes prédéfinies mises à disposition par le JDK (Java Development Toolkit). Nous en étudierons quelques-unes en cours. En cliquant à nouveau sur le triangle (∨) à gauche du dossier JRE System Library, on referme ce dossier.
Classe
Nous allons créer une classe. Pour cela, faire un clic droit sur le dossier src. Dans le menu qui apparaît, amener la souris sur New. Un sous-menu apparaît, amener la souris sur Class et cliquer.

Une fenêtre telle que celle de la capture d’écran ci-dessous apparaît.

Dans le champ Name, écrire HelloWorld puis sélectionner la boîte (checkbox) devant public static void main(String[] args). Cliquer sur le bouton Finish.
Dans la vue Package Explorer, on voit apparaître le fichier de la classe nommé HelloWorld.java. Dans le panneau central à droite de l’explorateur de package, on voit apparaître le code Java de cette classe. Ce panneau sert également à éditer le code de la classe.

Dans cette classe, tout ce qui se situe entre les chaînes de caractères
/** et */ et tout ce qui se situe entre la chaîne de caractère // et la fin de la ligne sont des zones dédiées à l’écriture de commentaires. Ces zones ont été générées par Eclipse. Noter également les mots clés public devant les déclarations de la classe et de la méthode main. Nous verrons la signification de ces mots clés dans un cour ultérieur.
Une méthode telle que la méthode main est celle qui sera exécutée au lancement du programme. Cette méthode, générée par Eclipse, est vide. Dans son corps, écrire l’instruction suivante :
System.out.println("Hello World!");
Vous obtenez :

Sauvegarder le fichier. Par défaut, Eclipse va compiler la classe dès que son fichier sera sauvegardé. Cette instruction a pour fonction d’écrire la chaîne de caractères "Hello World!" dans ce que l’on appelle la Console. Pour exécuter ce programme, cliquer sur le bouton rond vert avec un triangle blanc à l’intérieur situé dans la barre de menu.

Eclipse va alors exécuter la classe, ce qui aura pour effet d’exécuter la méthode main de votre classe qui constitue le point d’entrée du programme. Le panneau Console apparaitra en bas de fenêtre du code. On y voit le résultat de l’instruction d’écriture. L’exécution du programme est aussitôt terminée.

Vous venez d’exécuter votre premier programme !
Compiler et exécuter un programme Java en ligne de commande (sans IDE)
Comme vous avez pu le constater, créer une simple classe et exécuter son programme est très facile avec un IDE. Utiliser un IDE permet de développer une application avec une meilleure productivité. Mais comment ferions-nous sans IDE ?
Le code Java est d’abord compilé en code binaire. L’exécution d’un programme Java se fait ensuite par interprétation de ce code binaire grâce à une JVM (Java Virtual Machine). L’installation de cette machine se réalise en déployant un environnement JRE (Java Runtime Environment), qui fournit différents exécutables pour compiler et exécuter un programme Java, ainsi que différents utilitaires tels que javadoc pour la génération automatique de la documentation sous forme de pages html. L’utilisation de ces différents exécutables se fait via un terminal, outil que vous avez sans doute déjà utilisé.
Compiler votre programme « Hello World ! » en ligne de commande
Tel que mentionné précédemment, pour compiler et exécuter programme Java, il faut disposer d’un environnement JRE qui sera spécifique au système d’exploitation donné (Windows, Linux, Mac). La machine virtuelle fournie par cet environnement interprétera le code binaire compilé afin de le traduire en instructions spécifiques au système. Il existe différents fournisseurs d’environnements JRE, les plus connus étant Oracle, ainsi que le projet OpenJDK.
Les exécutables de la JRE servant à compiler et exécuter des programmes Java sont stockés dans un sous-répertoire nommé Java/<nom de la version>/bin. Ce répertoire est localisé dans le répertoire des programmes du système d’exploitation. Pour connaître la version de Java qui est installée, dans un terminal, tapez la commande suivante :
java -version↵
Cette version correspond à la version la plus récente du langage Java supportée par la machine virtuelle. Pour cet exercice, veuillez-vous assurer que la version de Java est supérieure à 1.8.
Pour une version donnée de Java, il existe deux types d’installation de la machine virtuelle : l’installation JRE telle que mentionnée précédemment et l’installation JDK. L’installation JDK contient, en plus de tout ce que contient une JRE, le code source de Java ainsi que l’exécutable javac servant à la compilation (une JRE standard ne pourra servir qu’à exécuter des programmes Java et non pas à les compiler).
Installation de la machine virtuelle
Si vous travaillez avec votre propre ordinateur (et non pas un ordinateur de l’école), et qu’un message stipulant que la commande javac n’est pas trouvée lors de l’exercice suivant, installer un JDK sur votre ordinateur. Vous pouvez utiliser le JDK fourni par Oracle ou celui du projet OpenJDK.
📝 Note : les versions récentes d’Eclipse contiennent leur propre JDK, qu’il serait également possible d’utiliser. Le cas échéant, celui-ci se trouvera dans le répertoire d’installation d’Eclipse nommé :
../eclipse/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.<version>
Mais cela peut également dépendre de la façon dont Eclipse a été installé (par exemple via l’installateur ou en via une archive) ou même du système d’exploitation. Pour connaître le répertoire de la JVM que Eclipse utilise, il suffit dans Eclipse de cliquer sur le menu
Window >> Preferences. Dans la boîte de dialogue qui s’affiche, sélectionner la brancheJava/Installed JREstel qu’illustré dans la capture d’écran suivante. La partie droite de la fenêtre indique le répertoire d’installation de la JVM utilisée par Eclipse.

Si ce n’est déjà fait, dans un terminal, naviguer dans le répertoire src du projet Eclipse de votre programme « Hello World ! ». Pour connaître ce répertoire, dans Eclipse, sélectionner le répertoire src dans l’explorateur de package, puis faites un clic-droit et sélectionner le menu Properties. Dans la fenêtre qui s’affiche, sélectionner la branche Resource. Le répertoire du dossier src est affiché dans le côté droit de la fenêtre tel qu’indiqué par le libellé Location. Un bouton vous permet également d’ouvrir un navigateur de fichier qui sera positionné directement sur le répertoire.

Ensuite taper la commande
javac tp01/HelloWorld.java↵
Examiner le contenu du répertoire src/tp01 contenant le fichier Java. Vous verrez que l’exécution du programme javac a produit un autre fichier nommé HelloWorld.class à partir du fichier HelloWorld.java. Ce fichier contient le code binaire généré par la compilation du code Java.
Il est possible de spécifier au programme javac un répertoire (différent de src) où stocker les fichiers compilés. Par exemple, lors de la compilation, Eclipse va stocker les fichiers compilés dans un répertoire différent de src nommé bin (pour binary) et situé au même niveau que src dans le répertoire du projet.
Exécuter votre programme « Hello World ! »
Le fichier HelloWorld.class contenant le code compilé peut maintenant être exécuté par la machine virtuelle. Pour ce faire, il faut utiliser l’exécutable nommé java en lui passant en paramètre le nom de la classe.
Toujours dans le répertoire src, taper la commande
java tp01/HelloWorld↵
Vous devriez voir s’afficher la chaîne de caractère
Hello World !
dans le terminal.
Il est également possible de réaliser ces deux opérations en une seule commande avec l’exécutable java. Supprimer d’abord le fichier compilé HelloWorld.class et vérifier qu’il a bien été supprimé.
Taper la commande
java tp01/HelloWorld.java↵
Vous verrez s’afficher dans le terminal la chaîne de caractère
Hello World !
Noter que le fichier compilé n’a pas été enregistré sur disque ; il a simplement été créé puis exécuté dans la foulée sans sauvegarde sur disque. Ainsi, il est préférable de compiler d’abord un programme en utilisant javac (ce que fait Eclipse), car le programme pourra ensuite être exécuté autant de fois que l’on souhaite sans avoir à le recompiler.
📝 Note : Ces commandes fonctionnent pour une classe qui est contenue dans un
package1 nommétp01(i.e., son fichier.javase trouve directement dans le sous-répertoiretp01du répertoiresrc). Si vous avez déclaré votre classe dans un autre package (par exemple un package nommétest), son fichier .java sera alors contenu dans un sous-répertoire nommétestdanssrc. Pour exécuter cette classe, il faudra alors se positionner dans le répertoiresrcet préfixer le nom de la classe par son nom de packagetestau lieu detp01.
Si vous tentez d’exécuter la classe dans le sous-répertoire
testau lieu desrc, vous verrez apparaître un message d’erreur tel que
Error: Could not find or load main class HelloWorld
Il faut donc bien vérifier que les commandes sont lancées à partir du répertoire
src.
Ces commandes simples nous montrent comment Eclipse (ou tout autre IDE) compile et exécute un programme Java pour nous. Sous Eclipse (ou la plupart des IDE), la compilation est exécutée à chaque fois qu’un fichier Java est sauvegardé. Ainsi, lorsqu’un projet contient plusieurs fichiers Java, Eclipse ne recompilera que les fichiers modifiés et ceux qui en dépendent, ce qui s’appelle la compilation incrémentale. Cela permet de réduire significativement le temps de compilation.
Nous verrons la notion de package dans un cours ultérieur.
Enregistrer votre projet sur GitLab (Optionnel)
Note : Cette étape est optionnelle pour ce TP mais fortement recommandée, car vous aurez besoin d'utiliser Git tout au long du cours et pour votre projet final.
Maintenant que vous avez créé votre premier projet Java, il est important d'apprendre à versionner votre code avec Git et à le stocker dans un dépôt distant sur GitLab. Cela vous permettra de suivre les modifications, de collaborer avec d'autres et de sauvegarder votre travail.
Créer un dépôt GitLab
- Rendez-vous sur GitLab et connectez-vous avec vos identifiants de l'école.
- Cliquez sur
New project(ou le bouton+dans le menu supérieur).

- Sélectionnez
Create blank project.

- Dans le champ
Project name, entrez le nom de votre projet en suivant le format :tp01-oojava. - Définissez le niveau de visibilité sur
Private. - Décochez
Initialize repository with a README(nous allons l'initialiser localement). - Cliquez sur
Create project.

- GitLab affichera une page avec des instructions. Gardez cette page ouverte car vous aurez besoin de l'URL du dépôt.

Initialiser Git dans votre projet
-
Ouvrez un terminal et naviguez vers le répertoire de votre projet Eclipse (le répertoire parent du dossier
src). -
Initialisez un dépôt Git :
git init -
Configurez votre identité Git (si ce n'est pas déjà fait) :
git config user.name "Votre Nom" git config user.email "votre.email@telecom-paris.fr" -
Créez un fichier
.gitignorepour exclure les fichiers compilés :echo "bin/" > .gitignore echo "*.class" >> .gitignore
Valider et pousser votre code
-
Ajoutez tous les fichiers à la zone de staging :
git add . -
Créez votre premier commit :
git commit -m "Initial commit: Hello World program" -
Ajoutez le dépôt GitLab distant (remplacez
<url-de-votre-depot>par l'URL de l'étape 8) :git remote add origin <url-de-votre-depot> -
Poussez votre code vers GitLab :
git push -u origin mainNote : Si votre branche par défaut s'appelle
masterau lieu demain, utilisez :git push -u origin master -
Rafraîchissez la page de votre projet GitLab dans le navigateur. Vous devriez maintenant voir les fichiers de votre projet dans le dépôt.
Vous avez réussi à versionner et pousser votre premier projet Java sur GitLab !
TP 2 : Programmer une classe Robot
Dans ce TP, nous allons apprendre à coder une première classe servant à modéliser un robot. A terme, cette classe servira à modéliser l'usine robotisée du simulateur que vous devez développer dans le cadre du projet de ce cours.
Parce que les logiciels de programmation connaissent mieux la langue anglaise que les autres langues, il peut être compliqué de travailler avec des noms de classe, de méthodes ou d'attributs écrits en français. C'est pourquoi vous utiliserez toujours des mots anglais dans votre code.
Internet est une référence pour la programmation en Java. Vous y trouverez des exemples de programmation pour tout ce que vous voudrez. Il suffit d'utiliser un moteur de recherche en utilisant les bons mots clé. Vous y trouverez également des tutoriels sur l'utilisation d'Eclipse dont la grande majorité sont en anglais. Si votre version d'Eclipse est en français, cela vous demandera parfois quelques efforts pour vous y retrouver.
Lancer Eclipse à partir des menus de votre système d'exploitation. A l'aide du menu File >> New >> Java Project, créer un projet nommé robotsim. Les sources de ce projet seront à conserver précieusement tout au long du cours car elles constitueront votre projet de développement à rendre en fin de cours.
Dans le dossier src du projet robotsim, tel que fait pour la classe HelloWorld du TP précédent, créer une classe nommée Robot. Cette classe servira à modéliser la notion de robot contenu dans une usine de production de rondelles telle que celle du projet que vous développerez.
Parmi les attributs de la classe Robot, on doit avoir :
- Un attribut nommé
namede typeString. - Un attribut nommé
speedde typedouble.
Utiliser l'éditeur de code Java pour déclarer ces attributs dans la classe Robot.
Nous allons maintenant écrire un constructeur pour cette classe. Ce constructeur devra initialiser tous les champs. Vous pouvez coder ce constructeur directement dans l'éditeur de la classe ou alors utiliser l'IDE :
- Faire un clic droit dans la fenêtre d'édition de la classe
Robotou sur le fichierRobot.javadans l'explorateur de package. - Un menu apparaît ; amener la souris sur
Source. - Un second menu apparaît ; cliquer sur
Generate Constructor using Fields(ne pas sélectionnerGenerate Constructor from Superclass; cette notion n'a pas encore été abordée dans le cours).

Une fenêtre apparaît qui vous propose de personnaliser le constructeur :

Vérifier que les deux attributs sont sélectionnés. Le point d'insertion du constructeur dans la classe peut être laissé tel quel ou sélectionné comme étant par exemple après la déclaration des attributs. Cliquer ensuite sur le bouton Generate.
On obtient alors une classe modifiée :

Avant de quitter une classe pour aller en éditer une autre, il est fortement conseillé de sauvegarder la classe en cours d'édition. Le bouton de sauvegarde est situé dans la barre de menu de Eclipse.
A présent, générons une seconde classe appelée TestRobotSim et contenant une méthode main comme dans la classe HelloWorld du premier TP. Dans cette méthode main, créer un objet de type Robot avec l'instruction :
Robot myRobot = new Robot("Robot 1", 5);
Puis demander l'affichage de l'objet dans la console avec l'instruction :
System.out.println(myRobot);
Puis exécuter le programme. Que remarquez-vous à l'affichage de la classe ?
On voit que Java ne sait pas afficher un objet de la classe Robot. Il sait afficher des chaînes de caractères, des nombres, etc. Mais il ne connaît pas la classe Robot qui est notre invention. Quand Java ne sait pas afficher un objet, il affiche le nom de la classe de l'objet, ici Robot, suivi du symbole @ (arobase), suivi de l'adresse en mémoire de l'objet exprimée en hexadécimal (base 16).
Si l'on veut que Java soit capable d'afficher un objet de la classe correctement, il faut lui fournir une méthode qui donne l'affichage sous la forme d'une chaîne de caractères. La fonction System.out.println(...) pourra ensuite appeler cette méthode pour afficher l'objet. Cette méthode possède l'en-tête suivante :
public String toString()
Pour générer cette méthode dans la classe, vous pouvez utiliser l'IDE :
- Clic droit sur la fenêtre d'édition de la classe
Robot. - Un menu apparaît. Amener la souris sur
Source. - Un second menu apparaît ; cliquer sur
Generate toString().
Une fenêtre apparaît pour vous proposer la génération d'une méthode toString() :

La méthode qui sera générée calculera une chaîne de caractères qui comprendra le nom de la classe suivi de la valeur de ses attributs. Dans cette fenêtre, il est possible d'ajouter d'autres propriétés à l'affichage. Cliquer sur le bouton Generate. La classe est modifiée
:

L'annotation @Override, placée juste avant la méthode, indique que cette méthode est la redéfinition d'une méthode héritée. Nous verrons cela dans un cours ultérieur.
Exécuter à nouveau le programme. Est-ce que l'affichage de la classe est plus compréhensible et utile ? Vous devriez obtenir ceci dans la vue Console :

Redéfinir l'affichage des robots
Modifier la méthode toString() de la classe afin qu'elle renvoie la chaîne de caractères ainsi formée :
« Je m’appelle Robot 1 et j’avance à 5.0 km/h. »
Exécuter votre programme et vérifier que tout fonctionne correctement.
Utilité d'un IDE
Dans ce TP, nous n'avons finalement écrit que peu de code par nous-même. Ce ne sera pas toujours le cas. L'environnement de développement nous permet de réaliser des tâches de codage standards (génération de constructeurs, getters, setters, toString(), etc.) en quelques clics de souris.
L'IDE vous signale également les erreurs et avertissements (warnings) de compilations via la vue Problems telle qu'illustrée par la capture d'écran suivante. Dans cette vue, un double-clic sur une erreur de la liste de la vue Problems vous amènera directement à la position de l'erreur dans l'éditeur de code Java.

Par ailleurs, dans la marge de l'éditeur de code, des suggestions de correction d'erreurs peuvent être proposées par l'IDE en cliquant sur l'ampoule tel qu'illustré par la capture d'écran suivante. Dans cet exemple, l'attribut speed n'a pas été écrit correctement.

Il est également possible de renommer automatiquement des éléments de code tels que les noms de classes ou de variables (refactoring).
Toutes ces fonctionnalités sont très utiles en environnement de développement industriel car cela permet d'améliorer grandement la productivité des développeurs. Il ne faut pas hésiter à vous en servir, bien que vous deviez toujours comprendre le code généré. En effet, l'IDE ne programmera pas d'algorithmes pour vous, bien que des extensions telles que Copilot (outil payant) utilisant l'intelligence artificielle peuvent éventuellement être utiles.
TP 3 : Modéliser une usine contenant des robots
Pour ce TP, vous allez continuer à travailler dans le même projet que celui du TP précédent, (nommé robotsim), et qui à terme, contiendra tout le code source de votre projet de développement.
Vous allez donc rajouter une nouvelle classe nommée Factory pour modéliser une usine de production robotisée. Lancer Eclipse pour travailler dans le projet robotsim.

Encapsuler les données de la classe Robot
Nous avons vu en classe l'importance d'encapsuler les données. Celle-ci s'effectue en utilisant le mot clé de visibilité private en Java. Modifier la visibilité des attributs de votre classe robot puis générer les accesseurs pour ces attributs, en considérant que le nom d'un robot ne pourra pas changer au cours de son existence (ce qui n'est pas le cas pour sa vitesse). Tout comme pour la génération de la méthode toString() du TP précédent, l'IDE peut automatiquement générer ces accesseurs pour vous.
Créer une classe Factory
En faisant un clic droit sur le package robotsim puis en sélectionnant le menu New >> Class, créer une classe nommée Factory.
Une usine ayant un nom et devant contenir plusieurs robots, déclarer dans cette classe un attribut name comme pour votre classe Robot et un attribut nommé robots de type ArrayList<Robot> pour contenir les robots de l'usine. Ne pas oublier d'encapsuler ces attributs en utilisant le qualificateur private. Vous devriez obtenir ceci :

Déclaration d'importation
Une marque rouge indiquant une erreur est apparue dans la marge gauche de l'éditeur de code. En survolant ce marqueur avec la souris, l'erreur s'affiche :

En effet, la classe ArrayList n'est pas connue par défaut. Il faut alors, tel que vu en cours, importer cette classe grâce à la déclaration :
import java.util.ArrayList;
Cette déclaration doit être placée au début du fichier de la classe. Ici encore, l'IDE peut vous aider. En cliquant sur la marque rouge d'erreur, l'IDE vous proposera différentes solutions possibles :

La première solution est bien sûr la bonne. Déplacer la souris et cliquer sur Import ArrayList. La déclaration d'importation est alors automatiquement ajoutée à la classe.
Écrire le constructeur
Comme nous l'avons vu en cours, par défaut l'attribut robots est initialisé avec la valeur null. Il faut spécifier un constructeur afin d'initialiser les attributs d'une classe avec des valeurs convenables. Consulter la Javadoc de la classe ArrayList pour connaître les constructeurs de cette classe.
Le constructeur doit créer et assigner l'ArrayList pour robots, et prendre le nom en argument.
Écrire un constructeur pour votre classe Factory.
Indice : Votre constructeur devrait ressembler à ceci :
public Factory(String name) { this.name = name; this.robots = new ArrayList<Robot>(); }
Ajouter un robot à l'usine
Ecrire une méthode dans la classe Factory qui permettra d'ajouter des robots à l'usine. Elle aura la signature suivante :
public boolean addRobot(String name)
Cette méthode devra d'abord vérifier que le nom du nouveau robot nommé name est unique parmi les noms des robots qui ont déjà été ajoutés à l'usine robotisée. Si le nom est unique, alors le robot sera ajouté à l'usine et la méthode retournera la valeur booléenne vraie. Dans le cas contraire, le robot ne sera pas ajouté à l'usine et la valeur booléenne faux sera retournée. Par défaut, la vitesse du robot créé sera de 0.0.
Vérifier l'unicité des noms des robots
Ecrire une méthode dans la classe Factory qui vérifiera l'unicité d'un nom de robot passé en paramètre. Cette méthode, qui sera appelée par la méthode addRobot, aura la signature suivante :
private boolean checkRobotName(String name)
Elle devra parcourir la liste des robots pour vérifier qu'aucun d'entre eux n'a le même nom que celui passé en paramètre.
Afficher l'usine et ses robots à la console
Ecrire une méthode dans la classe Factory ayant la signature suivante :
public void printToConsole()
Cette méthode affichera le nom de l'usine ainsi que la liste de ses robots à la console.
Tester les méthodes
Dans la méthode main de la classe TestRobotSim créée au TP précédent, ajouter des instructions qui permettrons de :
-
Créer un objet de la classe
Factory. -
Ajouter quelques robots à cet objet. Faites différents essais avec quelques robots de noms identiques afin de vérifier que votre programme fonctionne correctement.
-
Afficher l'objet de la classe
Factoryà la console.
Exécuter votre programme avec différents jeux de robots et vérifier que ce qui s'affiche à la console est correct.
Organiser les classes en packages
Vous avez sans doute remarqué lorsque vous avez créé les classes Robot et Factory avec Eclipse que celui-ci les a mises dans un package nommé robotsim, dont le nom est le même que celui du projet.

Cette organisation des classes n'est pas idéale car tel que vu en cours, il est préférable de regrouper les classes réalisant une fonctionnalité commune de l'application au sein d'un même package.
Les classes Factory et Robot réalisent la fonction de modélisation de l'usine robotisée. Elles seront donc regroupées au sein d'un package nommé fr.tp.robotsim.model. Qu'en est-il de la classe TestRobotSim ?
Pour changer le package d'une classe, il ne suffit pas de renommer la déclaration du package dans la classe. En effet, Java impose que le fichier de la classe soit localisé dans une arborescence de dossiers du système de fichier équivalente au nom du package de la classe, après conversion des caractères « . » du nom du package en caractères « / ».
Encore une fois, l'IDE pourra automatiquement changer la déclaration de package de la classe et déplacer son fichier dans le bon sous-répertoire correspondant. Pour ce faire, sélectionner le (ou les) classe(s) dans l'explorateur de package et faire un clic-droit. Dans le menu qui s'affiche, sélectionner Refactor >> Move....

Dans la boite de dialogue qui s'affiche, saisir le nom de package souhaité et cliquer sur Finish.

N'oubliez pas de changer également le package de la classe TestRobotSim et d'exécuter à nouveau votre classe TestRobotSim afin de verifier que votre programme fonctionne toujours correctement après la réorganisation de vos classes.
TD 1 : Exercice de conception : Modéliser la structure d'une usine robotisée
Projet : Développer un simulateur d’usine de production robotisée

Inspiré du laboratoire de systèmes cyber-physiques de l'institut Hasso Plattner (Potsdam, Allemagne)
Pourquoi ce système ?
Permet de mettre en œuvre les notions essentielles de l’OO.
- Délégation / non-intrusion, héritage, polymorphisme, classes abstraites, etc.
Permet de mettre en œuvre l'utilisation des interfaces pour l'intégration de différents composants logiciels.
- Une interface graphique vous sera fournie et l’intégration de ce composant permettra de visualiser la simulation.
Illustrer quelques patrons de conception (design patterns) et prendre conscience de l’importance de l’architecture logicielle.
Ce système peut être étendu pour mettre en œuvre des aspects avancés (cours de deuxième année) :
- Persistance des données.
- Programmation parallèle et synchronisation des accès aux ressources.
- Applications distribuées orientées services.
- Etc.
Exigences fonctionnelles pour le projet
Modéliser une usine de production robotisée simplifiée contenant les éléments suivants :
- Usine, robots, stations de recharge de robots, salles et portes, aires de travail, machines de production, convoyeur.
Pouvoir simuler le comportement des robots devant transporter les biens produits (rondelles) d’un endroit à l’autre dans l’usine.
- Pour chaque robot, on doit pouvoir donner une liste de positions à visiter dans l’usine et le robot devra se déplacer pour les parcourir successivement.
- Les robots devront contourner les obstacles sur leur parcours.
L’usine et sa simulation devront pouvoir être visualisés via une interface graphique.
Les modèles (données) doivent pouvoir être sauvegardés sur stockage permanent (disque).
L'application doit gérer les exceptions pouvant survenir en cours d'exécution du programme.
- Par exemple, lorsqu’il y a des problèmes lors de l’accès aux données sur stockage permanent.
Optionnel, si vous avez suffisamment de temps :
- Simuler la consommation d'énergie du robot. Si un robot atteint un niveau minimum d'énergie donné, il doit alors se diriger vers une station de recharge et y séjourner pendant un certain temps pour se recharger.
- Les portes s'ouvriront et se refermeront automatiquement lorsqu'un robot devra entrer dans une salle.
Exigences de développement
Mettre en œuvre les bonnes pratiques de programmation vues en classes.
- Définition et organisation des classes.
- Conventions de nommage.
- Commentaires et formatage du code.
L’architecture doit suivre le MVC tel que présenté en classe.
Autres remarques…
Le détail des fonctionnalités précédentes, à implémenter pour le projet, sera donné dans chacun des documents d’instructions des TP.
Étant donné le peu de temps disponible, plusieurs aspects du simulateur ne seront évidemment pas considérés pour le projet ; ils le seront dans le cadre d’un cours de programmation avancée en Java lors de la deuxième année.
- Parallélisme et accès concurrents aux ressources (synchronisation).
- Application distribuée.
- Base de données.
- Etc.
Ce système ne sert qu’à illustrer les notions et les bonnes pratiques de programmation du cours ; il ne s’agit pas d’en faire un simulateur professionnel :).
Exemple de structure d’usine de production robotisée

Importance de la conception : un autre exemple de système très (très) simple
Passerelle de la carrière de la Troche.
- Sur le chemin en allant prendre le RER B à la station du Guichet à partir de l’école.
Trouver deux erreurs de conception.

Réponse

Importance de la conception
"If you fail to plan, you are planning to fail!" -- (Benjamin Franklin peint par Joseph-Siffrein Duplessis)


Exercice de conception dirigé
En groupe de 2 ou 3 élèves, dessiner un diagramme de classe (voir exemple suivant) pour modéliser la structure de l'usine robotisée.
- Dessin sur papier ou autre outil de votre choix.
- 20-30 minutes de travail en équipe.
- Restitution de quelques équipes au tableau.
- Discussions et améliorations itératives du diagramme de classe.
-
Note : ne vous intéressez qu'à la structure de l'usine ; le comportement (modélisé par les méthodes des classes) sera spécifié lors d'un TP suivant.

Diagrammes de classes des étudiants — TD1 (2026)
Diagrammes de classes UML conçus par les équipes d'étudiants lors de l'exercice de conception dirigé.
Astuce : Cliquez sur une image pour l'afficher en taille réelle.
TP 4 : Coder en Java le modèle d'usine robotisée : vue structurelle
Le but de cet exercice est de coder en Java le diagramme de classe que vous avez dessiné lors du TD précédent. Ce diagramme a été produit alors que vous ne disposiez pas encore de toutes les notions et bonnes pratiques de modélisation qui ont été vues en classe par la suite, telles que les classes abstraites et le principe de responsabilité unique par exemple. Ainsi, la traduction du diagramme de classe en Java est également l'occasion d'améliorer votre conception en utilisant ces notions et bonnes pratiques, et de compléter le modèle au besoin.
Comme nous l'avons vu, l'usine robotisée est inspirée du laboratoire de systèmes cyber-physique de l'institut Hasso-Plattner (Figure 1).

Figure 1 : L’usine robotisée du laboratoire de systèmes cyber-physiques de l’institut Hasso-Plattner.
Tel qu'illustré par le plan de la Figure 2, l'usine robotisée est constituée de différentes salles dans lesquelles des machines sont installées. Ces machines sont destinées à effectuer un traitement particulier sur des rondelles produites par l'usine.
A l'intérieur de cette usine, des robots servent à transporter les rondelles produites entre les diverses machines de production. Lorsque nécessaire, ces robots peuvent rejoindre des stations de recharge afin de recharger leurs batteries jusqu'à ce qu'ils aient suffisamment d'énergie pour reprendre leur travail.

Figure 2 : Plan de l’usine robotisée.
Lors de la conception de systèmes comme une usine robotisée, la simulation est très utile pour évaluer différentes propriétés telles que la quantité de biens pouvant être produits par unité de temps, la quantité de robots nécessaires pour transporter les biens d'une machine à l'autre, ou la capacité des batteries des robots requise pour produire cette quantité de biens, afin d'éviter que les robots ne passent trop de temps en station de recharge.
Le but du simulateur que vous allez développer pour ce projet sera de visualiser le comportement de l'usine robotisée, c'est-à-dire de visualiser différents événements tels que le déplacement des robots transportant des rondelles dans le bâtiment, et l'ouverture de portes automatiques, permettant si le temps le permet de faire quelques estimations grossières de consommation énergétique des robots. Ces derniers aspects seront vus lors de TP ultérieurs.
Les constituants du système
Nous allons d'abord modéliser les constituants de l'usine robotisée. Elle se compose de différents types de composants tels que :
- Une usine.
- Ses salles et portes contenant des aires de production.
- Des machines de production positionnées dans les aires de production.
- Des robots.
- Des stations de recharge de robots.
- Des convoyeurs.
- Des rondelles produites par l'usine.
Créer une classe composant
Nous allons considérer que tous ces objets sont des composants de l'usine et que nous souhaitons représenter leur position dans l'usine en ne considérant que leurs coordonnées cartésienne (position x et y) dans le plan constitué par le sol du bâtiment.
Créer une première classe nommée Component qui définira des attributs communs à tous les composants de l'usine tels que leur position sur le sol de l'usine et leur dimension. Au besoin, créer des classes pour ces différents aspects.
La classe Robot que vous avez créée lors des TP précédents devra être modifiée afin d'hériter de cette classe Component. Par ailleurs, modifier également la classe Factory afin qu'elle possède une liste de composants au lieu d'une liste de robots. Modifier également la méthode addRobot() et la renommer en addComponent(). Cette méthode prendra en paramètre un objet de type Component au lieu d'un nom de robot, et elle ajoutera ce composant à la liste de composants de la classe Factory. Elle devra vérifier que le nom du composant passé en paramètre est unique parmi tous les noms des composants de l'usine.
Modéliser les différents types de composants du système
En utilisant la notion d'héritage de classe vue en cours, créer des classes pour chacun des différents types de composants de l'usine. Ajouter les attributs que vous pensez utiles pour ces différentes classes.
Redéfinir la méthode d'affichage des nouvelles classes
Redéfinir la méthode toString() de chaque nouvelle classe afin de personnaliser son affichage à la console.
Instancier une usine robotisée et l'afficher à la console
Afin de tester votre modèle, créer une classe test contenant une méthode main. Dans cette méthode, instancier une usine robotisée contenant 3 salles ayant chacune une aire de travail, contenant chacune une machine de production. Y ajouter 3 robots et une station de recharge.
Afficher cette usine à la console et vérifier que sa représentation sous forme de chaîne de caractère est correcte.
TP 5 : Visualiser le modèle d'usine robotisée grâce aux interfaces
Au cours du TP précédent, nous avons modélisé la structure de l'usine robotisée pour laquelle nous voulons développer un simulateur. Nous avons donc créé un modèle objet de l'usine. Au cours de ce TP, nous allons modifier ce modèle afin de permettre de le visualiser grâce à une interface graphique qui vous sera fournie. Pour cela, il faudra que votre modèle implémente les interfaces Java fournies par cette interface graphique.
Cette interface graphique est très simple ; la seule chose qu'elle sait faire, c'est d'afficher des figures géométriques de deux dimensions (2D) de forme rectangulaire, ovale ou polygonale, de différentes couleurs, et de différents types de traits (tirets et épaisseur) dans un canevas de dessin.
Tel que vu en classe, une interface définie un point de vue ou un descriptif de propriétés sur les classes qui l'implémentent. Ainsi, l'interface graphique fournie des interfaces Java spécifiant un simple point de vue figures 2D. Chaque composant de l'usine robotisée peut être en effet représenté par une figure 2D que sera dessinée dans un canevas, lui-même étant un point de vue sur l'usine robotisée qui contient les composants. Ainsi, il suffira que les classes de votre modèle implémentent le jeu d'interfaces figures 2D fournies par l'interface graphique pour que celle-ci puisse les afficher.
Ce jeu d'interfaces constitue donc un contrat entre le modèle et l'interface graphique devant afficher le modèle. Cette manière d'intégrer différents composants logiciels est très fréquente en programmation OO.
L'interface graphique de visualisation de figures
Télécharger la bibliothèque de l'interface graphique de visualisation de figures (canvas viewer) qui se trouve ici. Cette bibliothèque, nommée canvas-viewer.jar, n'est en réalité qu'un ensemble de classes Java compilées (fichiers .class) qui peuvent être utilisées par d'autres applications Java.
Ouvrir le fichier canvas-viewer.jar avec un utilitaire de compression de fichier tel que 7-Zip. Vous constaterez qu'il ne s'agit que d'une simple archive contenant les fichiers *.class dans la même arborescence de répertoire que celle des packages des classes. Vous y trouverez également des fichiers *.java,
ce qui vous permettra de voir le code source de l'interface graphique, et même d'y mettre des points d'arrêt si vous souhaitez comprendre ce qui s'y passe en utilisant le débogueur.
Configurer le projet du simulateur pour utiliser la bibliothèque de l'interface graphique
Dans le navigateur de projet ou de package dans Eclipse, sélectionner le projet de votre simulateur, puis faire un clic droit et cliquer sur le menu New >> Folder et créer un répertoire nomme libs. Déplacez le fichier canvas-viewer.jar dans ce répertoire (vous pouvez d'ailleurs directement déplacer le fichier à partir d'un navigateur de fichier de
votre système d'exploitation vers le répertoire du navigateur de projet dans Eclipse).
Sélectionner le projet de votre simulateur, puis faire un clic droit et cliquer sur le menu Properties. Dans la boîte de dialogue qui s'affiche, sélectionner la branche Java Build Path puis l'onglet Libraries dans la partie de droite de la fenêtre. Sélectionner Classpath puis cliquer sur le bouton Add Jars... et naviguer vers le fichier canvas-viewer.jar que vous avez ajouté dans le répertoire libs.

Dans le package explorer (ou le navigateur de projet), déplier la branche Referenced Libraries. Vous y verrez la bibliothèque canvas-viewer.jar que vous venez d'ajouter au projet. Vous verrez également les packages fournis par cette bibliothèque. Ouvrir les package fr.tp.inf112.projects.canvas.model et fr.tp.inf112.projects.canvas.model.impl.
Examiner le contenu de ces packages. Dans fr.tp.inf112.projects.canvas.model, vous trouverez des interfaces telles que Canvas, Figure, Style et Shape.
L'interface Canvas décrit les propriétés du canevas telles que son nom, sa largeur et sa hauteur, ainsi que la liste des figures à afficher dans le canevas.
L'interface Figure décrit les propriétés d'une figure 2D telles que son nom, sa position dans le cannevas (coordonnées x et y), son style, qui caractérise la couleur et le contour de la figure, et la forme de la figure telle que décrite par l'interface Shape. L'interface graphique ne peut afficher que trois types de formes dont les propriétés sont décrites par les sous interfaces RectangleShape, OvalShape et PolygonShape.
Le package fr.tp.inf112.projects.canvas.model.impl contient les classes BasicPoint et RGBColor qui sont des implémentations de base des interfaces Point et Color que vous pouvez utiliser telles qu'elles dans votre projet.
Afin de faciliter leur utilisation, toutes ces interfaces contiennent de la documentation sous format Javadoc qui explique les différentes méthodes requises par les interfaces.

Dans le package fr.tp.inf112.projects.canvas.view se trouvent les classes qui réalisent l'interface graphique qui servira à visualiser votre modèle d'usine robotisée. Il n'est pas nécessaire de comprendre ces classes, les interfaces graphiques n'étant pas au programme du cours.
Implémenter les interfaces du canevas avec le modèle d'usine robotisée
Pour afficher votre modèle dans l'interface graphique, vous devez déterminer les interfaces que devront implémenter les classes de votre modèle d'usine robotisée. Par exemple, l'usine robotisée, qui consiste en un plan dans lequel sont positionnés les composants peut être vue comme un canvas. De la même manière, tout composant de l'usine peut être représenté par une figure géométrique 2D. Ainsi, votre classe abstraite Component devra implémenter l'interface Figure :
public abstract class Component implements Figure
Nom et position de la figure dans le canevas
L'interface Figure demande de connaître un nom (méthode getName()) pour la figure, qui sera affiché sur le coin gauche en haut de la figure dans le canevas. Elle demande également les coordonnées de la figure afin de savoir où la dessiner dans le canevas.
Style de la figure
L'interface Figure nécessite également de connaitre le style d'affichage de la figure. Celui-ci est caractérisé par une couleur de fond pour la figure (méthode getBackgroundColor()), et les caractéristiques du trait du contour de la figure (épaisseur, pointillé, couleur, tels que décrits par l'interface Stroke).
Forme de la figure
Tel que mentionné précédemment, l'interface graphique ne sait dessiner que des figures de formes rectangulaire (interface RectangleShape), ovale (interface OvalShape) ou polygonale (interface PolygonShape). Pour chaque sous-classe concrète de la classe Component, il faut donc déterminer la forme souhaitée et définir des classes d'implémentation
pour ces formes. Puis, chaque composant devra définir la méthode getShape() de l'interface Figure pour retourner une instance de l'implémentation correspondant à la forme choisie pour le composant.
Par exemple, puisque les robots de l'usine sont de forme circulaire, votre classe Robot retournera une instance d'une classe d'implémentation de OvalShape, tandis que les classes Room ou Area, correspondant à des composants de forme rectangulaires, retourneront des instances de classe d'implémentation de RectangleShape.
Faites de même pour chaque classe composant de votre modèle devant être visualisé par l'interface graphique à l'exception de la classe Factory représentant l'usine robotisée, qui elle sera considérée comme étant de forme rectangulaire.
Le canevas
En effet, la classe Factory devra plutôt implémenter l'interface Canvas, qui représente le conteneur des figures. Notez que l'interface Canvas spécifie une méthode Collection<Figure> getFigures(). Puisque tous les composants de l'usine robotisée implémentent l'interface Figure, il sera possible de directement retourner l'attribut de votre classe Factory qui contient les composants. Cependant, vous aurez à transtyper cette référence comme suit :
public Collection<Figure> getFigures() {
return (Collection) components;
}
Lancement de l'interface graphique
Examiner la classe CanvasViewer du package fr.tp.inf112.projects.canvas.view de la bibliothèque de l'interface graphique (fichier canvas-viewer.jar). L'un des constructeurs de cette classe prend comme unique paramètre une instance de classe implémentant l'interface Canvas. C'est ce constructeur que vous utiliserez pour lancer l'interface graphique.
A cette fin, créer d'abord une classe nommée SimulatorApplication afin de représenter l'application du simulateur reliant le modèle et l'interface graphique. Dans cette classe, créer une méthode main. Dans cette méthode, instancier un modèle d'usine robotisée tel que celui du TP précédent (contenant 3 salles ayant chacune une aire de travail, contenant chacune une machine de production, ainsi que 3 robots et une station de recharge par exemple). Puis, instancier la classe CanvasViewer fournie par l'interface graphique en lui fournissant l'usine que vous venez de créer. Lancer la méthode main. Vérifier que votre modèle s'affiche correctement dans l'interface graphique tel qu'illustré par la figure plus bas.
Noter qu'aucune des actions définies par les menus de l'interface graphique ne fonctionnera (elles seront désactivées), mis à part l'action de quitter l'application. Ces actions seront définies au prochain TP, lorsque vous implémenterez l'interface contrôleur du design pattern modèle-vue-contrôleur que nous verrons en classe.

TP 6 : Simuler le modèle d'usine robotisée grâce au MVC
Au cours du TP précédent, vous avez modifié votre modèle objet d'usine robotisée pour lui faire implémenter les interfaces modèle de l'interface graphique Canvas Viewer qui vous a été fournie, afin de visualiser votre modèle sous forme de figures 2D de différentes couleurs, styles et formes géométriques.
Au cours de ce TP, vous allez définir le comportement de votre modèle afin de pouvoir le simuler. Puis, pour visualiser cette simulation avec l'interface graphique Canvas Viewer, vous devrez fournir une classe qui implémente le contrôleur de l'interface graphique afin qu'elle puisse contrôler l'affichage de votre modèle et sa simulation.
Spécifier le comportement du modèle
Modéliser la partie comportementale de l'usine robotisée consiste à définir des méthodes dans les différentes classes de composants de l'usine qui décriront comment les états, représentés par les différentes valeurs des attributs des composants, peuvent évoluer en fonction du temps et des différents événements pouvant se produire dans l'usine.
D'abord définir une nouvelle méthode nommée behave() dans la classe Component de votre modèle. Le contenu de cette méthode sera vide, et chaque composant de l'usine dont l'état peut changer au cours du temps pourra redéfinir cette méthode, et ainsi spécifier ce que le composant fait (i.e. son comportement) lorsque l'usine fonctionne.
Dans la classe Factory il faudra redéfinir la méthode behave() héritée de la classe Component. Dans cette méthode, pour chacun des composants contenus dans l'usine, il faudra appeler la méthode behave() du sous-composant. Ainsi, toute méthode redéfinie par une sous-classe de la classe Component sera exécutée, ce qui aura pour conséquence de simuler le comportement spécifique de chaque composant.
Déplacer les robots
Spécifier d'abord le comportement de la classe Robot. Pour l'instant, celui-ci ne consiste qu'à se déplacer d'un composant à un autre dans l'usine. A cette fin, ajouter un attribut à la classe Robot pour contenir une liste de composants que le robot devra visiter lorsqu'il se déplace dans l'usine.
Dans cette même classe Robot, redéfinissez la méthode behave() héritée de la classe Component. Dans cette méthode, récupérer le premier composant de la liste des composants à visiter par le robot. Puis, appeler une méthode nommée move() que vous définirez tel que décrit au paragraphe suivant. Toutefois, avant d'appeler cette méthode move(), il faudra vérifier si le robot a atteint le composant à visiter, c'est-à-dire vérifier si la position du robot coïncide avec
celle du composant à visiter. Dans ce cas, il faudra récupérer le composant suivant de la liste des composants à visiter, et faire en sorte que le robot se déplace maintenant vers ce composant. Lorsque la fin de la liste est atteinte, le robot devra se diriger à nouveau vers le premier composant de la liste des composants à visiter.
Définir la méthode move() de la classe Robot. Dans cette méthode, incrémenter (ou décrémenter selon le cas) les coordonnées x et y du robot d'une valeur telle que définie par un attribut spécifiant la vitesse du robot, de manière que le robot se rapproche du composant à visiter. Pour l'instant, considérer qu'il n'y a pas d'obstacle entre le composant à visiter et le robot. Les obstacles seront gérés lors d'un TP ultérieur.
Les observateurs
Ouvrir la bibliothèque canvas-viewer.jar située dans le répertoire libs de votre projet Eclipse de simulateur d'usine robotisée. Dans le package fr.tp.inf112.projects.canvas.controller se trouvent les interfaces CanvasViewerController, Observable et Observer.
Ouvrir la classe CanvasViewer de la bibliothèque canvas-viewer.jar. Vous constaterez que cette classe, qui joue le rôle de la vue (et donc de l'observateur) du MVC, implémente l'interface Observer. Cette interface ne définie qu'une seule méthode, modelChanged, qui devra être appelée par votre modèle à chaque fois que ses données auront changées, afin que la ou les vues puissent réafficher les données et que l'affichage reste cohérent avec les données du modèle.
Examiner le corps de la méthode modelChanged dans la classe CanvasViewer. Celle-ci ne fait qu'appeler les méthodes repaint de la barre de menu et du panneau qui affiche les figures de votre modèle. Les menus Start Animation et Stop Animation de la barre de menus de la fenêtre seront alors activés ou non selon que le modèle est en cours de simulation ou non, tel que déterminé par un appel de la méthode isAnimationRunning() de l'interface de contrôleur.
Implémenter l'interface de l'observable
Afin de notifier la vue lorsque ses données changent, votre modèle devra implémenter l'interface Observable :
public class Factory extends Component implements Canvas, Observable {
Tel que vu en classe, cette interface ne contient que deux méthodes servant à ajouter ou à enlever un observateur de l'observable. Afin de les implémenter, votre classe Factory devra donc déclarer un attribut servant à contenir les différents observateurs.
Notifier la ou les vue(s) lorsque les données du modèle ont changé
Puisque le simulateur s'appuie sur une architecture MVC, il faudra modifier le code de votre modèle pour que tout changement de ses donnés, telles que les coordonnées d'un robot qui se déplace par exemple, puisse notifier les vues qui auront été enregistrées comme observatrices du modèle. Ainsi, ces dernières pourront se réafficher pour visualiser les données modifiées du modèle.
A cette fin, dans la classe Factory, il faudra créer une méthode notifyObservers() qui appellera la méthode modelChanged() de chaque observateur qui se sera enregistré auprès du modèle. Cette méthode notifyObservers() devra être appelée par le modèle à chaque fois que ses données seront modifiées, et ce pour toutes les classes de votre
modèle devant être visualisées par l'interface graphique.
Il y a plusieurs façons de réaliser cela. Une manière simple consiste à ajouter un attribut de type Factory à la classe Component, de manière à ce que tout composant puisse appeler la méthode notifyObservers() de la classe Factory lorsque ses données sont changées via appels des mutateurs (setters).
Implémenter l'interface du contrôleur
Ouvrir l'interface CanvasViewerController de la bibliothèque canvas-viewer.jar. Il vous faudra créer une classe SimulatorController qui implémente cette interface. Une instance de cette classe devra être passée en argument du constructeur de la classe CanvasViewer que vous utiliserez pour visualiser votre modèle.
L'interface CanvasViewerController hérite de l'interface Observable. Cela veut dire que la vue s'enrtegistre auprès du modèle indirectement via le controleur. Il vous faudra donc dans votre contrôleur maintenir une référence vers le modèle et implémenter les méthodes de l'interface Observable en appleant les méthodes correspondantes du modèle.
En lisant la Javadoc des méthodes de l'interface CanvasViewerController, fournissez des implémentations des différentes méthodes requises par l'interface. Le contrôleur sert à démarrer et arrêter la simulation via les méthodes startAnimation() et stopAnimation(), qui sont appelées par la vue lorsque les menus du même nom sont sélectionnés. Par ailleurs, un appel à la méthode isAnimationRunning() permet de dire à la vue si la simulation est en cours d'exécution ou non, afin que celle-ci puisse gérer l'activation des menus de contrôle de l'animation.
Afin d'implémenter les méthode startAnimation(), stopAnimation() et isAnimationRunning() de votre classe contrôleur, ajouter un attribut à la classe Factory qui mémorisera si la simulation est en cours d'exécution ou non. Ajouter également des méthodes startSimulation(), stopSimulation() et isSimulationRunning() à la classe Factory pour gérer cet attribut. N'oubliez pas de notifier les observateurs lorsque la valeur de cet attribut a changé.
Dans la méthode startAnimation() du contrôleur, il faudra appeler la méthode startSimulation() de la classe Factory.
factoryModel.startSimulation();
Dans la méthode startSimulation() de la classe factory, ajouter le
code suivant :
while (factoryModel.isSimulationStarted()) {
factoryModel.behave();
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
}
Cette boucle appelle d'abord la méthode behave() puis appelle la méthode sleep() qui suspend ainsi l'exécution de la simulation pendant 200 millisecondes entre chaque exécution de la méthode behave(). Cela a pour but d'éviter que l'animation ne s'exécute trop rapidement et ainsi de permettre à l'utilisateur de mieux visualiser la simulation. Cette valeur peut être changée au besoin.
L'interface graphique prendra soin de lancer la simulation de votre modèle dans une tâche (thread) dédiée afin de ne pas perturber son affichage, qui s'exécute dans une autre tâche spécifique de la JVM nommée Event Dispatch Thread.
Noter les instructions try -- catch permettant de gérer l'exception InterruptedException, qui serait levée si une autre tâche venait à interrompre la tâche dans laquelle s'exécute le simulateur.
Finalement, implémenter les méthodes stopAnimation() et isAnimationRunning() de votre classe contrôleur en déléguant ces opérations aux méthodes stopSimulation() et isSimulationRunning() de la classe Factory.
Lancement de la simulation
Afin de tester votre simulation, modifier la méthode main() de la classe SimulatorApplication créée lors du TP précédent et ajouter quelques robots en leur donnant quelques composants à visiter.
Puis instancier la classe SimulatorController et la classe CanvasViewer de l'interface graphique, en utilisant cette fois le constructeur prenant en paramètre un contrôleur, et non pas un objet de type Canvas.
Lancer l'application et vérifier que votre robot se déplace correctement en visitant successivement les composants qu'il est censé visiter.
TP 7 : Sauvegarder le modèle d'usine robotisée
Au cours du TP précédent, vous avez modifié votre modèle OO d'usine robotisée pour que les robots puissent se déplacer d'un composant de l'usine à l'autre. Vous avez également implémenté les interfaces MVC fournies par l'interface graphique canvas viewer afin que les déplacements de ces robots puissent être automatiquement visualisés.
Au cours de ce TP, vous allez développer la couche d'accès aux données (ou de persistance), de votre simulateur afin de pouvoir stocker les données du modèle d'usine robotisée sur un support permanent (disque SSD ou base de données). Ces données pourront ainsi être relues lorsque le simulateur sera redémarré.
Cette couche de persistance s'appuiera sur les différentes classes de Java servant à réaliser les entrées et sorties de données, comme l'écriture et la lecture de données dans des fichiers, sous format texte ou binaire.
Introduction aux entrées-sorties en Java et à la persistance des données
Lire la présentation sur les entrées et sorties en Java disponible ici. Puis lire la présentation sur la persistance des données ici.
L'interface du contrôleur et la persistance
L'interface CanvasViewerController possède une méthode nommée getPersistenceManager tel que déclarée dans le code suivant :
/**
* Returns the persistence manager to be used to persist this canvas
* model into a data store.
* @return A non {@code null} {@code CanvasPersistenceManager}
* implementation for the desired data storage kind
* (file, database, etc.).
*/
CanvasPersistenceManager getPersistenceManager();
Cette méthode doit retourner un objet d'une classe qui devra implémenter l'interface CanvasPersistenceManager.
Implémenter l'interface de persistance
Examiner la documentation de l'interface CanvasPersistenceManager. Tel que vu dans la présentation sur la persistance des données, cette interface spécifie des signatures de méthodes décrivant la lecture, l'écriture (persist) et la suppression d'un modèle de canevas.
Pour ce simulateur, nous allons implémenter une couche de persistance de données s'appuyant sur le système de fichiers de l'ordinateur qui exécute le programme. Afin de simplifier l'implémentation de l'interface CanvasPersistenceManager, créer une classe qui hérite de la classe abstraite AbstractCanvasPersistenceManager. Cette classe est fournie par la bibliothèque canvas viewer.
L'interface CanvasChooser
La classe AbstractCanvasPersistenceManager contient un attribut de type CanvasChooser, et son constructeur prend en paramètre un objet de ce même type. Examiner l'interface CanvasChooser et sa documentation. Elle spécifie une méthode chooseCanvas, qui retourne une chaîne de caractère identifiant de manière unique un canevas.
Il n'est pas nécessaire de fournir une classe qui implémente l'interface CanvasChooser. Puique les modèles seront stockés dans le système de fichiers de l'ordinateur, vous pouvez simplement instancier la classe FileCanvasChooser, qui implémente CanvasChooser, et qui est fournie par la bibliothèque canvas viewer.
La classe FileCanvasChooser permet de parcourir le système de fichiers de l'ordinateur et de sélectionner un fichier dont l'extension attendue peut être spécifiée par un paramètre du constructeur de la classe. En effet, lorsqu'un utilisateur de l'interface graphique Canvas Viewer sélectionnera le menu File >> Open Canvas, un objet de type
FileCanvasChooser sera utilisé pour présenter à l'utilisateur une liste de fichiers et de dossiers, qu'il pourra parcourir pour choisir un modèle à visualiser.
Modéliser un identifiant unique du modèle
Pour stocker vos modèles dans le système de fichier (qui joue ici le rôle de base de données), chaque instance de votre modèle d'usine devra fournir une chaîne de caractères qui servira à l'identifier de manière unique dans le système de fichier. A cette fin, les méthodes getId() et setId() sont fournies par l'interface Canvas, de sorte que votre classe représentant l'usine robotisée (Factory), et implémentant l'interface Canvas, devra fournir un attribut servant d'identifiant ainsi que les accesseurs de cet attribut.
Lors de la sauvegarde du modèle via l'interface graphique Canvas Viewer, l'objet de type FileCanvasChooser sera également utilisé afin que l'utilisateur puisse choisir un nom de fichier existant dans le système de fichier, ou encore saisir un nouveau nom de fichier. Cet identifiant sera ensuite récupéré de l'objet FileCanvasChooser et
valorisé dans le modèle d'usine par l'appel de la méthode setId() de votre classe. Il sera constitué du nom du fichier et de son chemin dans l'arborescence des dossiers.
Rendre les classes du modèle sérialisables
Afin de simplifier la représentation de votre modèle dans un fichier, vous allez utiliser la sérialisation des données, telle que vue dans la présentation du cours sur les entrées et sorties de données. A cette fin, toute classe de votre modèle devant être sauvegardée devra implémenter l'interface Serializable. Modifier donc les classes de votre modèle pour leur faire implémenter cette interface. Remarquer que cette interface est une interface de marquage, qui ne déclare donc aucune méthode et ne sert qu'à marquer des classes.
Il arrive cependant que certaines des données des classes ne nécessitent pas d'être mémorisées d'une exécution à l'autre du programme. C'est notamment le cas des observateurs du modèle. Il est en effet préférable de ne pas stocker ces objets, d'autant plus qu'ils consistent en des instances des classes d'interface graphique Java qui ne sont pas toujours sérialisables.
Utiliser donc le mot clé transient pour déclarer que l'attribut des observateurs ne doit pas être sérialisé. Ne pas oublier de modifier l'instanciation de l'attribut pour utiliser une instanciation paresseuse, étant donné que lors de la désérialisation de l'objet, aucun constructeur de classe ne sera appelé, tel que vu dans la présentation sur les entrées-sorties.
Faites de même pour tous les autres attributs de vos classes du modèle qui ne doivent pas être sérialisés.
Implémenter les méthodes de persistance
La classe abstraite AbstractCanvasPersistenceManager ne fournie pas de corps pour les méthodes read, persist et delete de l'interface CanvasPersistenceManager. A vous de les implémenter dans votre classe de persistance en utilisant les flux de sérialisation d'objets ObjectInputStream et ObjectOutputStream tels qu'introduits dans la présentation sur les entrées-sorties.
En ce qui concerne la méthode delete, une manière simple de coder cette méthode consiste à instancier un objet de la classe java.io.File et d'appeler la méthode delete sur cet objet.
Tester la persistance du modèle
Afin de tester la persistance de votre modèle, lacer le simulateur tel que vous l'avez fait au TP précédent. Utiliser les sous-menus Open Canvas et Save Canvas du menu File de l'interface Canvas Viewer et vérifier que vous pouvez bien sauvegarder le modèle sur disque, puis le réafficher correctement dans l'interface graphique.
Mettre en place le logging de l'application
Nous avons vu en classe qu'il est préférable d'utiliser une bibliothèque de logging pour afficher les traces d'exécution du programme plutôt que d'utiliser directement la sortie console du système, afin de pouvoir stocker les traces dans d'autres supports tels que des fichiers par exemple. Dans cette partie de l'exercice, il faudra mettre en place un logging à la fois à la console et dans un fichier log en utilisant et en configurant le logger du JDK.
Configurer le logger
Tel que vu en classe, le logging peut être configuré en code Java ou via un fichier de configuration. Pour toute installation d'un JRE, un fichier de configuration par défaut est fourni dans le répertoire d'installation du JRE. Tel qu'illustré par la capture d'écran suivante, ouvrir le répertoire de bibliothèque JRE System Library de votre projet de simulateur afin de connaître le répertoire d'installation de la JRE dans le système de fichier de votre ordinateur. A partir de ce répertoire, naviguer vers un sous-répertoire nommé conf et copier le fichier logging.properties.

Créer un répertoire nommé config dans votre projet de simulateur et y déposer le fichier logging.properties précédemment récupéré tel qu'illustré par la capture d'écran suivante :

Ouvrir le fichier logging.properties dans Eclipse et modifier le fichier pour que deux handlers soient utilisés ; un premier handler qui écrira à la console et un second qui écrira dans un fichier tel qu'illustré par la capture d'écran suivante.

Ensuite, tel qu'illustré par la capture d'écran suivante, spécifier le chemin du fichier log pour qu'il soit créé dans le répertoire config du projet du simulateur, puis sauvegarder le fichier de configuration du logger.

Puis spécifier l'endroit du fichier de configuration de logging à utiliser par votre simulateur. Cela peut se faire en spécifiant la valeur de la propriété de la java.util.logging.config.file telle qu'illustrée par la capture d'écran suivante :

Finalement, sauvegarder également votre configuration de lancement du simulateur dans votre projet. Cela facilitera l'évaluation de votre projet par les enseignants, qui pourront utiliser directement votre configuration de lancement pour exécuter votre simulateur et vérifier son bon fonctionnement.
Cela se fait en sélectionnant l'onglet nommé Common puis en spécifiant le répertoire config dans le champ de texte de l'option Shared file tel qu'illustré par la capture d'écran suivante :

Définir un objet de type Logger pour afficher les messages
Dans la classe de l'application de votre simulateur (SimulatorApplication), créer une variable pour le logger telle de celle de la ligne suivante :
private static final Logger LOGGER = Logger.getLogger(Main.class.getName());
Puis, afficher deux messages d'information notifiant le démarrage de votre simulateur, de deux niveaux de détail différents : INFO et CONFIG, tel que donné dans le code suivant :
LOGGER.info("Starting the robot simulator...");
LOGGER.config("With parameters '" + Arrays.toString(args) + "'.");
Vérifier le bon fonctionnement du logger
Exécuter votre simulateur et observer les messages qui s'affichent à la console. Dans l'explorateur de projet de Eclipse, rafraîchir le dossier config. Le fichier log généré par le logger devrait apparaître. Vérifier les messages qui sont écrits dans le fichier. Est-ce que tous les messages s'affichent bien dans la console et dans le fichier log ? Si non, pourquoi ?
Examiner le contenu du fichier logging.properties et modifier le niveau de logging au niveau CONFIG et vérifier que les deux messages s'affichent bel et bien dans le fichier log.
Puis spécifier un niveau de logging plus fin (FINE par exemple) et relancer le simulateur. Est-ce que les messages de logging du simulateur s'affichent toujours ? Vous constaterez que plusieurs messages en provenance de l'interface graphique s'affichent maintenant à ce niveau de détail. Si les messages du simulateur ne s'affichent plus, examiner la valeur de la propriété java.util.logging.FileHandler.limit et rechercher sa documentation pour corriger le problème.
Noter que le fichier log est écrit au format xml, alors que les données sont écrites dans un autre format dans la console. Comment expliquez-vous cette différence ?
TP 8 : Eviter les obstacles
Au cours du TP 6, vous avez mis en œuvre le MVC afin de visualiser dans l'interface graphique tout changement des données du modèle. Puis vous avez défini un comportement très simple des robots de l'usine consistant à visiter une liste de composants de l'usine. Ces déplacements ne prenaient pas en compte les différents obstacles dans l'usine tels que les murs des salles par exemple.
Au cours de ce TP, il faudra raffiner ce déplacement des robots afin de contourner les obstacles dans l'usine. A cette fin, vous utiliserez une bibliothèque de calcul de trajectoire qui vous sera fournie.
Le calcul de trajectoire
Il existe plusieurs méthodes de calcul de trajectoires pour la robotique, tout comme pour les jeux vidéo. L'approche que vous allez mettre en œuvre pour ce projet s'inspire de ce qui est décrit ici.
La première étape consiste à créer une cartographie de l'espace dans lequel évoluent les robots. Cette cartographie déterminera l'occupation des différents objets contenus dans l'espace représenté. Une approche typique consiste à discrétiser cet espace tel qu'illustré par la figure suivante.

La partie de gauche de la figure représente une discrétisation de l'espace en carrés, dont la taille dépendra de la précision requise pour la simulation, ainsi que des ressources mémoire et de traitement allouées à cette simulation. En effet, une discrétisation très fine permettra de déterminer des trajectoires très précises, mais demandera plus de mémoire et de temps de calcul. Ceci-dit, en cas de problèmes, il est possible d'optimiser la quantité de mémoire requise pour stocker la cartographie en la représentant sous forme d'arbre kd de manière que seules les surfaces contenant des frontières d'obstacles soient cartographiées, tel qu'illustré par le côté droit de la figure précédente.
Lorsque l'espace aura été discrétisé, il pourra être vu comme un graphe. Ainsi, chaque case sera vue comme un sommet du graphe, et pour chaque case, on ajoutera une arête entre le sommet de cette case et le sommet de chacune des cases qui lui sont adjacentes. Cependant, on ne va pas nécessairement prendre en compte toutes les cases adjacentes. En effet, si une case adjacente coïncide avec un obstacle, on n'ajoutera pas d'arête entre le sommet de la case et la case de cet obstacle, ce qui permettra de rendre les sommets des obstacles non accessibles.
Il faudra donc être capable de détecter les obstacles, afin de savoir si une case adjacente doit être reliée par une arête ou non. Là encore, plusieurs approches sont possibles pour détecter les obstacles. Une approche fréquemment utilisée consiste à considérer que l'objet se déplaçant est un point, et d'augmenter la taille des objets à contourner de la moitié de la dimension maximale de l'objet se déplaçant, tel qu'illustré par la figure suivante.

Cela nécessite cependant des calculs pouvant être assez compliqués, dépendamment de la forme des objets. Il existe toutefois différentes bibliothèques permettant de modéliser des formes géométriques telles que Apache Commons Geometry, mais leur utilisation demeure compliquée.
Une approche très souvent utilisée dans les jeux vidéo consiste à approximer toute forme par une forme de taille similaire à la forme réelle, mais dont les contours sont plus simple tel qu'un cercle ou un rectangle par exemple. C'est cette dernière approximation que vous allez utiliser dans ce TP. Une explication de cette méthode se trouve ici.
Lorsque l'espace sera vu comme un graphe, on pourra utiliser des algorithmes très connus comme celui de Dijkstra qui permet de trouver le plus court chemin dans un graphe. Cette recherche de chemin, illustrée par une animation ici est celle que vous utiliserez dans ce TP. Tout comme pour l'interface graphique de visualisation, vous utiliserez une bibliothèque existante à intégrer à votre projet pour calculer le plus court chemin entre deux composants de l'usine robotisée.
Il existe plusieurs autres algorithmes de calcul de trajectoires tel que A*, qui ajoute des heuristiques à l'algorithme de Dijkstra afin de ne pas avoir à effectuer une recherche complète sur tous les sommets du graphe. Plus récemment, des algorithmes mettant en œuvre de l'apprentissage automatique (machine learning) ont également été développées pour calculer ces trajectoires.
Créer une interface de calcul de trajectoire
Tel que vu précédemment, le calcul de trajectoire est un sujet relativement complexe, et il existe plusieurs algorithmes pour faire ce calcul ayant des propriétés différentes. Ainsi il sera préférable d'encapsuler le calcul de trajectoire dans une classe dédiée, tel que préconisé par le principe de responsabilité unique en OO. Par ailleurs, puisqu'il existe plusieurs algorithmes, il sera préférable que votre modèle d'usine ne connaisse qu'une interface de calcul de trajectoire, découplant ainsi votre modèle d'usine des dépendances techniques liées à des algorithmes précis. Cela permettra également de facilement changer l'algorithme sans modifier les classes du modèle.
A cette fin créer une interface nommée FactoryPathFinder. Dans cette interface définir une signature de méthode nommée findPath(). Cette méthode définira deux paramètres, le premier étant un composant source et le deuxième un composant cible. Ces deux paramètres constitueront les sommets de départ et d'arrivée du chemin le plus court à calculer.
La méthode findPath() retournera une liste d'objets de type Position, une classe simple qui encapsule les coordonnées x et y dans un plan. Si cette classe Position n'existe pas encore dans votre modèle, vous pouvez la créer et modifier votre modèle afin qu'il stocke les coordonnées des composants sous forme d'instances de cette classe Position.
Modifier ensuite votre classe Robot afin de pouvoir lui fournir un objet de type FactoryPathFinder. Modifier le comportement de votre classe Robot (méthode behave()) pour utiliser l'objet FactoryPathFinder. A chaque fois que le composant à visiter change, calculer une nouvelle trajectoire pour cette nouvelle cible, et à chaque cycle de la simulation, déplacer le robot à la position suivante de la trajectoire calculée précédemment, et ce jusqu'à ce que la cible soit atteinte. Puis mettre à jour la nouvelle cible et ainsi de suite.
Implémenter l'interface de calcul de trajectoire
Il faut maintenant développer une classe implémentant l'interface FactoryPathFinder. Cette classe mettra en œuvre l'algorithme de Dijkstra en appelant une bibliothèque de modélisation de graphes existante.
Deux bibliothèques de modélisation de graphes
Choisir la bibliothèque que vous utiliserez parmi les deux bibliothèques décrites dans les sous-sections suivantes.
La bibliothèque JGraphT
JGraphT est une bibliothèque très riche et très utilisée pour différents traitements sur des graphes. Elle est également très bien documentée et open source. Elle fournit plusieurs classes pour modéliser différents types de graphes, dirigés ou non. Si vous choisissez cette bibliothèque, vous utiliserez la classe DefaultDirectedGraph. Cette classe est générique (tout comme l'interface List et ses classes telle que ArrayList), et peut donc être utilisée avec les classes de votre choix pour modéliser les sommets et les arêtes. Vous devrez utiliser votre propre classe pour représenter les sommets, et utiliser la classe DefaultEdge, fournie par JGraphT pour représenter les arêtes. Vous utiliserez la classe DijkstraShortestPath et sa méthode findPathBetween pour calculer le chemin le plus court entre deux sommets.
Configuration du projet du simulateur dans Eclipse
La bibliothèque JGraphT se télécharge ici (menu DOWNLOAD). Ajouter cette bibliothèque à votre projet dans Eclipse tel que décrit dans le TP 5 pour la bibliothèque canvas-viewer.jar. Configurer votre projet pour pouvoir utiliser les deux fichiers jgrapht-core-\<version\>.jar et jheaps-\<version\>.jar de JGraphT tel qu'illustré par la capture d'écran suivante.

La documentation de JGraphT se trouve ici, incluant une section Hello JGraphT pour vous aider à démarrer si vous choisissez d'utiliser JGraphT.
La bibliothèque « fait-maison » Graph
Cette bibliothèque, plus simple mais plus limitée que JGraphT fourni différentes interfaces modélisant un graphe (Graph), ses sommets (Vertex) et ses arcs (Edge), ainsi que des implémentations de base de ces interfaces. Pour cet exercice, vous utiliserez les classes GridGraph, GridVertex et GridEdge, qui implémentent les interfaces précédentes et facilitent la conversion d'une grille (telle que le plan de l'usine robotisée discrétisée) en graphe.
Cette bibliothèque contient également une classe nommée DijkstraAlgorithm qui fournit une méthode statique nommée findShortestPath(). Cette méthode prend en paramètre un objet de type Graph et deux objets de type Vertex représentant le sommet de départ et le sommet d'arrivée du chemin à trouver. Elle retourne une liste de sommets adjacents, débutant par le sommet de départ et se terminant par le sommet d'arrivée, et définissant le chemin le plus court entre ces sommets.
Configuration du projet du simulateur dans Eclipse
La bibliothèque « fait-maison » Graph se télécharge ici. Ajouter cette bibliothèque à votre projet Eclipse tel que décrit précédemment pour JGraphT. Configurer votre projet pour utiliser le fichier graph.jar tel qu'illustré par la capture d'écran précédente.
Approche d'utilisation de la bibliothèque de graphes
Pour utiliser l'interface graphique Canvas Viewer afin de visualiser votre modèle, vous avez fait implémenter les interfaces de programmation Canvas et Figure par votre modèle. Ces interfaces définissent un point de vue sur votre modèle. Dans le cas de l'algorithme de Dijkstra, vous utiliserez une autre approche, celle de la transformation de
modèles. Cette approche se définie comme étant la génération automatique d'un ou de plusieurs modèles cibles à partir d'un ou de plusieurs modèles source. Elle constitue un ingrédient essentiel à l'ingénierie dirigée par les modèles afin de pouvoir utiliser différents outils d'analyse des modèles, dont les formats d'entrée ne sont pas les mêmes que celui du modèle source.
La raison de ce choix, par rapport à celui de point de vue utilisé pour visualiser votre modèle d'usine robotisée, est que pour ce simulateur, on souhaite évaluer différents types d'algorithmes de calcul de trajectoire, ce qui pourra être réalisé simplement en utilisant différentes classes d'implémentations de l'interface FactoryPathFinder. Cette approche permet de minimiser le couplage entre les classes du modèle de l'usine robotisée et les classes de recherche de chemin. Cela n'est pas le cas pour la visualisation de l'usine robotisée, qui n'utilise qu'un seul type de visualisation. En faisant directement implémenter interfaces de canevas par le modèle d'usine robotisée, ce dernier est par conséquent fortement couplé à la bibliothèque Canvas Viewer. En revanche, l'utilisation de l'approche point de vue permet de meilleures performances que la transformation de modèles, car il n'est pas nécessaire de reconvertir tous les objets de l'usine en objets de canevas à chaque fois que les données de cette dernière ont changées, ce qui rendrait la visualisation bien moins fluide.
Transformer un modèle d'usine robotisée en graphe
Pour calculer le plus court chemin entre un robot et sa cible, il faudra développer du code Java pour transformer un modèle d'usine robotisée en modèle de graphe. Il faudra d'abord instancier un objet de la classe représentant un graphe de la bibliothèque de graphes que vous avez choisie (voir sections précédentes). Puis il faudra instancier des objets sommets et arêtes du graphe pour représenter les cases du plan de l'usine et leurs accessibilités.
La classe représentant les sommets devra pouvoir mémoriser la position de la case correspondante du plan de l'usine afin de pouvoir convertir le chemin calculé en une liste de positions à visiter par les robots, tel que définie par la méthode findPath de l'interface FactoryPathFinder.
Si vous utilisez JGraphT, vous pouvez utiliser n'importe quelle classe de votre choix pour représenter les sommets, et utiliser la classe DefaultEdge de JGraphT pour représenter les arêtes. Si vous utilisez la bibliothèque fait maison Graph, la classe des sommets devra être GridVertex ou une sous-classe de cette dernière, et les classes des sommets et arêtes seront respectivement GridVertex et GridEdge.
Pour chaque case de l'usine, il faudra construire un sommet en instanciant la classe de sommet choisie, qui mémorisera la position de la case associée. Puis lorsque tous les sommets du graphe auront été créés, il faudra construire les arêtes entre ces sommets. En première approximation, considérer que les robots ne peuvent se déplacer que dans les directions x (largeur) et y (hauteur). Ainsi, pour une case donnée, seules les quatre cases adjacentes à gauche, à droite, en haut et en bas seront reliées à la case centrale par une arête. Pour l'instant, considérer qu'il n'y a pas d'obstacles dans l'usine.
Calculer le plus court chemin
Dans la méthode findPath() de votre classe implémentant l'interface FactoryPathFinder, appeler la méthode de recherche de plus court chemin en utilisant les bonnes classes de la bibliothèque choisie. Il faudra ensuite convertir la liste de sommets retournée en liste de positions dans l'usine, que la méthode findPath() doit retourner tel que requis par l'interface FactoryPathFinder.
Tester le bon fonctionnement du calcul de trajectoire sans obstacles
Le développement de cette partie du simulateur étant plus complexe que les développements des TP précédents, utiliser une approche de développement incrémentale. Ainsi, tester d'abord votre algorithme de calcul de trajectoire sans prendre en compte les obstacles de l'usine.
Pour ce faire, instancier une usine contenant un robot et deux machines situées dans deux salles différentes. Chaque salle sera dotée d'une porte. Ajouter une station de recharge dans l'usine. Spécifier que les deux machines et la station de recharge devront être visitées par le robot. Lancer l'interface graphique et vérifier que votre robot se déplace correctement en visitant successivement les deux machines et la station de recharge, sans toutefois prendre en compte les obstacles constitués par les murs des salles.
Détecter les obstacles
Afin de contourner les obstacles, il faudra éviter d'ajouter une arrête si la case adjacente est localisée sur un objet ne devant pas être traversé par le robot. Cela est illustré par cette animation.
Une bonne façon de procéder consiste à déléguer la décision de savoir si une case contient un objet bloquant à l'objet Factory, car celui-ci connaît tous les composants. Vous pouvez par exemple ajouter une méthode overlays() à votre classe Component, qui prendra en paramètre la position d'un sommet du graphe et déterminera si celle-ci chevauche le composant et si ce composant est un obstacle. A cette fin, vous pouvez approximer la forme des composants de l'usine en utilisant une forme simple de taille similaire à la forme réelle tel que décrit précédemment.
L'objet Factory, à qui l'on demandera si un sommet chevauche l'un de ses composants, parcourra ainsi tous les composants pour appeler cette méthode overlays() et déterminer si un des composants constitue un obstacle, auquel cas on ne construira pas d'arc vers ce sommet.
La méthode overlays() pourra être redéfinie dans les sous-classes de composants pour prendre en compte leur spécificités. A cette fin considérer qu'une salle est composée de murs, d'une certaine épaisseur et de portes. Un mur constitue évidemment un obstacle et ne peut pas être chevauché. Une porte ne constitue un obstacle que si elle est fermée. Considérer que les aires n'ont pas de murs et peuvent donc être traversées par les robots. Faire également l'hypothèse simplificatrice que les machines, convoyeurs et stations de recharge peuvent être traversées par les robots.
Ne pas oublier de gérer le cas où aucun chemin n'existe entre un robot et sa cible. Consulter la documentation de la bibliothèque de graphe que vous avez choisie d'utiliser pour connaître ce qui sera retournée par l'algorithme pour ce cas particulier. Vous pouvez indiquer cet état de blocage du robot visuellement dans l'interface graphique en changeant le style de la figure retournée par le robot ou son libellé par exemple.
Gérer également le cas d'exception où un autre robot serait localisé dans la prochaine case où un robot doit se déplacer. Dans ce cas, le robot ne doit pas se déplacer, et ne continuera sa trajectoire que lors du prochain appel de la méthode behave() si la case a été libérée.
Tester le bon fonctionnement de la détection des obstacles
Relancer le simulateur avec le modèle décrit précédemment et vérifier que votre robot contourne bien les obstacles en n'entrent dans les salles que par leurs portes et que si celles-ci sont ouvertes. Tester également en instanciant plusieurs robots pour vérifier que ceux-ci n'entrent pas en collision.
Pour aller plus loin...
Les deux derniers exercices suivants sont optionnels et ne seront pas évalués, mais peuvent être amusants à développer.
Autoriser des déplacements en diagonale des robots
Modifier votre code pour que les robots puissent se déplacer en diagonale, en plus des orientations le long des axes x et y. Que constatez-vous ? Les robots se déplacent-ils toujours en prenant le chemin le plus court ? Proposer une solution à ce problème.
Gérer l'énergie des robots
Définir un niveau d'énergie pour les robots. Pour un robot, diminuer cette quantité d'énergie d'une valeur fixe à chaque déplacement. Lorsque la quantité d'énergie est basse, le robot calculera une trajectoire vers chaque station de recharge et ne décidera de continuer son travail que si la quantité d'énergie restante est suffisante. Sinon, il se dirigera vers la station de recharge la plus proche. Le comportement de cette station de recharge, lorsqu'elle détectera qu'un robot est présent, sera de recharger le robot jusqu'à ce que la batterie soit pleine, auquel cas le robot pourra poursuivre son travail.
Développer une porte automatisée
Implémenter un comportement pour les portes afin que celles-ci s'ouvrent automatiquement lors qu'un robot arrive à proximité et qu'elles se referment après le passage du robot.
Liens utiles et téléchargements
Cette page rassemble les outils externes importants, bibliothèques et téléchargements de cours référencés dans les TPs.
Outils externes
- Eclipse IDE : Eclipse IDE Java Developers download
- Oracle JDK : Oracle JDK downloads
- OpenJDK : OpenJDK install instructions
Téléchargements du cours
- Canvas Viewer (canvas-viewer.jar) : Download canvas-viewer.jar
- Bibliothèque helper (graph.jar) : Download graph.jar
- Matériel supplémentaire : Entrées/Sorties : INF112 I/O (PDF)
- Matériel supplémentaire : Persistance : INF112 Persistence (PDF)
Bibliothèques et références
- JGraphT : JGraphT official site
- Apache Commons Geometry : Apache Commons Geometry
- Path planning (MIT notes) : MIT path planning notes
- Dijkstra (explication/animation) : Dijkstra algorithm (Wikipedia, French)
- k-d tree (explication) : k-d tree (Wikipedia, French)
- Tutoriel supplémentaire sur Git: Introduction to Git
Utilitaires & outils optionnels
- 7-Zip : 7-Zip download
- Copilot pour Eclipse (optionnel) : Copilot4Eclipse marketplace
Inspiration pour le projet du cours
- Hasso-Plattner Institute : CPSLab resources (HPI)