Lancez Visual Studio Code (VS Code par la suite), par exemple en exécutant la commande code dans le terminal, ou en cliquant sur un raccourci; la touche [Alt] fait apparaître / disparaître la barre du menu horizontale.
Dans VS Code tapez la combinaison de touches [Ctrl]+[Shift]+[P] (ou [Alt]+[V] puis cliquez sur command Palette) puis tapez «java: create» dans la barre qui apparaît et sélectionnez Create Java Project et l'option No build tools.
Une fenêtre s'ouvre et vous invite à choisir votre espace de travail.
Indiquez le nom de votre projet (TD9) dans la barre de texte.
Activer assert dans votre machine virtuelle Java
Vérifier que les assert's sont activés en exécutant le programme
TestAssert.java.
En cas de problème, suivez la procédure ci-dessous dans Visual Studio Code: tapez la combinaison de touches [Ctrl]+[,] (la second touche c'est `virgule'), indiquez vm args dans la barre de recherche.
L'item Java>Debug>settings: Vm Args apparaît avec une barre de texte dans laquelle vous devez écrire -ea (pour enable asserts).
Consignes de programmation
Nommez les classes, variables et méthodes exactement comme le demande l'énoncé.
Pour des questions relatives à la syntaxe de Java, consultez ce mémento.
Documentation
Pour obtenir la description d'une classe Java standard, consultez internet. Il suffit souvent d'effectuer une recherche en indiquant Java suivi du nom de la classe dont vous souhaitez la documentation (e.g. Java LinkedList ou Java Array).
La documentation officielle (internet) est ici : Java 11.
Avertissement : tout votre code doit être écrit dans le fichier TD9.java
Afin de minimiser les risques d'erreurs de manipulation lors des
dépôts de fichiers, toutes
les classes que vous avez à modifier sont réunies dans un même
fichier TD9.java que vous devez déposer après chaque
question.
On rappelle néanmoins qu'en dehors de circonstances exceptionnelles, on écrit plutôt des classes différentes dans des fichiers différents, tant pour la lisibilité du code que pour l'efficacité de la compilation.
Jeu Rush Hour et but du TD
Rush Hour est un jeu de la société Thinkfun dont le but consiste à dégager une voiture d'un embouteillage. Les véhicules se déplacent sur une grille 6x6, soit horizontalement, soit verticalement. Chaque véhicule occupe 2 ou 3 cases de long. Les véhicules ne peuvent pas sortir du plateau, à l'exception de la voiture rouge qui peut sortir par la droite du plateau ; c'est là le but du jeu. Voici un exemple de configuration de départ :
L'objectif de ce TD est d'écrire un programme qui trouve la solution d'un tel problème, en un minimum de déplacements.
Si vous souhaitez vous familiariser avec ce jeu, vous pouvez jouer par exemple ici.
À télécharger
Extraire le contenu du fichier src.zip dans le répertoire
src du projet.
1. Représentation du problème
On adopte la représentation suivante du problème. Les 6 lignes sont numérotées de haut en bas, de 0 à 5, et les 6 colonnes de gauche à droite, de 0 à 5. La classe RushHour modélise un plateau de jeu général décrit par les cinq champs suivants :
int nbCars : le nombre de véhicules ;
String[] color : un tableau donnant la couleur de chaque véhicule (pour l'affichage de la solution) ;
boolean[] horiz : un tableau de booléens indiquant pour chaque véhicule s'il se déplace horizontalement ;
int[] len : un tableau donnant la longueur de chaque véhicule (len[i] représente le nombre de cases occupées par le véhicule i, et vaut 2 ou 3);
int[] moveOn : un tableau indiquant sur quelle ligne (resp. colonne) se déplace un véhicule horizontal (resp. vertical).
Par convention, la voiture rouge est le véhicule d'indice 0 et de longueur 2.
L'état du plateau à un moment du jeu est représenté par un objet de la classe State qui contient les champs suivants :
RushHour plateau : un pointeur vers un objet de la classe RushHour qui modélise les invariants du plateau de jeu.
int[] pos : un tableau indiquant la position de chaque véhicule (la colonne la plus à gauche d'un véhicule horizontal ou la ligne la plus haute d'un véhicule vertical) ;
int c, int d et State prev : indiquent que cet état a été obtenu à partir de l'état prev en déplaçant la voiture c de d cases (d vaut -1 ou +1 : -1 indique un déplacement vers la gauche ou vers le haut, +1 indique un déplacement vers la droite ou vers le bas);
Question 1.1 :
Complétez le constructeur State(RushHour plateau, int[] pos) de la classe State qui construit un état initial (les champs c, d et prev ne sont pas significatifs dans ce cas).
Complétez le constructeur State(State s, int c, int d) de la classe State qui construit un nouvel état à partir de s en déplaçant la voiture c de d case (d vaut -1 ou +1). On suppose que ce déplacement est possible. Attention : Il faut bien penser à instancier le champ plateau et le champ prev.
Complétez la méthode boolean success() de la classe State qui indique s'il s'agit d'un état correspondant à une fin de partie (i.e. la voiture rouge est située immédiatement devant la sortie).
Testez votre code en exécutant le fichier Test11.java.
Déposez le fichier TD9.java :
Question 1.2 : Complétez les méthodes :
boolean equals(Object o) de State qui renvoie true si l'objet o correspond au même état de jeu que l'état courant (quelque soit leur champ prev).
boolean[][] free() qui renvoie un tableau result de booléens indiquant quelles sont les places libres dans l'état courant. On adoptera la convention que result[i][j] représente la case sur la ligne i et la colonne j. On utilisera le pointeur plateau pour accéder aux champs nbCars, horiz, len et moveOn de la configuration générale du jeu.
Testez votre code en exécutant le fichier Test12.java.
Déposez le fichier TD9.java :
2. Déplacements possibles
À partir de maintenant, on ne travaille plus que dans la classe RushHour.
Question 2 : Complétez la méthode LinkedList<State> moves(State s) qui détermine l'ensemble des états que l'on peut atteindre à partir de l'état s en effectuant un unique déplacement (d'une seule case). Cet ensemble est représenté par une LinkedList<State>, l'ordre dans cette liste n'étant pas important. Pour écrire cette méthode, servez-vous de la méthode boolean[][] free de la classe State qui indique quelles sont les cases du plateau qui sont libres et que vous avez écrite dans la section précédente.
Testez votre code en exécutant le fichier Test2.java.
Déposez le fichier TD9.java :
3. Recherche d'une solution
On s'attaque maintenant à la recherche d'une solution, grâce à un parcours d'exploration des états du jeux.
3.1 Parcours en profondeur
On va commencer par implémenter un parcours en profondeur, qui représente la façon ``humaine'' de jouer.
Il y a deux idées pour cela
il faut mémoriser les états que l'on a déjà rencontrés, et pour cela on va utiliser un ensemblevisited de type HashSet<State> ; pour l'utiliser, on dispose des méthodes contains et add;
On utilise une pile qui contiendra au départ l'état initial.
Tant qu'elle n'est pas vide, on extrait le premier état de la pile. S'il correspond à une solution on a terminé. Sinon on ajoute tous ses voisins non déjà rencontrés dans la pile et dans l'ensemble visited.
Question 3.1 : Complétez la méthode State solveDFS(State s) pour qu'elle réalise cet algorithme. Elle doit renvoyer l'état correspondant à une solution (celui où la voiture rouge est située immédiatement devant la sortie).
Testez votre code en exécutant le fichier Test31.java.
Déposez le fichier TD9.java :
3.2 Parcours en largeur
On veut maintenant trouver la solution la plus courte (i.e. avec le moins de déplacement), pour cela on va utiliser une file.
Si on représente l'ensemble des états par un arbre dont la racine est l'état de départ et dont chaque noeud s a pour enfants les états que l'on peut atteindre à partir de s en effectuant un déplacement, on souhaite parcourir les noeuds de cet arbre « par niveaux », comme ceci :
1
/ \
/ \
2 3
/ | \ | \
4 5 6 7 8
/...
Question 3.2 : Complétez la méthode State solveBFS(State s) pour qu'elle réalise cet algorithme. Elle doit renvoyer l'état correspondant à une solution (celui où la voiture rouge est située immédiatement devant la sortie).
Testez votre code en exécutant le fichier Test32.java.
Déposez le fichier TD9.java :
4. Affichage d'une solution
Question 4 : Compléter la méthode void printSolution(State s) qui affiche une solution, étant donné l'état s correspondant à la solution (l'état final). On note que les états formant la solution sont chaînés à partir de s en suivant le champ prev. On s'appliquera à afficher la solution dans le bon ordre.
Cette méthode doit également afficher le nombre total de déplacements. On pourra ajouter un champ statique nbMoves à la class RushHour pour cela.
On utilisera possiblement une version récursive pour ce faire.
Testez votre code en exécutant le fichier Test4.java, qui doit donner quelque chose comme :
46 déplacements
on déplace le véhicule bleu vers la droite
on déplace le véhicule noir vers la droite
on déplace le véhicule vert vers le haut
on déplace le véhicule rose vers le bas
....
Note : votre solution peut différer de celle-ci (cela dépend de la façon dont la liste est construite par moves) mais elle doit comporter le même nombre de déplacements (46).