Practical activity

Duration2h30

Pré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.

Information

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.

Important

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.

Information

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”.



Information


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.

Information

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.

Information

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éfinir game_mode=GameMode.SIMULATION est strictement équivalent à définir les options preprocessing_time=0.0, turn_time=0.0, game_mode=GameMode.SEQUENTIAL et render_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.

Information

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.

Information

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.

En programmation standard, vous manipulez des variables avec des types de données basiques (entiers, flottants…), mais aussi des types de données plus complexes (listes, ensembles, dictionnaires…).
Ces structures de données complexes sont pratiques pour organiser l’information de manière commode.
La programmation orientée objet (POO) étend cette notion avec des objets.
Un objet est essentiellement une structure de données, complétée par des fonctions (appelées “méthodes”).

En Python, vous manipulez souvent des objets.
En fait, même les listes sont des objets.
Quand vous les manipulez en utilisant par exemple l.append(x) (où l est une liste et x un élément que vous voulez ajouter), vous appelez la méthode append de votre objet l.

Pour pouvoir créer vos propres objets personnalisés, vous devez définir une classe.
Une classe peut être comprise comme une sorte de moule, et un objet serait la chose produite en utilisant ce moule.
Un seul moule peut être utilisé pour produire plusieurs instances du même objet.

En Python, cela se fait comme suit :

# Ici, nous créons une classe "Animal"  
class Animal ():  
  
  # Les classes ont une méthode spéciale "__init__" appelée constructeur  
  # C'est le code qui sera exécuté quand vous instancierez un animal plus tard  
  # Toutes les méthodes d'une classe doivent commencer par le mot-clé "self"  
  # Ce mot-clé référence l'objet lui-même quand la classe est instanciée  
  def __init__ (self, name):  
    self.nickname = name # Crée un attribut pour stocker le nom  
    self.nb_meals = 0 # Crée un attribut pour compter combien de repas l'animal a eu  
  
  # Maintenant, définissons une autre méthode qui fait des choses personnalisées  
  def eat_stuff (self, stuff_name):  
    print("Yum, tasty", stuff_name)  
    self.nb_meals += 1  

Ensuite, vous pouvez instancier et utiliser un objet de votre classe comme suit :

# Instancie deux objets de la classe Animal  
a = Animal("Snoopy")  
b = Animal("Garfield")  
  
# Affiche leurs attributs  
print(a.nickname, a.nb_meals)  
print(b.nickname, b.nb_meals)  
  
# Appelle une méthode sur le premier objet  
a.eat_stuff("cookie")  
  
# Affiche leurs attributs à nouveau  
print(a.nickname, a.nb_meals)  
print(b.nickname, b.nb_meals)  

Cela devrait produire la sortie suivante :

Sortie
Snoopy 0  
Garfield 0  
Yum, tasty cookie  
Snoopy 1  
Garfield 0  

Comme vous pouvez le voir, les attributs sont liés aux objets.
Quand nous avons appelé la méthode eat_stuff sur l’objet a, cela a changé la valeur de l’attribut nb_meals pour a, mais rien n’a changé pour b.

Le cadre POO peut être très utile pour organiser les codes.
En particulier, il existe un mécanisme appelé héritage, qui peut être utilisé pour factoriser des fonctions.
Complétons le code précédent comme suit :

# Ici, nous créons une classe "Dog"  
# Notez que la classe "Dog" hérite de "Animal"  
class Dog (Animal):  
  
  # Quand la classe Dog est instanciée, c'est son constructeur qui est appelé  
  # Il est bon de pratique d'appeler aussi le constructeur du parent  
  # Puis vous pouvez compléter avec des codes additionnels  
  # Il est aussi bon de ne pas répéter les arguments de la classe parente  
  # Les arguments *args et **kwargs sont là pour cela  
  def __init__ (self, size, *args, **kwargs):  
    super().__init__(*args, **kwargs) # Appelle le constructeur du parent  
    self.size = size # Crée un attribut pour stocker la taille  
  
  # Maintenant, définissons une nouvelle méthode  
  def bark (self):  
    print("Woof")  

Essayons ce code :

# Instancie un objet de la classe Dog  
a = Dog(42, "Snoopy")  
  
# Affiche ses attributs  
print(a.nickname, a.size, a.nb_meals)  
  
# Appelle ses méthodes  
a.eat_stuff("cookie")  
a.bark()  
  
# Affiche ses attributs à nouveau  
print(a.nickname, a.size, a.nb_meals)  

Cela devrait produire la sortie suivante :

Sortie
Snoopy 42 0  
Yum, tasty cookie  
Woof  
Snoopy 42 1  

Avec cette factorisation, si nous décidons plus tard de créer une classe Cat héritant de Animal, toutes les instances de la classe Cat n’auront pas accès à la méthode bark, mais pourront utiliser eat_stuff.

Ce n’est qu’un aperçu de ce que vous pouvez faire avec le cadre POO.
Pour l’instant, nous n’irons pas au-delà de ces quelques notions.

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 dans TemplatePlayer.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 classe TemplatePlayer 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 module pyrat).
    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 classe Player.
    En particulier, vous pouvez accéder à l’attribut name, qui sera utile pour savoir où vous êtes dans le labyrinthe, comme montré plus tard dans cette page.

    class TemplatePlayer (Player):  
    Information

    Vous 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épertoire pyrat_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 code p = TemplatePlayer().
    Cette méthode doit toujours commencer par l’argument self, 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 dans args et kwargs au constructeur parent.
    Dans notre cas, ces arguments sont name et team, définis dans le constructeur de la classe Player.

    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éthode preprocessing 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")  
    Information

    Le @override qui apparaît au-dessus de preprocessing, turn et postprocessing est un décorateur qui indique que la méthode redéfinit une méthode existante dans la classe parente Player.

  • Tour – C’est ici que vous devez décrire ce qui se passe pendant un tour du jeu.
    Comme pour preprocessing, le jeu PyRat l’appellera quand nécessaire, donc ne touchez pas à sa définition.
    La méthode turn doit retourner une action valide, comme décrit dans l’énumération Action (définie dans le module pyrat).

    @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")  
Information

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  
Information

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.

Information

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  
Information

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.

Information

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.

Information

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 :

Sortie
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 avec game_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 avec game_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 (clause else du test if), 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 dans visited_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.

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 que Random3 pour attraper un morceau de fromage ?

    Correction

    Oui.
    Visuellement, on observe que la courbe cumulative associée à Random4 est à gauche de celle de Random3.

  • Est-ce significatif ?

    Correction

    Oui.
    Le test U de Mann-Whitney nous donne la sortie suivante :

    Sortie
    Mann-Whitney U test between turns of program 'Random 3' and of program 'Random 4': MannwhitneyuResult(statistic=621805.0, pvalue=3.9989134611571045e-21)  

    La p-value est suffisamment faible pour conclure à la significativité du test.

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 attribut self.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 de turn.

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 que Random4 pour attraper un morceau de fromage ?

    Correction

    Oui.
    Visuellement, on observe que la courbe cumulative associée à Random5 est à gauche de celle de Random4.

  • Est-ce significatif ?

    Correction

    Oui.
    Le test U de Mann-Whitney nous donne la sortie suivante :

    Sortie
    Mann-Whitney U test between turns of program 'Random 4' and of program 'Random 5': MannwhitneyuResult(statistic=609971.0, pvalue=1.6479782581220633e-17)   

    La p-value est suffisamment faible pour conclure à la significativité du test.

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 dans Random4).
  • Pour chaque action a dans L, déterminez la cellule c 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.