Practical activity
Duration2h30Présentation & objectifs
Cette activité pratique a pour but de vous préparer au projet PyRat.
Vous allez installer le logiciel et écrire un programme simple pour celui-ci.
Ce tutoriel peut être assez long.
Si vous n’avez pas assez de temps pour le terminer en classe, veuillez le finir à la maison.
Il contient tous les éléments nécessaires pour bien comprendre le projet.
Contenu de l’activité
1 — Installation de PyRat
1.1 — Télécharger et installer PyRat
Pour installer PyRat, assurez-vous d’abord que vous remplissez toutes les exigences techniques mentionnées dans la page principale de la session.
Ensuite, rendez-vous sur le dépôt logiciel PyRat et suivez les instructions d’installation.
1.2 — L’espace de travail PyRat
Lors de l’étape 4 de l’installation, il vous a été demandé de créer un espace de travail, en exécutant une commande qui crée un répertoire par défaut appelé pyrat_workspace
.
Ce répertoire contient trois sous-répertoires :
-
pyrat_workspace/players
– Ce répertoire contient quelques joueurs.
Un joueur est un code Python qui décrit comment un personnage se déplace dans le labyrinthe.
Nous allons analyser cela en détail dans cette activité pratique. -
pyrat_workspace/games
– Ce répertoire contient quelques scripts qui lancent des parties PyRat.
En quelques mots, un script crée essentiellement un jeu et des joueurs, puis ajoute les joueurs au jeu, et lance la partie.
Encore une fois, nous allons détailler cela dans cette activité. -
pyrat_workspace/doc
– Ce répertoire contient la documentation du logiciel PyRat, et des fichiers dans les deux sous-répertoires ci-dessus.
Elle a été générée automatiquement à l’étape 5 du processus d’installation.
N’hésitez pas à relancer la même commande plus tard si vous souhaitez intégrer vos nouveaux programmes à la documentation.
Ces répertoires sont l’endroit où vous devez mettre tous les codes que vous développerez dans le projet.
Veuillez les garder organisés, c’est-à-dire que vous devez mettre les joueurs que vous développerez dans pyrat_workspace/players
et vos scripts dans pyrat_workspace/games
.
1.3 — Vérification de l’installation
Maintenant, ouvrez Visual Studio Code, et ajoutez le dossier pyrat_workspace
à votre espace de travail Visual Studio Code.
Rendez-vous dans le sous-répertoire pyrat_workspace/games
, et ouvrez le script sample_game.ipynb
.
Puis, exécutez ce script en cliquant sur le bouton “Run All”.
Vous devriez voir un labyrinthe, avec deux joueurs – un rat et un python – qui se déplacent pour récupérer des morceaux de fromage.
Vous pouvez fermer la fenêtre du jeu à tout moment en utilisant la croix en haut de la fenêtre, ou en appuyant sur la touche “Esc”.
Cependant, fermer l’interface ne stoppera pas le jeu, car le jeu réel tourne en arrière-plan (voir ici pour plus de détails).
Si vous voulez arrêter complètement le jeu, vous devez arrêter le processus PyRat, soit en cliquant sur le bouton “Stop” dans Visual Studio Code, soit en appuyant sur “Ctrl + C” dans le terminal où le processus tourne.
Si l’installation n’a pas réussi, veuillez faire dans cet ordre :
- Vérifiez la section des problèmes du dépôt PyRat.
- Si vous ne trouvez pas de solution, demandez à votre enseignant, ou sur le Discord du cours en utilisant le bouton en bas à gauche de la page.
- Si le problème persiste (ou si plusieurs personnes ont le même problème), veuillez créer un issue sur le dépôt PyRat.
2 — Travailler en groupe
Pour travailler efficacement avec les autres étudiants de votre groupe, nous vous proposons deux solutions possibles.
La première option est beaucoup plus simple que la seconde.
Si vous ne connaissez pas encore Git, nous vous conseillons d’utiliser Live Share.
Vous pourrez revenir à Git plus tard dans le semestre si vous souhaitez travailler différemment.
2.1 — Live Share
Facile à utiliser | Nécessite que l’hôte soit connecté |
Vous voyez où les autres travaillent en temps réel | Le projet sera stocké uniquement sur l’ordinateur de l’hôte |
Pas de gestion de versions des fichiers |
Cette extension de Visual Studio Code vous permet de partager votre session de codage, et de travailler tous simultanément sur le même projet.
Vous devriez déjà l’avoir installée lors de la session environnement 2.
2.2 — Git
Garde l’historique des modifications | Nécessite du temps pour comprendre |
Les autres n’ont pas besoin d’être connectés pour travailler | Attention aux conflits si plusieurs personnes travaillent sur le même fichier |
Très utilisé en industrie/recherche | Vous ne voyez pas les autres utilisateurs en temps réel |
Un dépôt Git est un endroit en ligne où votre code est stocké.
Les personnes autorisées peuvent télécharger une copie locale de ce code, travailler dessus et pousser leurs modifications en ligne.
2.2.1 — Créer le dépôt Git
Pour configurer un dépôt Git, un des étudiants de votre groupe doit d’abord se rendre sur le GitLab de l’école.
Cet étudiant va préparer le dépôt.
Les autres le rejoindront ensuite.
Pendant que le premier étudiant prépare le dépôt, il doit s’assurer qu’il a un compte GitLab, et configurer son compte.
La première chose à faire est de créer un groupe avec les autres membres de l’équipe.
Pour cela, cliquez sur “Create a group”, puis sur “Create group”.
Donnez au groupe le nom “pyrat_group” (ou le nom que vous voulez, tant qu’il n’est pas utilisé), et définissez sa visibilité sur “Private”.
Enfin, cliquez sur “Create group”.
Maintenant que le groupe est créé, cliquez sur son nom dans le panneau de gauche.
Un nouveau bouton apparaîtra sur la page pour “Invite your colleagues” dans une boîte nommée “Collaborate with your team”.
Cliquez dessus.
Puis, recherchez tous les noms des membres de votre groupe PyRat, et sélectionnez-les.
Choisissez le rôle “Developer”, et cliquez sur “Invite”.
Maintenant que nous avons un groupe d’étudiants, créons le projet.
Restez sur la page du groupe, et cliquez sur “Create new project” puis “Create blank project”.
Donnez au projet le nom “pyrat_git_workspace” (ou le nom que vous voulez, tant qu’il n’est pas utilisé), et cliquez sur “Create project”.
Si c’est la première fois que vous créez un dépôt Git sur GitLab, il vous sera demandé de créer une clé SSH.
Pour cela, cliquez sur le bouton “Add SSH key” (ou accédez au menu depuis vos paramètres utilisateur).
Là, cliquez sur “Add new key”.
Dans le champ “Key”, collez votre clé SSH, que vous pouvez obtenir comme suit :
Voici un tutoriel avec plusieurs façons de créer une clé SSH pour Windows.
Suivez-le pour créer une clé SSH, et copiez-la dans le champ “Key”.
Supprimez également la date d’expiration.
Puis, cliquez sur “Add key”.
Voici un tutoriel pour créer une clé SSH pour Linux.
Suivez-le pour créer une clé SSH, et copiez-la dans le champ “Key”.
Supprimez également la date d’expiration.
Puis, cliquez sur “Add key”.
Voici un tutoriel pour créer une clé SSH pour MacOS.
Suivez-le pour créer une clé SSH, et copiez-la dans le champ “Key”.
Supprimez également la date d’expiration.
Puis, cliquez sur “Add key”.
Une fois fait, retournez à la page du projet.
2.2.2 — Cloner le dépôt Git
OK !
Maintenant, le groupe et le projet sont créés !
Créons une copie locale pour travailler dessus.
Cliquez sur le bouton “Code”, et copiez le champ “Clone with SSH”.
Puis, ouvrez votre terminal préféré, naviguez (avec cd
ou dir
) vers le répertoire où vous voulez créer votre copie locale, et tapez la commande suivante (remplacez <the_field_you_just_copied>
par ce que vous venez de copier) :
git clone <the_field_you_just_copied>
git clone <the_field_you_just_copied>
git clone <the_field_you_just_copied>
git clone <the_field_you_just_copied>
Cela devrait créer un répertoire appelé pyrat_git_workspace
(ou le nom que vous lui avez donné) sur votre ordinateur, qui ne contient qu’un fichier README.md
.
2.2.3 — Configuration dans Visual Studio Code
Importons ce répertoire dans Visual Studio Code.
Pour cela, allez dans votre espace de travail VSCode, et ajoutez le dossier pyrat_git_workspace
à l’espace de travail.
Une fois fait, votre dossier apparaîtra dans votre espace de travail comme suit :
Si vous cliquez sur “Source control” dans VSCode, vous verrez également une nouvelle entrée, car VSCode détectera qu’il s’agit d’un dépôt Git :
2.2.4 — Ajouter du contenu au dépôt
Initialisons le dépôt avec les éléments de base de PyRat.
Copiez-collez le contenu de votre répertoire pyrat_workspace
que vous avez créé lors de l’installation dans votre dossier pyrat_git_workspace
.
Vous verrez que les fichiers apparaîtront en vert, ce qui signifie qu’ils sont “Untracked”, c’est-à-dire pas encore ajoutés au dépôt.
Allez dans “Source Control”, et cliquez sur “Stage all changes” pour indiquer que vous souhaitez ajouter toutes les modifications locales au dépôt.
Vous pouvez aussi choisir de le faire fichier par fichier si vous préférez.
Les fichiers passeront de “Untracked” à “Added”.
Ensuite, écrivez un commentaire pour décrire ce que vous avez fait, et cliquez sur “Commit”.
Enfin, cliquez sur “Sync changes” pour envoyer vos modifications commitées au serveur.
2.2.5 — Les autres membres de l’équipe rejoignent le projet !
Maintenant, tout est prêt pour commencer à travailler !
Les autres étudiants de votre groupe doivent cloner le dépôt et l’ajouter à l’espace de travail VSCode.
2.2.6 — Utiliser Git pour développer collaborativement
Lorsque vous travaillez avec Git, vous devez faire comme suit :
- D’abord, quand vous commencez à travailler, récupérez la dernière version depuis le serveur Git (“Pull”).
- Ensuite, créez, supprimez ou mettez à jour des fichiers, écrivez du code, etc.
- Une fois satisfait, ajoutez les fichiers mis à jour à Git (“Stage changes”).
- Puis, décrivez vos modifications avec un message (“Commit”).
- Enfin, envoyez vos modifications au serveur Git (“Sync changes”).
Notez que si vous travaillez sur les mêmes fichiers que vos coéquipiers, vous pouvez créer des conflits.
En effet, si vous poussez une version A du code dans le dépôt (où il y avait une version antérieure B), cela fonctionnera comme prévu.
Cependant, si votre coéquipier travaillait sur une version C du code, dérivée de B, il peut y avoir des incompatibilités avec la version en ligne A.
Dans ce cas, il lui sera demandé de résoudre le conflit, c’est-à-dire de fusionner sa version C avec A.
Ce que nous vous avons présenté ci-dessus est seulement l’usage très basique de Git.
Vous trouverez de nombreuses ressources en ligne pour vous expliquer comment Git fonctionne, notamment ses fonctionnalités avancées, comme les branches.
Ci-dessus, nous vous avons demandé de créer votre dépôt sur la page GitLab de l’école.
Pour des raisons de stockage, ce dépôt est nettoyé chaque année.
Par conséquent, à la fin de l’année, vous n’aurez plus que votre copie locale.
Vous pouvez aussi travailler sur GitHub si vous préférez, mais ce n’est pas hébergé à IMT Atlantique.
3 — Comment fonctionne PyRat ?
3.1 — Éléments dans l’interface
Lors de la vérification de l’installation, il vous a été demandé d’exécuter un script appelé sample_game.ipynb
.
Ce script aurait dû ouvrir une interface graphique (GUI) similaire à la suivante :
Regardons les différents éléments dans cette image.
Sur la partie gauche de l’écran, vous pouvez voir la zone des scores, qui montre quels joueurs sont engagés dans la partie, comment ils sont regroupés en équipes, et leurs scores actuels.
Dans cet exemple, nous avons un match entre deux équipes, respectivement nommées “Team Ratz” et “Team Pythonz”.
Ici, chaque équipe contient un seul joueur, respectivement Random2
(avec la peau d’un rat), et Random3
(avec la peau d’un serpent).
Sur la partie droite de l’écran, vous trouverez le labyrinthe, dans lequel le jeu se déroule.
Le labyrinthe se trouve dans un rectangle de dimensions maze_width
x maze_height
qui peut avoir des trous.
Les cellules sont numérotées de 0
à maze_width * maze_height - 1
.
Vous pouvez aussi voir des murs, et des cellules séparées par de la boue.
Les premiers ne peuvent pas être traversés, et les seconds nécessitent le nombre de tours indiqué pour atteindre la cellule de l’autre côté.
Les personnages et les morceaux de fromage sont affichés dans le labyrinthe à leur position actuelle.
Notez cependant que l’interface graphique n’est pas synchronisée avec le jeu réel, pour pouvoir le visualiser agréablement.
Par conséquent, si vous choisissez d’imprimer votre position actuelle dans votre code, vous ne verrez pas la même cellule que dans l’interface graphique.
La couleur autour des joueurs indique leur équipe.
Vous pouvez aussi remarquer quelques petits drapeaux au milieu du labyrinthe, qui indiquent les positions de départ.
3.2 — Gagner une partie PyRat
Selon le nombre d’équipes en jeu, la règle pour gagner une partie PyRat change.
Plus précisément, on distingue les deux cas suivants :
-
Jeu à équipe unique – La partie est terminée lorsque tous les morceaux de fromage ont été attrapés.
-
Jeu multi-équipes – La partie est terminée lorsque les classements ne peuvent plus changer, c’est-à-dire qu’aucune équipe ne peut attraper assez de fromage pour changer le classement.
Quand une partie est terminée, vous aurez accès à beaucoup d’informations sur ce qui s’est passé durant la partie.
Vous apprendrez cela plus tard dans cette activité.
3.3 — Phases du jeu
PyRat est essentiellement une bibliothèque de fonctions Python qui permet de créer un jeu de labyrinthe, dans lequel les joueurs s’affrontent pour attraper des morceaux de fromage.
Une partie PyRat peut être décomposée en trois phases :
-
Tour – À chaque tour, les joueurs reçoivent la configuration actuelle du jeu, et doivent choisir où aller ensuite.
Plus précisément, ils doivent choisir une action parmi les quatre directions cardinales, ou rester sur place.
Les tours sont répétés jusqu’à la fin du jeu. -
Prétraitement – De plus, les joueurs ont la possibilité de faire des calculs au début du jeu.
Cette phase a lieu avant le premier tour du jeu, et dispose généralement d’un temps plus long que les tours.
Typiquement, c’est le moment de pré-calculer des trajectoires intéressantes, préparer une stratégie, etc. -
Post-traitement – Enfin, lorsque la partie est terminée, une phase finale a lieu.
Dans cette phase, les joueurs peuvent utiliser les informations recueillies pendant le jeu pour améliorer un modèle, ou faire un nettoyage de fichiers temporaires, par exemple.
Les phases de prétraitement et post-traitement sont optionnelles.
Pour créer un programme PyRat, seule la fonction décrivant le comportement au tour doit être définie.
Dans presque tous les cas, vous aurez seulement besoin de décrire les fonctions preprocessing
et turn
pour atteindre vos objectifs.
Selon la paramétrisation du jeu, les joueurs peuvent avoir un temps limité pour prendre leurs décisions, ou non.
Par défaut, une partie PyRat attribue 3s pour le prétraitement, et 0.1s par tour.
Le post-traitement n’a jamais de limite de temps.
3.4 — Les différents modes de jeu
Un jeu PyRat standard ressemble au diagramme suivant (cliquez ou téléchargez pour zoomer).
Dans ce diagramme, les lignes horizontales représentent des processus indépendants qui tournent simultanément sur l’ordinateur.
Les flèches représentent la communication entre ces processus.
La largeur des différentes boîtes représente le temps nécessaire à l’opération associée, sauf pour les boîtes de prétraitement et de tour, qui ont une durée fixe (preprocessing_time
et turn_time
, respectivement).
Pour ces boîtes, les losanges représentent le moment où les joueurs terminent leurs calculs.
Notez que l’interface graphique (GUI) vit aussi dans son propre processus.
Si vous la fermez, cela n’interrompra pas le jeu, mais arrêtera simplement le rendu de ce qui se passe.
Consultez ici pour les instructions sur comment arrêter complètement le jeu.
Maintenant, que se passe-t-il lorsqu’un joueur est trop lent et ne termine pas ses calculs de prétraitement ou de tour dans le temps imparti ?
Cela dépend de l’option game_mode
, qui peut être définie lors de la création d’une partie PyRat.
Voici les modes possibles :
-
Match – En mode match (
game_mode=GameMode.MATCH
), si un joueur prend plus de temps que prévu dans une phase, il sautera des tours jusqu’à ce que ses calculs soient terminés.
Cela peut arriver aussi bien pendant la phase de prétraitement (les autres joueurs peuvent alors commencer à se déplacer avant le joueur en retard), que lors de chaque tour.
Même s’il est en retard à un moment donné, tous les joueurs recevront toujours des informations à jour sur le jeu.Ceci est résumé par le diagramme suivant.
Dans cet exemple, le Joueur 1 a été si lent pendant le prétraitement qu’il doit sauter deux tours.
Le Joueur 2 a terminé le prétraitement à temps et a donc pu effectuer son tour 1 normalement.
Cependant, il a pris trop de temps à ce moment-là et a dû sauter le tour 2. -
Synchronisé – En mode synchronisé (
game_mode=GameMode.SYNCHRONOUS
), le jeu attendra que tous les joueurs aient pris leurs décisions avant de les appliquer.
Les phases de prétraitement et de tour ont toujours un temps fixe, mais les joueurs peuvent le dépasser et les tours ne seront pas sautés.Cela se traduit par le diagramme suivant.
Avec les mêmes problèmes de temps que dans l’exemple précédent, on voit que le comportement du jeu PyRat n’est pas le même. -
Séquentiel – En mode séquentiel (
game_mode=GameMode.SEQUENTIAL
), le multiprocessing est désactivé.
Toutes les opérations sont faites les unes après les autres, et les dépassements de temps sont traités comme en mode synchronisé.
L’intérêt de ce mode est qu’il accélère les calculs, car isoler les joueurs dans des processus séparés peut causer une surcharge de calcul.Dans le diagramme suivant, on observe que les joueurs tournent dans le même processus que le jeu PyRat.
Notez que l’interface graphique tourne toujours dans son propre processus.
Les flèches entre PyRat et les joueurs sont omises pour la lisibilité.
Cependant, notez que les joueurs reçoivent les mêmes informations, et qu’ils se déplacent simultanément dans le labyrinthe.
Seul le processus de prise de décision est séquentiel. -
Simulation – Enfin, le mode simulation doit être utilisé lorsque vous lancez de nombreuses parties PyRat et souhaitez qu’elles se terminent le plus rapidement possible.
Définirgame_mode=GameMode.SIMULATION
est strictement équivalent à définir les optionspreprocessing_time=0.0
,turn_time=0.0
,game_mode=GameMode.SEQUENTIAL
etrender_mode=RenderMode.NO_RENDERING
lors de la création du jeu.Le diagramme résultant est le suivant.
Les rectangles indiquent le début de la phase, et les losanges la fin des calculs.
Contrairement au mode séquentiel, il n’y a plus d’interface graphique, et les tours commenceront dès que tous les joueurs sont prêts.
Par défaut, PyRat utilise les options suivantes :
-
Jeu à équipe unique – Mode séquentiel.
-
Jeu multi-équipes – Mode match.
3.5 — PyRat et la POO
PyRat est ce que l’on appelle un programme orienté objet.
Pour l’instant, vous n’avez pas vraiment besoin de comprendre plus sur la programmation orientée objet (POO) pour pouvoir commencer à travailler sur le projet.
Au cours du semestre, vous suivrez un cours dédié sur le sujet.
Connaître un peu plus la POO peut vous aider à mieux comprendre PyRat.
Normalement, vous devriez pouvoir écrire un programme PyRat sans cela, en acceptant d’utiliser quelques codes que vous ne comprenez pas entièrement.
Si certains points ne sont pas clairs lors de votre lecture, nous vous encourageons à revenir ici pour lire les quelques éléments ci-dessous.
4 — Tutoriel
4.1 — Découvrir PyRat
Ouvrez maintenant Visual Studio Code.
Dans votre espace de travail PyRat, dans le sous-répertoire pyrat_workspace/games
, vous devriez trouver un fichier nommé tutorial.ipynb
.
Nous vous invitons à lire son contenu, et à exécuter les blocs de code au fur et à mesure de votre lecture.
Ce tutoriel vous expliquera comment créer un jeu PyRat, et comment écrire un joueur PyRat.
Une fois que vous avez fini de lire le fichier tutoriel, veuillez continuer la lecture de cette page.
4.2 — Le modèle PyRat
Lorsque vous créez un espace de travail PyRat, on vous donne quelques programmes dans le sous-répertoire pyrat_workspace/players
.
Pendant le tutoriel, vous avez déjà rencontré le fichier TemplatePlayer.py
, qui peut être utilisé comme projet vierge pour commencer à créer un joueur PyRat.
Détaillons les différents éléments de ce fichier :
-
Documentation – Le programme commence par une documentation globale qui explique ce qu’il contient.
Il est toujours bon d’ajouter un petit texte comme celui-ci, pour vous (ou les autres utilisateurs de votre code) pour se souvenir de ce que vous faites.
De plus, ce type particulier de commentaires (""" ... """
) est analysé lors de la production des fichiers de documentation, comme vous l’avez fait lors de l’installation.
Vous trouverez plusieurs endroits dansTemplatePlayer.py
avec de la documentation.
Dans les blocs de code suivants, nous les omettrons pour la lisibilité.""" This file contains useful elements to define a particular player. In order to use this player, you need to instanciate it and add it to a game. Please refer to example games to see how to do it properly. """
-
Imports – La chose suivante que vous trouverez dans ce fichier est une série d’importations.
Les trois premières lignes importent quelques décorateurs, qui permettent d’indiquer les types des différents arguments de fonction en Python (ex.x: Number
).
Cela peut être utile pour indiquer aux gens comment utiliser votre code correctement.
De plus, certains IDE utilisent ces indications pour détecter d’éventuelles erreurs dans les codes.# External imports from typing import * from typing_extensions import * from numbers import * # PyRat imports from pyrat import Player, Maze, GameState, Action
-
Définition de la classe – Un programme PyRat doit commencer par les lignes suivantes.
En écrivant vos propres programmes, vous devrez changer le nom de la classeTemplatePlayer
par un nom de votre choix.De plus, notez que tous les joueurs PyRat doivent hériter de la classe
Player
(définie dans le modulepyrat
).
Cela se fait en ajoutant(Player)
après le nom de la classe ci-dessous.
Ce faisant, tous les joueurs que vous écrirez auront accès aux attributs (données) et méthodes (fonctions) définis dans la classePlayer
.
En particulier, vous pouvez accéder à l’attributname
, qui sera utile pour savoir où vous êtes dans le labyrinthe, comme montré plus tard dans cette page.class TemplatePlayer (Player):
InformationVous pouvez consulter la documentation de la classe
Player
, que vous avez générée lors de l’installation.
Elle devrait se trouver dans le sous-répertoirepyrat_workspace/doc
. -
Constructeur – Il y a une méthode particulière appelée
__init__
qui est appelée quand vous créez un joueur, c’est-à-dire quand vous exécutez le codep = TemplatePlayer()
.
Cette méthode doit toujours commencer par l’argumentself
, et finir par les arguments*args, **kwargs
.
Vous pouvez ajouter tous les arguments que vous voulez au milieu.
Si vous voulez plus de détails, veuillez vous référer à la section PyRat et POO, et à ce lien.De plus, le corps de cette méthode doit toujours commencer par
super().__init__(*args, **kwargs)
.
Cette ligne permet de passer les arguments supplémentaires dansargs
etkwargs
au constructeur parent.
Dans notre cas, ces arguments sontname
etteam
, définis dans le constructeur de la classePlayer
.L’appel
print
est juste là pour l’exemple.
Vous pouvez le remplacer par ce que vous voulez (cela est aussi vrai pour les méthodes suivantes).def __init__ ( self: Self, *args: Any, **kwargs: Any ) -> Self: # Inherit from parent class super().__init__(*args, **kwargs) # Print phase of the game print("Constructor")
-
Prétraitement – Comme mentionné ci-dessus, un jeu PyRat commence par une phase de prétraitement, c’est-à-dire des calculs faits avant que les joueurs commencent à se déplacer dans le labyrinthe.
La méthodepreprocessing
est l’endroit où vous devez décrire ces calculs.
C’est le jeu PyRat qui appellera cette méthode quand nécessaire.
Pour cette raison, vous ne pouvez pas renommer la méthode, ni ajouter/enlever des arguments, sinon le jeu plantera.@override def preprocessing ( self: Self, maze: Maze, game_state: GameState, ) -> None: # Print phase of the game print("Preprocessing")
InformationLe
@override
qui apparaît au-dessus depreprocessing
,turn
etpostprocessing
est un décorateur qui indique que la méthode redéfinit une méthode existante dans la classe parentePlayer
. -
Tour – C’est ici que vous devez décrire ce qui se passe pendant un tour du jeu.
Comme pourpreprocessing
, le jeu PyRat l’appellera quand nécessaire, donc ne touchez pas à sa définition.
La méthodeturn
doit retourner une action valide, comme décrit dans l’énumérationAction
(définie dans le modulepyrat
).@override def turn ( self: Self, maze: Maze, game_state: GameState, ) -> Action: # Print phase of the game print("Turn", game_state.turn) # Return an action return Action.NOTHING
-
Post-traitement – Enfin, vous avez une méthode
postprocessing
pour décrire le comportement post-traitement de votre joueur.
Comme pour les méthodes précédentes, le jeu PyRat l’appellera quand nécessaire, donc ne touchez pas à sa définition.@override def postprocessing ( self: Self, maze: Maze, game_state: GameState, stats: Dict[str, Any], ) -> None: # Print phase of the game print("Postprocessing")
Notez que vous n’êtes pas limité à ces méthodes prédéfinies.
Si vous voulez ajouter une méthode say_hello
à votre joueur, il vous suffit d’ajouter le code suivant :
def say_hello ( self: Self,
to_whom: str
) -> None:
print("Hello", to_whom)
Vous pouvez ensuite appeler cette méthode depuis __init__
, preprocessing
, turn
, postprocessing
, ou toute méthode que vous avez ajoutée à votre classe, selon vos besoins.
Maintenant que vous êtes familier avec le fichier PlayerTemplate.py
, voyons comment les fichiers Random1.py
, Random2.py
et Random3.py
ont été construits.
Nous allons détailler ces trois programmes aléatoires.
Cela devrait vous aider à démarrer dans l’activité pratique ci-dessous :
4.3 — Random 1
Le premier programme aléatoire, défini dans le fichier Random1.py
, est essentiellement le joueur le plus naïf que vous puissiez imaginer.
À chaque tour, le programme choisira une décision au hasard, parmi les décisions valides possibles.
Ces décisions possibles sont définies dans l’énumération Action
dans le module pyrat
.
Pour créer ce programme, nous allons faire les changements suivants au modèle.
D’abord, nous allons utiliser des nombres aléatoires, donc nous devons importer la bibliothèque random
:
import random
Ensuite, nous renommons la classe en Random1
:
class Random1 (Player):
Il n’y a rien à faire à l’instanciation pour ce joueur, mais nous devons quand même garder le constructeur pour passer les arguments name
et turn
au constructeur de la classe Player
si nécessaire (avec la ligne qui commence par super
).
De même, nous n’avons rien à faire pendant les phases de prétraitement et post-traitement.
Ces méthodes peuvent être supprimées, car elles sont optionnelles.
Nous n’avons donc que la méthode turn
qui reste, qui doit retourner une action.
Pour cela, nous choisissons de définir une méthode appelée find_next_action
, qui fera cela :
@override
def turn ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Return an action
action = self.find_next_action()
return action
Enfin, définissons cette méthode find_next_action
, pour qu’elle retourne une action aléatoire parmi la liste des actions possibles :
def find_next_action ( self: Self
) -> Action:
# Choose a random action to perform
action = random.choice(list(Action))
return action
Au lieu de créer une méthode find_next_action
, nous aurions pu écrire directement notre méthode turn
comme suit :
@override
def turn ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Choose a random action to perform
action = random.choice(list(Action))
return action
Cependant, trouver la prochaine action deviendra de plus en plus complexe avec les prochains programmes aléatoires, donc c’est une bonne idée de créer une fonction pour cela dès maintenant.
Dans le sous-répertoire pyrat_workspace/games
, vous pouvez voir un fichier appelé visualize_Random1.ipynb
.
Ce fichier crée une partie avec une instance de Random1
.
Si vous exécutez son contenu, vous le verrez se déplacer dans le labyrinthe, comme suit :
4.4 — Random 2
Le deuxième joueur aléatoire est un peu plus intelligent.
Se déplacer comme Random1
a l’inconvénient de parfois se heurter aux murs, ou peut-être de retourner une action qui ne fait rien (Action.NOTHING
).
Ici, nous allons retourner une action au hasard, parmi celles qui mènent quelque part.
Pour obtenir cette information, nous allons utiliser les arguments fournis par le jeu PyRat à la méthode turn
.
Concrètement, partons du fichier Random1.py
pour construire le fichier Random2.py
.
D’abord, changeons le nom de la classe en Random2
:
class Random2 (Player):
Maintenant, mettons à jour la méthode find_next_action
pour retourner une action valide aléatoire.
Pour cela, nous aurons besoin d’utiliser la carte du labyrinthe et la configuration actuelle du jeu, reçues par turn
sous forme des arguments maze
(un objet de la classe Maze
, définie dans le module pyrat
) et game_state
(un objet de la classe GameState
, définie dans le module pyrat
), respectivement.
Ces arguments ont plusieurs méthodes qui peuvent être appelées.
En particulier, game_state
contient la position actuelle du joueur dans le labyrinthe, que vous pouvez récupérer avec game_state.player_locations[self.name]
(self.name
est défini dans la classe parente Player
).
De plus, maze
contient une méthode get_neighbors
qui peut vous donner les cellules autour d’une cellule donnée, et une méthode locations_to_action
, qui retourne l’action nécessaire pour aller d’une cellule à une cellule adjacente.
Si vous consultez la documentation ou le code de la classe Maze
, vous ne trouverez pas la méthode get_neighbors
mentionnée ci-dessus.
En fait, comme PyRat est orienté objet, Maze
hérite de la classe Graph
, car un labyrinthe est un graphe particulier.
Cela est indiqué dans Maze.py
par le code suivant :
class Maze (Graph):
En pratique, cela signifie que toute variable de type Maze
aura aussi accès aux attributs et méthodes de la classe parente Graph
.
C’est très pratique pour factoriser les codes.
Vous apprendrez plus sur la programmation orientée objet (POO) dans la session de programmation 4.
Pour l’instant, souvenez-vous simplement que vous pouvez vérifier les méthodes et attributs disponibles d’une variable v
avec la commande Python suivante :
print(dir(v))
Passons les arguments nécessaires à find_next_action
dans turn
:
@override
def turn ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Return an action
action = self.find_next_action(maze, game_state)
return action
Et mettons à jour la méthode find_next_action
:
def find_next_action ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Choose a random neighbor
neighbors = maze.get_neighbors(game_state.player_locations[self.name])
neighbor = random.choice(neighbors)
# Retrieve the corresponding action
action = maze.locations_to_action(game_state.player_locations[self.name], neighbor)
return action
Vous pouvez consulter la documentation des classes Maze
et GameState
, que vous avez générée lors de l’installation.
Elles devraient se trouver dans le sous-répertoire pyrat_workspace/doc
.
De plus, pour savoir quelles méthodes et attributs sont utilisables, vous pouvez utiliser le mot-clé dir
comme suit : dir(maze)
ou dir(game_state)
.
Par convention, en Python, les attributs et méthodes commençant par un underscore ne doivent pas être utilisés par vous.
Dans le sous-répertoire pyrat_workspace/games
, vous pouvez voir un fichier appelé visualize_Random2.ipynb
.
Ce fichier crée une partie avec une instance de Random2
.
Si vous exécutez son contenu, vous le verrez se déplacer dans le labyrinthe, comme dans la vidéo ci-dessous.
Notez que puisque le programme utilise des valeurs aléatoires, vous pouvez observer une trajectoire différente.
4.5 — Random 3
Le troisième joueur aléatoire fonctionne comme suit : à chaque tour, il se déplace vers une cellule adjacente non visitée au hasard.
S’il n’en existe pas, il se comporte comme Random2
.
Partons du contenu du fichier Random2.py
pour créer le fichier Random3.py
.
Comme avant, nous renommons d’abord la classe en Random3
:
class Random3 (Player):
Maintenant, contrairement à ce que nous avons fait avec les programmes précédents, nous allons devoir mémoriser des informations entre les tours.
Plus précisément, nous allons maintenir un ensemble, que nous enrichirons tour après tour.
Un ensemble est une structure de données qui peut contenir des éléments.
Contrairement aux listes, il ne peut pas y avoir de doublons d’un élément dans un ensemble (par exemple, ajouter 42
deux fois dans un ensemble revient à l’ajouter une seule fois).
De plus, les ensembles sont des structures de données non ordonnées.
Pour créer cet ensemble, nous devons le déclarer et l’initialiser dans la méthode __init__
comme suit.
Toutes les variables définies dans cette méthode particulière seront accessibles à toutes les méthodes de la classe qui commencent par l’argument self
.
def __init__ ( self: Self,
*args: Any,
**kwargs: Any
) -> Self:
# Inherit from parent class
super().__init__(*args, **kwargs)
# We create an attribute to keep track of visited cells
self.visited_cells = set()
Ensuite, mettons à jour la méthode turn
pour remplir cet ensemble :
@override
def turn ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Mark current cell as visited
if game_state.player_locations[self.name] not in self.visited_cells:
self.visited_cells.add(game_state.player_locations[self.name])
# Return an action
action = self.find_next_action(maze, game_state)
return action
Et prenons cette information en compte dans la méthode find_next_action
:
def find_next_action ( self: Self,
maze: Maze,
game_state: GameState,
) -> Action:
# Go to an unvisited neighbor in priority
neighbors = maze.get_neighbors(game_state.player_locations[self.name])
unvisited_neighbors = [neighbor for neighbor in neighbors if neighbor not in self.visited_cells]
if len(unvisited_neighbors) > 0:
neighbor = random.choice(unvisited_neighbors)
# If there is no unvisited neighbor, choose one randomly
else:
neighbor = random.choice(neighbors)
# Retrieve the corresponding action
action = maze.locations_to_action(game_state.player_locations[self.name], neighbor)
return action
Dans le sous-répertoire pyrat_workspace/games
, vous pouvez voir un fichier appelé visualize_Random3.ipynb
.
Ce fichier crée une partie avec une instance de Random3
.
Si vous exécutez son contenu, vous le verrez se déplacer dans le labyrinthe, comme suit :
4.6 — Comparaison des programmes aléatoires
Nous avons maintenant trois joueurs fonctionnels, mais savons-nous si nos changements ont vraiment amélioré le comportement du rat ?
Un rapide coup d’œil aux vidéos ci-dessus pourrait suggérer que peut-être non, car la durée de la vidéo pour Random2
est plus longue que celle de Random1
.
Pour évaluer la qualité de nos programmes, il est bon de faire tourner plusieurs parties et d’observer les résultats en moyenne.
Dans le sous-répertoire pyrat_workspace/games
, nous vous fournissons un script appelé compare_all_randoms.ipynb
.
Ce script effectuera 1 000 parties aléatoires pour chacun des trois joueurs, et enregistrera combien de tours il faut pour compléter chaque partie.
Tous les joueurs joueront exactement les mêmes 1 000 parties (c’est-à-dire que les labyrinthes et les positions des morceaux de fromage seront les mêmes) afin qu’ils soient comparés équitablement.
Si vous regardez le script en détail, vous verrez qu’il tourne en mode simulation, ce qui signifie que l’interface graphique n’apparaîtra pas.
Ce script peut prendre un certain temps à s’exécuter.
Une fois le script terminé, il produira une figure qui montre la distribution cumulative de ces listes de tours nécessaires.
Pour chaque courbe, plus elle est à gauche, plus c’est rapide pour compléter toutes les parties :
À partir de ces courbes, il semble assez sûr de conclure que Random3
est une meilleure stratégie que Random2
, qui est une meilleure stratégie que Random1
.
Cependant, c’est encore une interprétation visuelle.
En effet, avons-nous fait assez de parties pour avoir un résultat qui a du sens ?
Ou observerions-nous quelque chose de différent avec plus de parties ?
Nous pouvons rendre cette conclusion plus robuste en utilisant un test d’hypothèse statistique.
En quelques mots, un tel test vous donnera une information quantitative sur la confiance que vous pouvez avoir dans votre conclusion.
Ici, nous allons utiliser un test U de Mann-Whitney pour essayer de répondre à la question “Est-ce que Random2
attrape le morceau de fromage plus vite que Random1
?”.
Le script compare_all_randoms.ipynb
effectue ce test pour vous (pour toutes les paires de programmes), et produit la sortie suivante :
Mann-Whitney U test between turns of program 'Random 1' and of program 'Random 2': MannwhitneyuResult(statistic=621982.0, pvalue=3.510523794197863e-21)
Mann-Whitney U test between turns of program 'Random 1' and of program 'Random 3': MannwhitneyuResult(statistic=787040.5, pvalue=1.821119832542608e-109)
Mann-Whitney U test between turns of program 'Random 2' and of program 'Random 3': MannwhitneyuResult(statistic=690090.5, pvalue=4.74979492768772e-49)
Sans entrer dans les détails, vous pouvez considérer que le champ “p-value” est un indicateur de la confiance dans la réponse.
Dans de nombreuses applications, une p-value inférieure à 0.05 est considérée comme suffisamment petite pour que le test soit significatif (c’est-à-dire que l’on peut faire confiance à ses conclusions).
Vous apprendrez plus sur ces tests dans vos cours de statistiques.
Nous pouvons maintenant conclure en toute sécurité que Random3
est une meilleure stratégie que Random2
, qui est une meilleure stratégie que Random1
.
5 — Votre premier programme
Cela a été une introduction assez longue (mais nécessaire) !
Maintenant, plongeons dans le code et écrivons votre premier programme PyRat.
5.1 — Random 4
Nous allons écrire une amélioration de Random3
.
D’abord, commencez par faire une copie de ce fichier et renommez la copie Random4.py
.
De plus, comme nous l’avons fait dans les trois premiers programmes aléatoires, vous devez renommer la classe en Random4
.
Comme vous avez pu le voir en exécutant visualize_Random3.ipynb
, le joueur Random3
a l’inconvénient de se déplacer aléatoirement lorsqu’il est coincé dans une zone où toutes les cellules ont déjà été visitées.
Une simple amélioration serait de se souvenir de notre trajectoire, et de revenir sur nos pas lorsque nous atteignons une zone où tous les voisins sont visités.
Pour créer cette stratégie, vous devrez faire ce qui suit :
-
__init__
– Dans le constructeur, vous devez créer un nouvel attribut nomméself.trajectory
.
Cela doit être une liste, qui stockera toutes vos positions passées. -
preprocessing
– Pendant la phase de prétraitement, vous devez initialiser la trajectoire avec votre position initiale.
Cette position peut être obtenue avecgame_state.player_locations[self.name]
. -
turn
– Au début de votre phase de tour, vous devez ajouter votre position actuelle à la trajectoire.
Cette position peut être obtenue avecgame_state.player_locations[self.name]
. -
find_next_action
– S’il n’y a pas de voisins non visités, au lieu de retourner un voisin aléatoire (clauseelse
du testif
), vous devez :- Retirer la dernière entrée dans votre trajectoire (utilisez la fonction
pop
des listes).
En effet, puisque vous revenez en arrière et que vous venez d’ajouter la position actuelle à la trajectoire, vous ne voulez pas y retourner.
Notez que cette position doit toujours être dansvisited_cells
, ce qui vous empêche d’y retourner plus tard. - Extraire la dernière entrée dans votre trajectoire (utilisez la fonction
pop
des listes), car elle contient le voisin où vous voulez aller ensuite.
- Retirer la dernière entrée dans votre trajectoire (utilisez la fonction
Ensuite, écrivez un programme visualize_Random4.ipynb
(dans le sous-répertoire pyrat_workspace/games
) qui lance une partie avec votre programme.
Vous devriez voir votre programme agir comme le joueur Random3
, sauf qu’il revient en arrière plus intelligemment lorsqu’il est coincé.
Voici une vidéo du comportement que vous devriez observer.
Faites attention à quand le rat entre dans une impasse, ou une zone avec uniquement des voisins visités (marquée par la trace marron) :
5.2 — Comparer les programmes aléatoires
Une fois que vous avez terminé, mettez à jour le script compare_all_randoms.ipynb
, pour comparer tous les programmes aléatoires (1 à 4).
Puis, exécutez ce script étendu, et répondez aux questions suivantes :
-
Est-ce que
Random4
est plus rapide queRandom3
pour attraper un morceau de fromage ?Correction -
Est-ce significatif ?
Correction
Pour aller plus loin
6 — Random 5
Écrivons un dernier programme aléatoire appelé Random5
.
Ici, nous allons faire une petite amélioration à Random4
.
En effet, parfois, le rat entre dans des impasses, ce qui est inutile.
Il existe plusieurs façons d’éviter d’entrer dans une impasse :
- Vous pourriez calculer un ensemble de cellules interdites où vous ne devriez jamais aller (et l’utiliser dans la méthode
find_next_action
). - Ou vous pourriez mettre à jour l’objet labyrinthe en supprimant les impasses (et utiliser ce labyrinthe mis à jour au lieu de celui reçu par la méthode
turn
).
Nous allons prendre la deuxième option ici.
Pour cela, consultez la documentation des classes Maze
et Graph
dans le module pyrat
.
Vous devriez trouver une fonction appelée remove_vertex
, qui supprime une cellule du labyrinthe.
Cela va être utile.
Alors, qu’est-ce qu’une impasse ?
C’est une cellule qui respecte tous les points suivants :
- Elle a un seul voisin.
- Ce n’est pas votre position de départ.
- Elle ne contient pas un morceau de fromage.
Notez que si vous supprimez une cellule d’impasse, sa cellule voisine peut toujours être une impasse.
Par conséquent, vous devrez peut-être parcourir vos sommets plusieurs fois.
Votre travail maintenant est :
- Écrire une méthode
simplify_maze
qui supprime toutes les impasses dans le labyrinthe. - Appeler cette méthode dans
preprocessing
et stocker son résultat dans un attributself.updated_maze
de votre classe. - Mettre à jour votre méthode
find_next_action
pour utiliser cet attribut au lieu du labyrinthe que vous recevez à chaque tour via les arguments deturn
.
Voici une vidéo du résultat attendu.
Remarquez comment le rat évite les impasses :
7 — Comparer les programmes aléatoires
Une fois que vous avez terminé, mettez à jour à nouveau le script compare_all_randoms.ipynb
, pour comparer tous les programmes aléatoires (1 à 5).
Puis, exécutez ce script étendu, et répondez aux questions suivantes :
-
Est-ce que
Random5
est plus rapide queRandom4
pour attraper un morceau de fromage ?Correction -
Est-ce significatif ?
Correction
Pour aller plus loin
8 — Trier les actions
Jusqu’à présent, vous avez choisi la prochaine action à visiter de manière aléatoire, avec une probabilité uniforme.
Une amélioration possible serait d’explorer les actions dans un ordre intéressant, selon qu’elles vous rapprochent ou vous éloignent du morceau de fromage.
Évidemment, cette notion de plus proche/plus loin est complexe, car elle nécessiterait de savoir comment atteindre le morceau de fromage dans le labyrinthe pour l’évaluer.
Cependant, une bonne distance que vous pouvez utiliser est la distance euclidienne.
En d’autres termes, voici ce que vous pouvez faire :
- Vous avez une liste
L
d’actions possibles (c’est-à-dire une fois que vous avez fait tous les filtrages dansRandom4
). - Pour chaque action
a
dansL
, déterminez la cellulec
où elle vous mènerait. - Calculez la distance euclidienne entre
c
et le morceau de fromage. - Choisissez l’action avec la valeur minimale de
c
.