.. raw:: html
.. role:: greenbox
.. role:: whitebox
.. _02afirstgame:
Une première version
====================
Une première version minimale d'un jeu d'aventure en mode texte a été créée. Elle est bien sûr infiniment plus simple que Zork. Il y a moins de lieux, pour le moment pas encore d'objets ni de personnages autres que le joueur et très peu d'interactions. Cette première version servira de base à ce qui va suivre, et sera améliorée au fur et à mesure.
GitHub stuff
------------
Le starter code a été récupéré à l'étape précédente. Ouvrir la ``[REPOSITORY_HOMEPAGE]`` et démarrer le codespace associé :
:greenbox:`Code` :greenbox:`Codespaces`
.. tip::
La compréhension et la manipulation de Git sont grandement facilitées par l'extension `GitLens `_ de VS Code.
Exécution
---------
Le jeu se lance depuis le terminal::
$ python game.py
Entrez votre nom: Indiana
Bienvenue Indiana dans ce jeu d'aventure !
Entrez 'help' si vous avez besoin d'aide.
Vous êtes dans un marécage sombre et ténébreux. L'eau bouillonne, les abords sont vaseux.
Sorties: N, O
>
Déplacez vous, explorez les différentes possibilités, faites un plan rapide de la map du jeu. Repérez tous les lieux, les directions par lesquelles on y rentre ou on en sort. Explorez tous les passages possibles pour vérifier la cohérence de l'implémentation.
Pour quitter le jeu, il suffit de taper ``quit``.
.. quiz:: quizz-01
:title: La map du jeu
.. warning::
Les chaines de caractères doivent être renseignées exactement comme elles sont affichées dans le jeu. Les majuscules et les minuscules sont prises en compte. Les espaces sont importants. Les accents aussi.
A titre d'exemple, la réponse à la question *Quel est le lieu où se trouve le joueur au démarrage du jeu ?*, la réponse doit être *un marécage sombre et ténébreux"*. Si la description est constituée de deux phrases, on ne retient que la première.
#. :quiz:`{"type":"FB","answer":"6", "size":5}` Combien y a t-il de lieux ?
#. :quiz:`{"type":"FB","answer":"7", "size":5}` Combien y a t-il de passages entre les différents lieux ?
#. :quiz:`{"type":"FB","answer":"une grotte profonde et sombre", "size":45}` Quel est le lieu au nord ouest ?
#. :quiz:`{"type":"FB","answer":"un petit chalet pittoresque avec un toit de chaume", "size":45}` Quel est le lieu au nord est ?
#. :quiz:`{"type":"FB","answer":"un marécage sombre et ténébreux", "size":45}` Quel est le lieu au sud est ?
#. :quiz:`{"type":"FB","answer":"un énorme château fort avec des douves et un pont levis", "size":45}` Quel est le lieu au sud ouest ?
#. :quiz:`{"type":"TF","answer":"F"}` On peut aller directement de la grotte au château fort
#. :quiz:`{"type":"TF","answer":"T"}` On peut aller directement de la grotte au chalet
#. :quiz:`{"type":"TF","answer":"T"}` On peut aller directement du marécage au château fort
#. :quiz:`{"type":"TF","answer":"F"}` On peut aller directement du marécage au chalet
Analyse du code de :file:`game.py`
----------------------------------
:file:`game.py` est le fichier principal du jeu. Observer attentivement le code de ce fichier.
.. quiz:: quizz-02
:title: Structure de game.py
.. warning::
Quelques indications pour les réponses à apporter :
- Les lignes consécutives doivent être séparées par un tiret. Exemple : ``10-15`` ;
- Les lignes non consécutives doivent être séparées par des virgules suivies d'un seul espace. Exemple : ``10, 11, 13, 17`` ;
- Les objets doivent être décrits par la chaine de caractères utilisée par la commande :command:`type()`. Exemple : si la commande :command:`type()` retourne ``list``, la réponse est ``list``;
- Les fonctions et les méthodes doivent comporter les parenthèses. Exemple : ``__init__()``.
#. :quiz:`{"type":"FB","answer":"Game", "size":15}` Quelle est la classe principale définie dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"4", "size":5}` Combien a t-elle d'attributs ?
#. :quiz:`{"type":"FB","answer":"5", "size":5}` Combien a t-elle de méthodes ?
#. :quiz:`{"type":"FB","answer":"__init__()", "size":15}` Quel est le constructeur ?
#. :quiz:`{"type":"FB","answer":"play()", "size":15}` Quelle est la méthode principale ?
#. :quiz:`{"type":"FB","answer":"57", "size":5}` A quelle ligne demande t-on le nom du joueur ?
#. :quiz:`{"type":"FB","answer":"67", "size":5}` A quelle ligne demande t-on la commande du joueur ?
#. :quiz:`{"type":"FB","answer":"67", "size":5}` A quelle ligne traite t-on la commande du joueur ?
#. :quiz:`{"type":"FB","answer":"91", "size":5}` A quelle ligne affiche t-on la localisation du joueur au démarrage du jeu ?
#. :quiz:`{"type":"FB","answer":"99", "size":5}` Quelle est la première ligne de code exécutée par l'instruction :command:`$ python game.py` ?
#. :quiz:`{"type":"FB","answer":"33, 35, 37, 39, 41, 43", "size":15}` Quelles sont les lignes qui définissent les différents lieux ?
#. :quiz:`{"type":"FB","answer":"list", "size":15}` Quel est le type de l'objet qui les regroupe ?
#. :quiz:`{"type":"FB","answer":"self.rooms", "size":15}` Quelle est le nom de la variable qui permet d'y faire référence dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"48-53", "size":10}` Quelles sont les lignes qui définissent les différents passages entre les lieux ?
#. :quiz:`{"type":"FB","answer":"58", "size":5}` A quelle ligne définit on le lieu où est placé le joueur au démarrage du jeu ?
#. :quiz:`{"type":"FB","answer":"3", "size":5}` Combien y a t-il de commandes dans cette version du jeu ?
#. :quiz:`{"type":"FB","answer":"24, 26, 28", "size":10}` A quelles lignes sont elles définies ?
#. :quiz:`{"type":"FB","answer":"dict", "size":15}` Quelle est le type de l'objet qui les regroupe ?
#. :quiz:`{"type":"FB","answer":"self.commands", "size":15}` Quelle est le nom de la variable qui permet d'y faire référence dans ce fichier ?
#. Pour la ligne 5, ``room`` fait référence au fichier :quiz:`{"type":"FB","answer":"room.py", "size":10}` et ``Room`` fait référence à la classe :quiz:`{"type":"FB","answer":"Room", "size":15}`.
#. Pour la ligne 8, ``actions`` fait référence au fichier :quiz:`{"type":"FB","answer":"actions.py", "size":10}`.
#. :quiz:`{"type":"FB","answer":"setup()", "size":10}` est la méthode qui initialise le jeu.
#. :quiz:`{"type":"FB","answer":"str", "size":15}` est le type de l'objet qui permet de stocker les instructions que fournit le joueur au moteur du jeu.
#. Il est transformé en un objet de type :quiz:`{"type":"FB","answer":"list", "size":15}` par la méthode :quiz:`{"type":"FB","answer":"split()", "size":15}` à la ligne :quiz:`{"type":"FB","answer":"74", "size":5}` de la méthode :quiz:`{"type":"FB","answer":"process_command()", "size":15}`.
#. La vérification que la commande est reconnue par le moteur de jeu ou non est faite à la ligne :quiz:`{"type":"FB","answer":"79", "size":5}`.
#. Si la commande n'est pas valide, un message d'erreur est affiché à la ligne :quiz:`{"type":"FB","answer":"80", "size":5}`.
#. Si la commande est valide, un élément de la chaine de caractère entrée au clavier par le joueur est transformée en un objet de type :quiz:`{"type":"FB","answer":"Command", "size":15}` à la ligne :quiz:`{"type":"FB","answer":"83", "size":5}`.
#. Si la commande est valide, l'action correspondante est déclenchée à la ligne :quiz:`{"type":"FB","answer":"84", "size":5}`.
#. :quiz:`{"type":"TF","answer":"T"}` La ligne 96 crée une instance de la classe ``Game``.
#. :quiz:`{"type":"TF","answer":"F"}` La ligne 96 crée une référence vers une instance de la classe ``Game``.
#. La ligne 96 appelle la méthode :quiz:`{"type":"FB","answer":"play()", "size":15}` sur une instance de la classe :quiz:`{"type":"FB","answer":"Game", "size":15}`.
Analyse du code de :file:`room.py`
----------------------------------
La ligne 5 de :file:`game.py` importe la classe ``Room`` du fichier :file:`room.py`. Observer attentivement le code de ce fichier pour répondre aux questions suivantes.
.. quiz:: quizz-03
:title: Structure de room.py
#. :quiz:`{"type":"FB","answer":"Room", "size":15}` Quelle est la classe principale définie dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"3", "size":5}` Combien a t-elle d'attributs ?
#. :quiz:`{"type":"FB","answer":"4", "size":5}` Combien a t-elle de méthodes ?
#. :quiz:`{"type":"FB","answer":"__init__()", "size":15}` Quel est le constructeur ?
#. :quiz:`{"type":"FB","answer":"str", "size":15}` Quelle est le type de l'objet utilisé pour la description du lieu ? Observer un exemple d'appel au constructeur de cette classe dans le fichier ``game.py``.
#. :quiz:`{"type":"FB","answer":"self.description", "size":15}` Quelle est la variable qui permet d'y faire référence dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"dict", "size":15}` Quel est le type de l'objet qui regroupe les différentes sorties d'un lieu ?
#. :quiz:`{"type":"FB","answer":"self.exits", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"Room", "size":15}` Que retourne la méthode :meth:`get_exit()` si la direction passée en paramètre est valide ?
#. :quiz:`{"type":"FB","answer":"None", "size":15}` Que retourne la méthode :meth:`get_exit()` si la direction passée en paramètre n'est pas valide ?
#. :quiz:`{"type":"FB","answer":"str", "size":15}` Pour la méthode :meth:`get_exit_string()`, quel est le type de la valeur de retour ?
Les f-strings
.............
Pour la méthode :meth:`Room.get_long_description`, observer la construction de la valeur de retour.
La chaine de caractères est une f-string, pour *formatted string*. Elle permet de concaténer des chaînes de caractères avec des variables.
Pour concaténer une variable dans une f-string, il suffit de l'entourer de ``{}``. Par exemple, pour concaténer la variable ``self.description`` dans une f-string, il suffit d'écrire ``f"Vous êtes {self.description}"``.
Si ``self.description`` contient la chaine de caractère ``dans une forêt enchantée``, la f-string précédente sera transformée en ``Vous êtes dans une forêt enchantée``.
.. note::
Pour plus d'informations sur les f-strings, consulter la `documentation officielle `_.
Pour faciliter la compréhension globale du code, on peut utiliser un diagramme de classes.
Pour la classe ``Room``, le diagramme de classes est le suivant :
.. image:: images/room.png
:width: 300px
:align: center
Le diagramme est divisé en 3 parties :
- la première partie représente le nom de la classe ;
- la deuxième partie expose les attributs et leur type;
- et la troisième partie expose les méthodes, leurs arguments et le type de la valeur de retour.
.. note::
Le diagramme de classes est un outil très utile pour comprendre le code. Il permet de visualiser les attributs et les méthodes de chaque classe ainsi que les relations entre les différentes classes.
Analyse du code de :file:`player.py`
------------------------------------
La ligne 6 de :file:`game.py` importe la classe ``Player`` du fichier :file:`player.py`. Observer attentivement le code de ce fichier.
.. quiz:: quizz-04
:title: Structure de player.py
#. :quiz:`{"type":"FB","answer":"Player", "size":15}` Quelle est la classe principale définie dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"2", "size":5}` Combien a t-elle d'attributs ?
#. :quiz:`{"type":"FB","answer":"2", "size":5}` Combien a t-elle de méthodes ?
#. :quiz:`{"type":"FB","answer":"__init__()", "size":15}` Quel est le constructeur ?
#. :quiz:`{"type":"FB","answer":"str", "size":15}` Quel est le type de l'objet qui stocke le nom du joueur ?
#. :quiz:`{"type":"FB","answer":"self.name", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"Room", "size":15}` Quel est le type de l'objet qui stocke le lieu dans lequel se trouve le joueur ?
#. :quiz:`{"type":"FB","answer":"self.current_room", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"True", "size":10}` Que retourne la méthode :meth:`move()` si le déplacement est valide ?
#. :quiz:`{"type":"FB","answer":"False", "size":10}` Que retourne la méthode :meth:`move()` si le déplacement n'est pas valide ?
#. :quiz:`{"type":"FB","answer":"20", "size":5}` Quelle est la ligne qui permet de déplacer le joueur dans le lieu de destination ?
Le diagramme de classes de la classe ``Player`` est le suivant :
.. image:: images/player.png
:width: 300px
:align: center
Il y a une relation d'association entre la classe ``Player`` et la classe ``Room``. Cela signifie que la classe ``Player`` utilise la classe ``Room``. Cette relation est représentée par une ligne. Ici la cardinalité est représentée par un ``1``. Cela signifie que la classe ``Player`` utilise une seule instance de la classe ``Room`` (le joueur ne peut être présent que dans un seul lieu à chaque tour).
Analyse du code de :file:`actions.py`
-------------------------------------
La ligne 8 de :file:`game.py` importe la classe ``Actions`` du fichier :file:`command.py`. Observer attentivement le code de ce fichier.
.. quiz:: quizz-05
:title: Structure de actions.py
#. :quiz:`{"type":"FB","answer":"Action", "size":15}` Quelle est la classe principale définie dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"0", "size":5}` Combien a t-elle d'attributs ?
#. :quiz:`{"type":"FB","answer":"0", "size":5}` Combien a t-elle de méthodes d'instance ?
#. :quiz:`{"type":"FB","answer":"0", "size":5}` Combien a t-elle de méthodes de classe ?
#. :quiz:`{"type":"FB","answer":"3", "size":5}` Combien a t-elle de fonctions ?
#. :quiz:`{"type":"TF","answer":"F"}` Y a t-il un constructeur ?
#. :quiz:`{"type":"TF","answer":"T"}` Les fonctions ont elles toutes la même signature ?
#. :quiz:`{"type":"FB","answer":"3", "size":5}` Combien ont elles de paramètres ?
#. :quiz:`{"type":"FB","answer":"Game", "size":15}` De quel type est le premier paramètre ?
#. :quiz:`{"type":"FB","answer":"list", "size":15}` De quel type est le deuxième paramètre ?
#. :quiz:`{"type":"FB","answer":"int", "size":15}` De quel type est le troisième paramètre ?
#. :quiz:`{"type":"FB","answer":"bool", "size":15}` De quel type est la valeur de retour ?
Les fonctions de ce fichier sont des fonctions dites de callback. Elles sont associées à la commande correspondante avec l'attribut :meth:`Command.action` de la classe :class:`Command`.
#. :quiz:`{"type":"FB","answer":"", "size":15}` Quelle est la fonction de callback associée à la commande ``help`` ?
#. :quiz:`{"type":"FB","answer":"", "size":15}` Quelle est la fonction de callback associée à la commande ``quit`` ?
#. :quiz:`{"type":"FB","answer":"", "size":15}` Quelle est la fonction de callback associée à la commande ``go`` ?
Les fonctions de callback ont en argument un objet de type :class:`Game` et ont donc accès à tous les attributs et toutes les méthodes de cette classe. Pour ces fonctions...
#. :quiz:`{"type":"FB","answer":"game.player.current_room", "size":15}` Quelle est la variable qui permet d'accéder à la salle courante ?
#. :quiz:`{"type":"FB","answer":"game.player.current_room.name", "size":15}` Quelle est la variable qui permet d'accéder au nom de la salle courante ?
#. :quiz:`{"type":"FB","answer":"game.player.current_room.description", "size":15}` Quelle est la variable qui permet d'accéder à la description de la salle courante ?
#. :quiz:`{"type":"FB","answer":"game.player.current_room.exits", "size":15}` Quelle est la variable qui permet d'accéder aux sorties de la salle courante ?
Le diagramme de classes de la classe ``Actions`` est le suivant :
.. image:: images/actions.png
:width: 300px
:align: center
Cette classe est particulière puisqu'elle ne possède ni attributs ni méthodes, uniquement des fonctions. C'est une classe de fonctions. Elle est utilisée pour regrouper les fonctions de callback associées aux commandes. Cela permet de les organiser dans un même fichier.
Analyse du code de :file:`command.py`
-------------------------------------
La ligne 7 de :file:`game.py` importe la classe ``Command`` du fichier :file:`command.py`. Observer attentivement le code de ce fichier.
.. quiz:: quizz-04
:title: Structure de player.py
#. :quiz:`{"type":"FB","answer":"Command", "size":15}` Quelle est la classe principale définie dans ce fichier ?
#. :quiz:`{"type":"FB","answer":"4", "size":5}` Combien a t-elle d'attributs ?
#. :quiz:`{"type":"FB","answer":"2", "size":5}` Combien a t-elle de méthodes ?
#. :quiz:`{"type":"FB","answer":"__init__()", "size":15}` Quel est le constructeur ?
#. :quiz:`{"type":"FB","answer":"str", "size":15}` Quel est le type de l'objet qui stocke le nom de la commande ?
#. :quiz:`{"type":"FB","answer":"self.command_word", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"str", "size":15}` Quel est le type de l'objet qui stocke l'information de ce que fait la commande ?
#. :quiz:`{"type":"FB","answer":"self.help_string", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"function", "size":15}` Quel est le type de l'objet qui stocke l'action déclenchée par la commande ?
#. :quiz:`{"type":"FB","answer":"self.action", "size":15}` Quelle est la variable qui permet d'y faire référence ?
#. :quiz:`{"type":"FB","answer":"int", "size":15}` Quel est le type de l'objet qui stocke le nombre de paramètres que nécessite par la commande ?
#. :quiz:`{"type":"FB","answer":"self.number_of_parameters", "size":15}` Quelle est la variable qui permet d'y faire référence ?
La docstring
............
Cette classe possède une docstring. Elle est très utile pour documenter le code. Elle est accessible via la fonction ``help`` de Python.
Par convention une docstring de classe est structurée en plusieurs parties :
- une première ligne qui décrit la classe ;
- une ligne vide ;
- une description plus détaillée de la classe (optionnel) ;
- une ligne vide (optionnel) ;
- une liste des attributs de la classe ;
- une ligne vide ;
- une liste des méthodes de la classe ;
- une ligne vide ;
- une liste des exceptions levées par la classe (optionnel) ;
- une ligne vide ;
- des exemples d'utilisation sous forme de doctest.
.. note::
Pour plus d'informations sur les docstrings, consulter la `documentation officielle `__.
Le diagramme de classes de la classe ``Command`` est le suivant :
.. image:: images/command.png
:width: 300px
:align: center
Une seule fonction de callback (de la classe ``Actions``) est associée à une commande. Cette fonction est appelée lorsque l'utilisateur tape la commande correspondante dans la console.
Exercices
---------
.. admonition:: Docstrings
Compléter la docstring des classes ``Room`` et ``Player`` en utilisant la convention décrite ci-dessus.
.. admonition:: Diagramme de classe
Dessiner le diagramme de classe de la classe ``Game``.
.. admonition:: Passage interdit
Apporter les modifications nécessaires pour que le passage entre la forêt et la tour en pierre ne soit pas possible. Vérifiez que les déplacements possibles sont conformes à la nouvelle carte.
.. admonition:: Sens unique
Apporter les modifications nécessaires pour que l'on puisse aller du marécage vers la tour en pierre, mais pas l'inverse. Vérifiez que les déplacements possibles sont conformes à la nouvelle carte.
.. admonition:: Commande vide
Actuellement la commande vide est traitée comme une commande inconnue. Lorsque le joueur appuie sur ``Entrée`` à l'invite de commande, le jeu affiche un message d'erreur::
>
Commande '' non reconnue. Entrez 'help' pour voir la liste des commandes disponibles.
>
Il serait plus judicieux de ne rien afficher::
>
>
>
Modifier le code en conséquence.
.. admonition:: Amélioration de la description du lieu
Actuellement, la description de chaque lieu telle qu'initialisée dans la classe :class:`Game` (lignes 33, 35, 37, 39, 41 et 43) contient le mot "dans". Il serait plus judicieux de factoriser ce mot en l'incluant plutôt au moment de la fabrication de la chaine de description.
Ainsi, ligne 35 dans la classe :class:`Game`, la description de la tour serait "*une immense tour en pierre qui s'élève au dessus des nuages.*"" et non "*dans une immense tour en pierre qui s'élève au dessus des nuages.*". Modifier la construction des 6 lieux en conséquence.
Cependant, on souhaite conserver le même affichage dans le terminal. Il faut donc modifier la fabrication de la chaine de description. Dans quelle classe est-elle effectuée ? A quelle ligne ? Modifier le code de cette classe en conséquence et vérifier le bon fonctionnement du jeu.
..
ligne 31 de la classe Room
return f"\nVous êtes dans {self.description}\n\n{self.get_exit_string()}\n"
GitHub stuff
------------
Enregistrer les modifications apportées aux fichiers dans le repo local au codespace ::
$ git add .
$ git commit -m "Une première version"
Pour référencer plus facilement cette version dans le futur, créer un tag ::
$ git tag v1
Synchroniser votre codespace avec votre dépôt GitHub ::
$ git push
Vérifier que les modifications sont bien présentes dans le dépôt GitHub.