Lire les 10 points ci-dessous
- Il est préférable de réaliser tous les TP sous Linux, car une aisance d'utilisation de ce système vous sera utile dans les prochaines unités d'informatique.
- Objectif du TP : créer le minimum de classes pour reconstituer une première version du jeu d'aventure, très simplifiée et volontairement mal programmée. Le chapitre de livre qui vous guidera dans la réalisation de votre projet s'appuie sur cette version (appelée zuul-bad) pour vous montrer comment l'améliorer.
- Vous avez déjà dû essayer une version un peu plus évoluée pour vous faire une idée des fonctionnalités de base ; sinon, vous pourrez le faire en dehors de ce tp pour ne pas perdre de temps en présence d'enseignants (en cliquant ici, puis en téléchargeant le fichier zuul-with-images.jar, puis en tapant java -jar db_game.jar).
- Le nom des classes, des attributs et des méthodes a été volontairement conservé en anglais pour rester compatible avec le livre et les versions suivantes du projet.
- Lorsque
le compilateur vous signale une erreur, il faut traduire en français le message d'erreur
affiché en bas de la fenêtre ; l'endroit de l'erreur est surligné.
Lorsque c'est une classe de test qui vous signale une erreur (en
français), il faut souvent se reporter à un numéro de ligne.
Pour afficher les numéros de ligne dans l'éditeur de classes de BlueJ, il faut aller dans le menu Tools/Preferences... et cocher la case Display line numbers.
Si vous ne comprenez pas certains mots d'anglais, demandez à un intervenant - Répartissez les fenêtres sur votre écran pour pouvoir les voir simultanément : le texte de ce tp, le projet BlueJ, la classe que vous êtes en train d'écrire, et (sauf si vous savez déjà tout par coeur) le PDF du cours 2 (et pourquoi pas MaPremiereClasse du TP 2.1). Il est déraisonnable de ne pas voir simultanément l'énoncé du TP et le code que vous êtes en train d'écrire.
- Il est essentiel de bien suivre l'énoncé à la lettre ; sauter la moindre étape ou sous-étape peut au minimum faire échouer des tests, au pire vous envoyer dans une mauvaise direction et empêcher le moteur de jeu de fonctionner.
- Bien lire tous les paragraphes commençant par + car ils vous apprennent une nouvelle notion en programmation Java, au moment où vous en avez besoin (pour résoudre l'exercice précédent).
- Si
vous ne voyez pas comment faire un exercice après avoir cherché pendant quelques minutes,
vous pouvez regarder les indices
figurant à la fin de cet énoncé.
Si après avoir appliqué les directives données dans ces indices, vous n'arrivez toujours pas à faire l'exercice, appelez un intervenant pour ne garder aucune incompréhension ! - La séance Résa 3.1 est obligatoire pour tous ceux qui n'ont pas terminé le TP 3.1.
0. Le moteur de jeu zuul-bad
Pourquoi "-bad" ? Parce que mal programmé dans un premier temps.
Il sera composé de 5 classes ; chaque classe étant créée pour pouvoir construire des objets de cette classe, voyons
ci-dessous ce que représente dans le jeu un objet de chacune de ces classes.
Il n'y a rien à programmer dans cette partie 0, elle doit juste vous permettre de comprendre
le fonctionnement global du moteur de jeu que vous allez devoir construire dans les parties suivantes.
Ensuite, un exercice de la liste officielle des exercices vous demandera d'incorporer les vrais lieux de VOTRE jeu.
I. La classe Room
- Enregistrer dans un sous-répertoire TP3 du répertoire IPO ou A3P de son compte ESIEE le projet : zuul-bad3.jar joint à ce message
(lien tout en bas de la page).
Dans BlueJ, Open Zip/Jar..., chercher le fichier enregistré, puis double-cliquer sur l'icône v1.
Attention à ne pas détruire les lignes package v1; qui indiquent que les classes sont dans le répertoire v1.
Veillez à ce que les accolades { et } des classes et des méthodes soient en début de ligne. - Compléter la classe Room
qui devra représenter une pièce (ou plus généralement un lieu) dans le jeu d'aventure.
Pour l'instant, n'y créer qu'un attribut aDescription qui est une chaîne de caractères décrivant succinctement le lieu.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe RoomTest et exécuter testAttribut_2().
Si vous voyez apparaître tout en bas la mention 'testAttribut_2 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.RoomTest.testAttribut_2' pour voir le message d'erreur sous la barre rouge. [Si le test vous dit que vous devriez avoir 5 attributs, ignorez-le jusqu'au point 5 ci-dessous] - Écrire le
constructeur naturel
de cette classe (voir TP2 ou TD2 ou Cours2 si vous ne savez plus ce que c'est).
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe RoomTest et exécuter testConstructeur_3().
Si vous voyez apparaître tout en bas la mention 'testConstructeur_3 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.RoomTest.testConstructeur_3' pour voir le message d'erreur sous la barre rouge. - Écrire l'accesseur
pour l'unique attribut (appelé getter en anglais, car on leur donne un nom commençant par get suivi du nom de l'attribut, ici getDescription).
AIDE : voir des exemples d'accesseurs dans le TD2 ou le Cours2.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe RoomTest et exécuter testAccesseur_4().
Si vous voyez apparaître tout en bas la mention 'testAccesseur_4 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.RoomTest.testAccesseur_4' pour voir le message d'erreur sous la barre rouge.
Puis vérifier interactivement le bon fonctionnement de cette première version de la classe Room.
- Ajouter maintenant 4 attributs de type Room
correspondant au lieu se trouvant dans chacune des 4 directions
Nord, Est, Sud, Ouest, par rapport à la pièce courante. Le premier devrait
s'appeler aNorthExit.
+ La valeur par défaut de ces attributs est la valeur spéciale nullExceptionnellement, pour rester compatible avec le livre, parce-que vous élaborez une première version volontairement mal programmée (à ne jamais faire dans un autre exercice, ni surtout en contrôle) et que l'exercice 7.6 vous en expliquera l'inconvénient :
- déclarez public ces 4 attributs,
- ne modifiez pas le constructeur naturel : les attributs seront initialisés par la procédure de l'exercice suivant.
Exceptionnellement, nous choisissons de conserver cette valeur, sans l'écrire explicitement.
Une variable valant null ne désigne aucun objet ; on ne peut donc appeler aucune méthode dessus (sous peine d'obtenir une NullPointerException à l'exécution).
C'est la situation initiale dans les 4 directions (tant qu'on n'a pas appelé la méthode suivante) : il n'y a aucune sortie !
On ne pourrait d'ailleurs pas spécifier les 4 sorties en paramètres du constructeur de Room, car comment créer le premier lieu et spécifier ses 4 sorties avant d'avoir créé les autres lieux ??
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe RoomTest et exécuter testAttributs_5().
Si vous voyez apparaître tout en bas la mention 'testAttributs_5 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.RoomTest.testAttributs_5' pour voir le message d'erreur sous la barre rouge. (À partir de maintenant, ne plus utiliser les méthodes test..._1, _2, _3, et _4.)
- Écrire la procédure
setExits
(ça veut dire quoi en français ?) qui possède 4 paramètres : les 4 Room, pour permettre d'initialiser les 4 attributs précédemment définis.
Si un paramètre est null, cela veut dire qu'il n'y a pas de lieu dans cette direction.
Attention ! Bien choisir l'ordre des paramètres pour se souvenir facilement de l'ordre des directions (identique à l'ordre de déclaration des attributs ?)
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe RoomTest et exécuter testsetExits_6().
Si vous voyez apparaître tout en bas la mention 'testsetExits_6 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.RoomTest.testsetExits_6' pour voir le message d'erreur sous la barre rouge.
Puis vérifier interactivement dans la fenêtre principale de BlueJ le bon fonctionnement de cette seconde version de la classe Room. (créer 3 ou 4 pièces pour cela et utiliser 2 ou une fois la valeur null pour signifier qu'il n'y a pas de sortie dans cette direction), puis inspecter l'objet Room sur lequel vous avez appelé la méthode setExits. Vous pouvez cliquer sur les flèches courbes pour accéder à l'objet dont la référence est contenue dans cet attribut.
II. La classe Command
- Compléter la classe
Command
qui représentera une commande tapée au clavier qui provoque une action dans le jeu d'aventure.
Y créer deux attributs chaînes de caractères aCommandWord et aSecondWord qui, POUR INFORMATION, contiendront respectivement le premier et le second mot, c'est-à-dire "go"et "north" par exemple dans le cas de la commande "go north", mais "take" et "key" par exemple dans le cas de la commande "take key".
Toujours POUR INFORMATION, le premier mot sera null si la commande n'a pas été reconnue (par une autre classe restant à écrire). Le second mot restera null si un seul mot a été tapé (** Ces conventions serviront aux exercices 4 et 5 ci-dessous).
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe CommandTest et exécuter testAttributs_2().
Si vous voyez apparaître tout en bas la mention 'testAttributs_2 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.CommandTest.testAttributs_2' pour voir le message d'erreur sous la barre rouge. - Écrivez le
constructeur naturel.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe CommandTest et exécuter testConstructeur_3().
Si vous voyez apparaître tout en bas la mention 'testConstructeur_3 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.CommandTest.testConstructeur_3' pour voir le message d'erreur sous la barre rouge. - Écrivez les deux accesseurs.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe CommandTest et exécuter testAccesseurs_4().
Si vous voyez apparaître tout en bas la mention 'testAccesseurs_4 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.CommandTest.testAccesseurs_4' pour voir le message d'erreur sous la barre rouge.
Puis vérifier interactivement le bon fonctionnement de cette première version de la classe Command. - Ajouter une fonction booléenne
hasSecondWord
(ça veut dire quoi en français ?) qui permettra de vérifier qu'un second mot a bien été tapé (voir ** ci-dessus); par exemple, la commande quit n'en
nécessite pas ...
(indice au V.1)
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe CommandTest et exécuter testHSW_5().
Si vous voyez apparaître tout en bas la mention 'testHSW_5 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.CommandTest.testHSW_5' pour voir le message d'erreur sous la barre rouge. - Ajouter également une fonction booléenne
isUnknown
(ça veut dire quoi en français ?) qui retourne vrai si le premier mot est null (la partie du programme qui lira la commande tapée mettra null si on n'a pas tapé une commande valide), et bien entendu faux sinon.
(indice au V.1)
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe CommandTest et exécuter testIU_6().
Si vous voyez apparaître tout en bas la mention 'testIU_6 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.CommandTest.testIU_6' pour voir le message d'erreur sous la barre rouge.
Puis vérifier interactivement le bon fonctionnement de cette seconde version de la classe Command. - Si ce n'est pas déjà le cas, récrire ces 2 dernières fonctions sans le moindre if , en commençant par isUnknown. (remarquez que vous retournez la même expression booléenne dans les 2 cas du if
...)
+ Pour vérifier que deux valeurs sont différentes, on écrit val1 != val2 (non égal).
III. La classe Game
- A garder en tête pour la suite :
On n'appelle jamais la méthode m en écrivant seulement m(); mais toujours en écrivant objet.m();
Si objet doit être l'objet courant, on écrit this.m();
Mais si par erreur on écrit Classe.m(); on obtient un message d'erreur incompréhensible à ce stade : non-static method m() cannot be referenced from a static context - Compléter la classe
Game
qui constituera le "moteur" du jeu d'aventure.
Pour l'instant, elle ne doit posséder qu'un attribut aCurrentRoom (ça veut dire quoi en français ?) qui est bien sûr une Room (celle où se situe le joueur). Elle sera mise à jour à chaque déplacement.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testAttributs_2().
Si vous voyez apparaître tout en bas la mention 'testAttributs_2 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.GameTest.testAttributs_2' pour voir le message d'erreur sous la barre rouge. - Écrire la procédure privée
createRooms
(sans aucun paramètre) qui ne comportera pour l'instant aucune instruction.
Compiler sans tester. Puis :
1déclarer/créer les 5 lieux ci-dessous, puis
2positionner les sorties pour créer le "réseau" de lieux ci-dessous, puis
3initialiser le lieu courant (où débute-t-on le jeu ?) comme indiqué ci-dessous.
Comme il y a clairement 3 parties dans cette méthode, il est fortement conseillé de les distinguer grâce à des commentaires séparateurs bien visibles.
Par ailleurs, cette méthode restera privée, car elle ne sera appelée que par le constructeur de cette classe. (indice au V.2)
Comme vous n'avez pas forcément de scénario, ni de plan des lieux, en voici un pour pouvoir avancer dans le développement (vous l'adapterez plus tard aux lieux que vous aurez choisis).
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testcreateRooms_3().Détails des 5 lieux (avec leur description exacte) :
- vOutside -> "outside the main entrance of the university"
- vTheatre -> "in a lecture theatre"
- vPub -> "in the campus pub"
- vLab -> "in a computing lab"
- vOffice -> "in the computing admin office"
On décide de faire commencer le jeu à l'extérieur de l'entrée principale (c'est-à-dire outside the main entrance of the university...).
Si vous voyez apparaître tout en bas la mention 'testcreateRooms_3 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.GameTest.testcreateRooms_3' pour voir le message d'erreur sous la barre rouge. Attention ! Le réseau de pièces n'est pas testé ici ; il ne le sera qu'avec le test du constructeur ci-dessous. - Créer un
constructeur par défaut
qui se contentera d'appeler la méthode createRooms déjà écrite (mais qui sera complétée au point suivant) ; le but est de ne pas avoir de constructeurs trop longs.
+ Le constructeur par défaut est le constructeur sans paramètre (qui remplace le constructeur par défaut fourni par Java quand on n'écrit aucun constructeur dans une classe).
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testConstructeur_4().
Si vous voyez apparaître tout en bas la mention 'testConstructeur_4 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.GameTest.testConstructeur_4' pour voir le message d'erreur sous la barre rouge. - Écrire la procédure privée
goRoom
qui sera exécutée lorsqu'on tapera la commande "go" et qui permettra de changer de
pièce. Elle acceptera juste un paramètre de type Command dans lequel on pourra récupérer la direction souhaitée.
Suivre dans l'ordre les 4 étapes a,b,c,d et chacune des sous-étapes, en les séparant par des commentaires. La complexité de cette méthode justifie d'indiquer également en commentaires les sous-parties 1 2 3... quand elles existent. (indice au V.3)
a) (inutile de vérifier que le CommandWord vaut "go") S'il n'y a pas de second mot dans la commande, afficher le message "Go where ?", et goRoom est terminée !
+ Pour exprimer la négation d'une valeur booléenne b, on peut utiliser (b==false), mais, plus élégant, on peut utiliser l'opérateur booléen de négation en écrivant (!b).
+ Pour dire que la procédure se termine prématurément, il suffit d'utiliser l'instruction return; (qui ne retourne aucune valeur puisque ce n'est pas une fonction).
b) Déterminer le lieu suivant (vNextRoom) en fonction de la direction souhaitée dans la commande (puisqu'on sait maintenant qu'il y a un second mot).
Aide :
1 déclarer une variable vNextRoom initialisée à null (en attendant d'y mettre la Room dans laquelle on veut aller)
2 puis, récupérer le second mot dans une variable vDirection
3 puis, initialiser vNextRoom en fonction de vDirection (4 cas possibles, sinon "Unknown direction !" et goRoom est terminée !)
Attention ! Bien tenir compte du premier + ci-dessous.
4 enfin, si vNextRoom est toujours null après ça, c'est qu'il n'y a pas de sortie dans la direction souhaitée (cas que nous traiterons au c) ci-dessous)
+ Pour vérifier l'égalité de deux valeurs, on écrit généralement x==y.
Mais ici, les valeurs sont des String, donc des objets. x==y signifie 'x et y sont-ils le même objet ?'
Mais que se passera-t-il si x et y sont 2 objets différents contenant tous deux le mot "ok"par exemple ?
x==y vaudra faux alors que x.equals(y) ou y.equals(x)vaudra bien vrai.
La fonction booléenne equals est disponible pour tous les objets, donc en particulier pour les String.
À partir de maintenant, pensez à toujours utiliser equals quand vous comparez deux String, en particulier au TP 3.2 !
+ Pour vérifier que deux objets sont différents, il faudra prendre la négation de ref1.equals(ref2) .
c) S'il n'y a pas de sortie dans la direction souhaitée, afficher le message "There is no door !" et goRoom est terminée !
d) Sinon :
1 changer de lieu,
2 afficher le lieu courant,
3 afficher les sorties disponibles comme dans l'exemple suivant :
Exits: east south west
Remarquez que l'on affiche uniquement les directions disponibles, pas les lieux !
+ Dans l'instruction System.out.println, on peut supprimer ln. Dans ce cas, le prochain affichage ne se fera plus à la ligne suivante, mais à la suite sur la même ligne.
Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testgoRoom_5().
Si vous voyez apparaître tout en bas la mention 'testgoRoom_5 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.GameTest.testgoRoom_5' pour voir le message d'erreur sous la barre rouge. - Vérifier interactivement le bon fonctionnement de cette première version de la classe Game. Vous pouvez rendre publiques les méthodes que vous voulez tester, puis les remettre privées puisqu'elles
n'auront pas vocation à être appelées en dehors de cette classe.
Si une méthode vous demande un paramètre de type Command,
vous pouvez simplement cliquer sur un objet Command
que vous avez créé auparavant.
- Quelle différence y a-t-il entre l'enchaînement de
if du 4b et celui du 4d ?
(question sans objet si vous avez utilisé un
switch)
IV. Fin du TP
- Revérifiez bien maintenant que Tout tester sur les classes
Room et
Command
ne donne bien que du vert !
Pour la classe Game,
les tests 6 7 8 ne pourront passer qu'après le début du TP 3.2 (il vous manque 4 méthodes).
- Il faut maintenant tester tout ça interactivement pour vérifier que tout fonctionne bien dans tous les cas.
Cela peut nécessiter des créations d'objets et des appels de méthodes pour créer un réseau de pièces
et pouvoir vraiment tester
goRoom.
Vous pouvez aussi rendre temporairement publiques certaines méthodes.
Attention : Le TP suivant ne pourra être commencé qu'après que celui-ci soit terminé, testé, et fonctionnel. Il est donc conseillé de terminer rapidement ce TP en travail personnel pour ne pas prendre trop de retard.
D'autre part, lors du prochain TP 3.2, il sera nécessaire de disposer de la dernière version
de votre travail sur ce TP 3.1.
Pensez à le sauvegarder dans un cloud si vous ne l'avez pas effectué sur votre compte ESIEE.
V. Indices pour ce TP
1) Indices pour écrire les méthodes hasSecondWord et isUnknown
(ces fonctions ont-elles besoin d'un paramètre ?) :
- la fonction doit-elle répondre vrai quand l'attribut correspondant vaut null ou bien ne vaut pas null ?
- si vous ne voyez pas
comment écrire chacune de ces 2 fonctions en une seule instruction, vous
pouvez demander à l'intervenant, ou bien attendre le cours 3.
2) Indices pour écrire la méthode createRooms :
Bien comprendre qu'elle comporte 3 parties distinctes et successives :
- 1ère partie : déclaration des 5 variables Room dont les noms sont donnés dans l'énoncé, et initialisation de ces variables en créant les 5 objets Room avec les
descriptions données dans l'énoncé.
- 2ème partie :
positionner correctement les sorties de chacune des 5 pièces en
fonction du plan donné dans l'énoncé
(nomDeVariable -> "description
de la pièce"),
en utilisant la méthode écrite précédemment, et en respectant bien l'ordre des 4 sorties de chaque
pièce imposé par cette méthode.
- 3ème partie : dire que le lieu courant au début du jeu est celui indiqué dans le message affiché au III.7
Bien comprendre la nature de ce qui lui est passé en paramètre, et qu'elle comporte 6 parties distinctes et successives :
- 1ère partie : traiter le cas où il n'y a pas de second mot : une fonction écrite dans un exercice précédent nous permet de le savoir, il faut afficher un message et terminer la procédure.
- 2ème partie : mettre dans la variable vNextRoom soit la prochaine pièce dans laquelle on veut aller (en fonction de la direction présente dans le second mot), soit null (s'il n'y a pas de pièce dans cette direction ou si cette direction n'est pas parmi les 4 reconnues).
Choisissez bien l'enchaînement des if ou des if/else, et rappelez-vous que les chaînes de caractères sont des objets !
- 3ème partie : traiter le cas où la partie précédente n'a pas pu déterminer de prochaine pièce : afficher un message et terminer la procédure.
- 4ème partie : le lieu courant doit changer puisqu'on sait maintenant dans quelle pièce on veut aller.
- 5ème partie : il suffit d'afficher la description de la nouvelle pièce courante.
- 6ème partie : pour afficher les sorties de la pièce courante, il faut passer en revue successivement les 4 directions possibles et n'afficher une direction que lorsque la sortie correspondante dans la Room ne vaut pas null.
Choisissez bien l'enchaînement des if ou des if/else, et utilisez les espaces, print et println aux bons moments.
Si vous n'arrivez toujours pas à terminer ces exercices, posez votre question à un intervenant ou bien par mail, mais surtout n'attendez pas le prochain TP.