TP 3.2 2425v2

Avant de commencer ce TP,  le TP 3.1 doit être entièrement terminé, avec tous nos tests et vos tests réussis.

Si ce n'est pas le cas, demandez de l'aide à l'intervenant dès le début du TP.
Attention ! Il y a beaucoup d'apports de syntaxe ci-dessous pour les boucles et les tableaux.

I. Suite de la classe Game

  1. Ajouter une procédure privée printWelcome sans aucun paramètre (qui sera appelée au début du jeu, dans la partie III.) pour afficher le message suivant (notez que les 2 dernières lignes changeraient si le lieu initial n'était pas 'main entrance') :
    Welcome to the World of Zuul!
    World of Zuul is a new, incredibly boring adventure game.
    Type 'help' if you need help.
     
    You are outside the main entrance of the university
    Exits: east south west
    Remarquez qu'on affiche juste les directions possibles, pas les lieux.

  2. Ajouter 2 méthodes privées printHelp et quit (qui seront exécutées lorsqu'on tapera respectivement les commandes "help" et "quit") :
    a) La première est une procédure (sans aucun paramètre) qui affiche :
    You are lost. You are alone.
    You wander around at the university.

    Your command words are:
      go quit help
    Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testprint_6().
    Si vous voyez apparaître tout en bas la mention 'testprint_6 succeeded', vous pouvez passer à la suite. Sinon, vous devez voir apparaître une fenêtre 'BlueJ: Test Results'. Cliquer sur la ligne 'v1.GameTest.testprint_6' pour voir le message d'erreur sous la barre rouge.
    b) La seconde est une fonction booléenne qui prend une Command en paramètre. S'il y a un second mot, elle retourne faux après avoir affiché "Quit what ?". Sinon, elle se contente de retourner vrai. (indice au VI.1) Elle servira à quitter le jeu.
    Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testquit_7().
    Si vous voyez apparaître tout en bas la mention 'testquit_7 succeeded', vous pouvez passer à la suite. Sinon, cliquez sur la ligne 'v1.GameTest.testquit_7' pour voir le message d'erreur sous la barre rouge.

  3. Ajouter maintenant une fonction booléenne privée processCommand qui prend une Command en paramètre. Son rôle est d'appeler la bonne méthode en fonction de la commande passée en paramètre.
    a) Si la commande est inconnue (comment le savoir tout de suite ?), elle retournera faux après avoir affiché le message "I don't know what you mean...".
    b) Sinon, après avoir appelé la méthode correspondant à la commande, elle retournera :
    - soit le résultat (booléen) de quit() (dans le cas de la commande "quit"),
    - soit systématiquement faux  (pour les autres commandes).
    c) Si aucun mot de commande n'a été reconnu, afficher "Erreur du programmeur : commande non reconnue !", car cela aurait dû être détecté au a) par isUnknown(), puis retourner vrai pour quitter le jeu.
    (indice au VI.2)
    Lorsque vous n'avez plus d'erreur de compilation, clic-droit sur la classe GameTest et exécuter testprocessCommand_8().
    Si vous voyez apparaître tout en bas la mention 'testprocessCommand_8 succeeded', vous pouvez passer à la suite. Sinon, cliquez sur la ligne 'v1.GameTest.testprocessCommand_8' pour voir le message d'erreur sous la barre rouge.

  4. Vérifier interactivement le bon fonctionnement de cette deuxième 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.

II. Créer les deux dernières classes

  1. Créer une classe CommandWords qui contiendra les mots de commande acceptés par le jeu.
    Copiez/collez le code de la classe jointe en bas de ce sujet.

    Voici le contenu de cette classe avec des commentaires à chaque ligne pour expliquer son fonctionnement.
    Vous devez la lire attentivement et en détail. Si après avoir lu en plus les paragraphes expliquant les tableaux et la boucle for vous ne comprenez pas 100% de ce qui est ci-dessous, demandez à l'intervenant.

    private static final String[] sValidCommands = { "go", "quit", "help" };
      // static :
    pour que ce tableau n'existe qu'en un seul exemplaire dans cette classe (même si on l'instancie plusieurs fois)
      // final :
    pour qu'on ne puisse pas modifier ce tableau
      // [] :
    pour indiquer qu'on ne veut pas une seule String mais plusieurs
      // { , , } :
    pour indiquer quelles sont les 3 String à stocker dans ce tableau
     
    public boolean isCommand( final String pString ) //
    pour vérifier si pString est une commande valide
    {
      for ( int i=0; i<sValidCommands.length; i++ ) {
        // sValidCommands.length :
    vaut le nombre de cases du tableau, ici 3
        // i++
    est strictement équivalent à i = i+1 (exécuté à la fin de chaque tour de boucle)
        //
    donc, POUR i prenant les valeurs 0, 1, 2, FAIRE :

        if ( sValidCommands[i].equals( pString ) ) // [i] pour indiquer qu'on veut accéder à la case n°i du tableau
          return true; //
    termine la méthode et retourne vrai puisqu'on a trouvé le mot parmi les commandes valides

      } // fin de la boucle POUR
      return false; //
    retourne faux puisqu'on n'a pas trouvé le mot parmi les commandes valides
    } // isCommand(.)

    Ce qu'il faut retenir sur les tableaux   

    1. Un tableau permet de regrouper/manipuler plusieurs données de même type. Par exemple, pour un tableau de String, on trouverait une String dans chaque case du tableau.
    2. Lorsqu'on ajoute [] après un type T, cela crée un nouveau type 'tableau d'éléments de type T ' qui fait partie des types objet (ou reference types). Par exemple, boolean[] désigne un type 'tableau de booléens', mais T peut très bien être une classe ou n'importe quel autre type.
    3. On déclare les tableaux comme toute autre variable (locale, attribut ou paramètre) en utilisant le type int[] si c'est un tableau d'entiers. Par exemple, pour une variable locale, on écrirait : int[] vTab;
      Attention ! Comme pour les objets, l'instruction précédente ne crée aucun tableau ; elle déclare juste une variable référence qui pourra contenir l'adresse d'un tableau d'entiers.
    4. On crée les tableaux presque comme on crée les objets : vTab = new int[12]; si on veut un tableau de 12 entiers. La variable vTab contient alors la référence vers l'objet tableau qui ici comporte 12 cases, chacune pouvant contenir un entier.
    5. On peut connaître la taille de tout tableau en utilisant l'attribut public length qui est automatiquement créé lors de la création du tableau (attention, ce n'est pas une fonction, rien à voir avec length() de la classe String).
    6. Chaque case du tableau est numérotée de 0 à la taille-1. Ces numéros s'appellent des indices.
    7. On accède à une case du tableau par son indice, et vTab[indice] peut servir à la fois pour lire la valeur dans la case et pour y écrire une nouvelle valeur. Si indice est hors des limites 0 à taille-1, une exception est générée et, en général, le programme s'arrête.
    8. Il existe une manière courte de déclarer+créer+initialiser un tableau de type T quand on connaît toutes ses valeurs :
      T [] vTab = { valeur0, ..., valeurN-1 };
    9. On ne peut pas afficher les valeurs d'un tableau par un simple S.o.p(vTab); qui se contenterait d'afficher l'adresse (= la référence) de ce tableau.

    Ce qu'il faut retenir sur la boucle for   

    C'est une boucle souvent utilisée avec les tableaux. Même si elle est rigoureusement équivalente à une boucle while (comme on pourra le constater ci-dessous), il est très pratique d'avoir toutes les informations rassemblées en une ligne au lieu d'être potentiellement dispersées dans une méthode.
    On l'utilise à chaque fois que l'on souhaite exprimer une répétition un nombre connu de fois.

    L'ensemble des instructions :

    initialisation ;
    while ( condition_de_continuation ) {
      instructions_à_répéter ;
      incrémentation ;
    } // while

    peut/doit avantageusement être remplacé par :

    for ( initialisation ; condition_de_continuation ; incrémentation ) {
      instructions_à_répéter ;
    } // for

    Ce qu'on appelle initialisation comprend en général la déclaration, par exemple : int i=0;
    Ce qu'on appelle incrémentation est en général de la forme i = i+1, mais rien n'empêche d'écrire i = i-2 et d'adapter l'initialisation et la condition en conséquence !
    Attention !  La condition_de_continuation (comme son nom l'indique) n'est pas une condition d'arrêt !
    Il est important de noter que l'incrémentation a lieu en fin de boucle, après les instructions_à_répéter.
    A noter également : c'est le seul cas en java où un point-virgule (à l'intérieur des parenthèses) ne marque pas la fin de l'instruction for !

    Si ça ne suffit pas, vous pouvez aussi lire ce mini-cours sur la boucle for

  2. Créer une classe Parser qui lira la commande tapée au clavier, vérifiera qu'elle est valide, et qui construira l'objet Command correspondant.
    Comme il y a beaucoup de nouvelles notions importantes dans cette classe, copiez/collez le code de la classe jointe en bas de ce sujet, et nous reviendrons sur ces notions au TP suivant.
    En attendant lire la description ci-dessous :
    - déclaration d'un objet de la classe CommandWords qui gèrera la validité des commandes tapées (L23) et d'un objet Scanner qui ira lire les commandes tapées au clavier (L24)
    - constructeur par défaut qui crée les 2 objets déclarés en attribut (L29-34) (à quoi les attributs sont-ils initialisés ?)
    - une fonction qui retourne un objet Command en fonction de la commande tapée au clavier (L39-47) ; son principe :
       1) s'il y a un mot dans la ligne tapée, le mémoriser dans un 1er mot, puis s'il y a encore un mot, le mémoriser dans le 2nd mot. (L50-57)
       2) Demander à l'objet CommandWords si le premier mot correspond à une commande valide ou pas. (L62)
       3) Si oui, retourner un nouvel objet Command composé des deux mots (le second pouvant éventuellement être null) (L63) ; sinon, retourner un nouvel objet Command contenant 2 fois null (L66).
    Plus de détails sur le Scanner dans l'exercice 7.2.1 (partie V.).

III. Pour obtenir un jeu qui fonctionne

  1. Tous les éléments nécessaires sont désormais réunis, il ne manque plus dans la classe Game qu'un attribut aParser (objet qu'il ne faudra pas oublier de créer dans le constructeur) et une procédure play()sans paramètre qui devra lire répétitivement des commandes au clavier et les exécuter jusqu'à ce qu'on tape "quit".
    Pour cela, elle devra :
  2. Afficher le message de bienvenue.
  3. Initialiser une variable booléenne vFinished à faux pour signifier qu'on ne veut pas quitter le jeu.
  4. TANT QUE cette variable est fausse, faire 2 choses :
    a) Récupérer dans une variable de type Command la commande tapée au clavier.
    b) Mettre dans vFinished le résultat de l'exécution de la commande.
    + Pour exprimer un 'TANT QUE' en java, il suffit de le traduire en anglais : while. La syntaxe est la même que celle d'un if, c'est-à-dire que la condition doit être entre parenthèses et une seule instruction peut la suivre (sauf si on met des accolades, bien entendu).
  5. Afficher un message de fin : "Thank you for playing.  Good bye.".
  6. Vérifier maintenant que le jeu est parfaitement fonctionnel en lançant la méthode play() sur un objet Game ; ensuite, appeler cette méthode (redevenue privée) dans le constructeur.
    Ne pas hésiter à tester tous les cas.

    Ce qu'il faut retenir sur la boucle while   

    La syntaxe est identique à celle d'un if (faire bien attention de ne pas les confondre !) :
    avant...
    while ( condition ) {
      instructions
    } // while
    après...
    La boucle ci-dessus répète les instructions tant que la condition reste vraie.
    Quatre remarques :

    1. Si la condition est fausse avant d'arriver sur le while, les instructions ne seront jamais exécutées
    2. Dans tous les cas, si on se retrouve après le while, c'est que la condition est fausse.
    3. La boucle while s'utilise à chaque fois qu'on ne peut pas prévoir en rentrant dans la boucle combien de fois elle va tourner (puisqu'on ne sait pas quand la condition deviendra fausse).
    4. Dans la condition, ne pas utiliser == ou != avec des nombres, mais plutôt < <= > ou >=.

IV. Compétences à vérifier

Après les TP 3.1 & 3.2,  érifiez que vous avez acquis les compétences ci-dessous, ainsi que celles du TP1 et celles du TP2. Sinon, posez des questions à l'intervenant

V. Avancement du projet

VI. Indices pour ce TP

1) Indices pour écrire la méthode quit :
- La situation normale est l'absence de second mot. Dans ce cas, se contenter de retourner vrai (pour quitter le jeu).
- Dans le cas contraire, afficher un message et retourner faux (pour ne pas quitter le jeu).

2) Indices pour écrire la méthode processCommand :
- Comprendre la nature de ce qui lui est passé en paramètre.
- Traiter le cas où la commande est inconnue : une fonction écrite dans un exercice précédent nous permet de le savoir, il faut afficher un message et terminer la fonction (en indiquant qu'on ne veut pas quitter le jeu => retourner faux).
- Mémoriser le mot de commande à tester.
- Tester chaque mot de commande pour exécuter la bonne commande en appelant la méthode correspondante, et retourner faux (sauf la commande quit qui doit retourner le résultat booléen de la fonction quit).
- À la fin de ces tests, si vous n'avez détecté aucun mot de commande valide, c'est que vous avez fait une erreur : soit isUnknown() ne fonctionne pas bien, soit ces tests ne sont pas bons.
Choisissez bien l'enchaînement des if ou des if/else, et rappelez-vous que les chaînes de caractères sont des objets !

Si vous n'arrivez toujours pas à terminer ces exercices (notamment en travail personnel), demandez de l'aide sans attendre.