R APPORT D ʼ ANALYSE
POURSUITE DE ROBOTS
T ABLE DES MATIÈRES
I.I
NTRODUCTION! 3
II.Q
UELQUES CONCEPTS INFORMATIQUES! 4
‣INTERFACES" 4
‣PROGRAMMATION ÉVÉNEMENTIELLE" 5
‣EXCEPTIONS" 6
‣RÉFÉRENCE DʼOBJET" 6
III.A
RCHITECTURE DU PROGRAMME! 7
‣CARTE" 7
‣ROBOTS" 8
‣INTERFACES GRAPHIQUES" 8
‣CONTRÔLE" 10
‣DIAGRAMME DE COMPOSITION ET DʼIMPLANTATION" 11
IV.S
TRUCTURE DES DONNÉES! 12
‣LA CARTE" 12
LISTE CHAÎNÉE! 13
LISTE DʼADJACENCE! 14
GRAPHE! 14
DICTIONNAIRE! 16
‣ROBOTS" 17
MATRICE DʼACCESSIBILITÉ! 17
PRISE DE DÉCISION! 19
PROBLÈME DU PLUS COURS CHEMIN! 19
I.I
NTRODUCTIONCe projet a pour objectif la simulation dʼune poursuite entre deux robots appelés «Proie» et
«Prédateur» tous deux pouvant se déplacer sur un espace clos à deux dimensions.
Il sera construit essentiellement autour de : Une grille
Deux robots
Une unité de visualisation : Pour lʼaffichage graphique des courses poursuites Une unité de contrôle : Pour la gestion des courses poursuites
Une unité de commande : Pour la saisie des paramètres de course par lʼutilisateur Le projet sera développé selon les concepts de la programmation orientée objet. Nous verrons alors comment les exploiter afin de rendre les éléments de lʼapplication indépendant et interopérable.
Le choix des structures de données est une étape cruciale au bon développement dʼune application. Ainsi nous vous proposerons diverses solution avec leur avantages et leurs inconvénients.
II.Q
UELQUES CONCEPTS INFORMATIQUESAvant de décrire lʼarchitecture de notre projet, nous allons définir quelques concepts essentiels au bon déroulement de nos travaux.
‣INTERFACES
Une interface est la couche limite entre deux éléments par laquelle ont lieu des échanges et des interactions.
Pour une classe ou un objet, une interface permet le couplage entre classes ou objets et seule la connaissance exhaustive des interfaces permet de garantir lʼinteropérabilité entre plusieurs entités hétérogènes.
En POO, celles-ci sont représentées par des déclarations de méthodes sans implantation. On nʼy retrouve uniquement, pour chaque méthode, que la définition de son profil, cʼest à dire son en-tête.
Si les interfaces permettent de déclarer des variables de référence portant leur type, elles ne sont pas, par contre, instanciables. En particulier, une interface ne possède pas de constructeur.
Une classe peut implanter une ou plusieurs interfaces, cʼest ce qui fait la force de cet outil.
Prenons un exemple, nous créons deux interfaces IPredateur et IVeneneux:
IPredateur : Attaquer
Manger une proie
IVeneneux :
Injecter du venin
Maintenant nous créons deux classes à partir de nos interfaces dont voici les intitulés : Lion implante IPredateur
Araignée implante IPrédateur, IVeneneux
Nos deux classes devront alors déclarer parmi leurs méthodes, celles présentent dans les interfaces quʼils implantent, il sʼagit dʼune sortie de contrat. Lʼavantage de celui-ci présente deux intérets :
La séparation entre le code de définition du comportement de lʼobjet et le code réalisant son implantation.
La spécification dʼun objet interface sur lequel il sera possible de «caster» nʼimporte quel objet implantant cette interface. Sur ces objets interface, ne seront utilisables que les méthodes décrites dans lʼinterface.
Ainsi grâce à ce concept, il est possible de créer des comportement génériques, le code devenant plus flexible tout en étant plus strict.
‣PROGRAMMATION ÉVÉNEMENTIELLE
En informatique, la programmation événementielle se dit fondée sur les événements. Le programme sera principalement définit par ses réactions aux différents événements qui peuvent se produire.
Nous pouvons définir la programmation événementielle comme ceci :
Un objet (la source) créer un type dʼévénement suite à une action (appel de méthode).
Un objet écouteur (la cible) reçoit la délégation de lʼévénement afin de pouvoir le traiter.
Pour cela, la source doit se lier à un écouteur, une méthode puissante permettant de mettre en place de manière souple et efficace ce modèle est lʼutilisation dʼinterfaces :
Une interface pour lʼécouteur permettant de recevoir les événements de la source que lʼon appellera «ITypeObjetEcouteur»
Une interface pour la source permettant dʼajouter/supprimer des écouteurs de type
«IObjetEcouteur» que lʼon appellera «IEcouteurs»
ITypeObjetEcouteur :
Mon action événementielle 1 Mon action événementielle 2 ...
IEcouteurs :
Ajouter un écouteur Supprimer un écouteur
La source dʼévénements se dotera dʼune série de méthodes appelant les actions associés aux écouteurs.
‣EXCEPTIONS
Comme dans tous programmes informatiques, la gestion des erreurs est souvent considérée comme une tâche fastidieuse et contraignante.
De nombreux langages de programmation de haut niveau possède un mécanisme de gestion de serreurs très répandu, celui des exceptions.
Ainsi lorsque quʼune fonction dʼun programme nʼest pas définie pour certaines valeurs de ses arguments, on défini une exception. Celle-ci représente donc une erreur, parmi les plus courantes en programmation nous avons lʼaccès non autorisé à une zone mémoire (erreur de manipulation de pointeur) et la division par zéro (on ne prévoit pas le cas où le diviseur est nul).
En plus des erreurs génériques déjà existantes, il est possible de créer soit même des exceptions et de définir un traitement particulier pour chacune dʼentre elles.
‣RÉFÉRENCE DʼOBJET
En programmation orientée objet, un objet est une instance de classe, la création dʼobjets sʼappelle donc lʼinstanciation. Cette instanciation ce fait grâce à un opérateur suivi du nom de la classe à instancier et de parenthèse contenant les paramètres dʼinstanciation (parenthèse vide sʼil il nʼy a pas de paramètres)
par exemple prenons une classe «Personne» nʼayant aucun paramètres : opérateur Personne()
Dans lʼétat actuel des choses (la simple création dʼobjet), lʼobjet nouvellement créé est inutilisable, pour la simple est bonne raison quʼil nʼy a aucune façon de la désigner (Personne représente le nom de la classe est non celui dʼune de ces instanciations).
Il est heureusement possible dʼidentifier chaque objet par un nom grâce à un élément appelé référence. Dʼun point de vue technique une référence désigne lʼadresse mémoire ou est stockée lʼobjet.
Ainsi on comprend rapidement que notre objet pourra être traité en tout point de notre programme si nous faisons passer son adresse mémoire en paramètre dʼautres objets.
Suivant le langage des programmation utilisé, les références se font soit de manière implicite, soit manuellement. Dans le cas dʼune utilisation masquée, la référence dʼun objet sera son nom :
Personne michel = opérateur Personne() Michel sera alors la référence de notre objet.
Ce concept est très important dans la réussite dʼun programme bien construit et sera, pour nous, un atout primordial.
III.A
RCHITECTURE DU PROGRAMMENous allons dans cette partie définir les interfaces dont seront dotées nos classes ainsi que les exceptions associées.
‣CARTE
Notre carte est un rectangle divisé en cases, chacune dʼentre elle pourra contenir un seul et unique élément qui sera soit un robot soit un obstacle. Celle-ci sera donc dotée dʼun système dʼajout et de suppression dʼobjets.
Lors dʼun ajout, la carte devra prendre en paramètre lʼobjet ainsi que les coordonnées auxquelles on souhaite le voir. Une interface «ICoordonnees» sera donc créée :
ICoordonnees :
Récupérer la valeur entière en abscisse Récupérer la valeur entière en ordonnée Récupérer les coordonnées
Calculer un vecteur Calculer un point
Transformer les coordonnées en numéro par rapport à la largeur dʼune carte
Cette interface est complétée dʼune série de méthodes de calculs mathématiques qui nous seront utiles par la suite.
Nos robots devront être dotés dʼun champ de vision, nous avons aussi décidé de lui attribuer un champ de déplacement. Cependant tous deux ayant une conception similaire nous parlerons plutôt de champ dʼaccessibilité.
Ce champ sera représenté par une matrice booléenne dont nous étudierons les subtilités par la suite.
IAccessibilite :
Récupérer la matrice booléenne dʼaccessibilité ICarte :
Ajouter un objet au point de coordonnées (x , y) Supprimer un objet
Déplacer un objet au point de coordonnées (x , y) Vérifier si une case est occupée
Vérifier si deux objets sont voisins Vérifier si un objet est sur la carte Vérifier si un objet peut en voir un autre Retourner les coordonnées dʼun objet
Récupérer les cases voisines au point de coordonnées (x , y) Vider la carte
Bien entendu cette interface étant le point névralgique de notre application, une gestion des exceptions devra être mise en place de façon rigoureuse afin dʼéviter par exemple :
Lʼajout de doublons
Le traitement de coordonnées inexistantes La manipulation dʼobjets absents sur la carte Les erreurs dʼadressage
‣ROBOTS
Un robot occupe exactement une case et une seule sur la grille. Il se déplace dʼune case à la fois maximum, au cours dʼune opération appelée tour. Chaque robot à son propre système de contrôle autonome qui décide de lʼaction à réaliser au tour suivant :
Rester immobile Se déplacer
Nous avons alors élaboré une interface «IAutomate», celle-ci ayant la faculté de voir et de se déplacer nous lui implantons «IVision» et «IDeplacement» (cf. «IAccessibilite»)
IAutomate :
Rejoindre une carte implantant lʼinterface «ICarte»
Quitter une carte
Se déplacer sur une carte au point de coordonnées (x , y)
‣INTERFACES GRAPHIQUES
Notre fenêtre principale réunie deux des trois unités indispensables à la bonne construction de notre projet :
Lʼunité de visualisation pour lʼaffichage graphique du déroulement des courses et statistiques Lʼunité de commande pour assurer la saisie par lʼutilisateur des différents paramètres
nécessaires à lʼexécution dʼune session de course Lʼunité de visualisation est divisée en deux interfaces :
Lʼinterface «IInformations» permettant dʼafficher des informations sur le jeu de courses en cours :
Envoyer le nombre de courses effectuées Envoyer le nombre de tours effectués
Envoyer le nombre de tours moyen par course Envoyer le taux dʼéchecs des courses poursuites
Lʼinterface «IGrille» permettant de visualiser les courses poursuites : Ajouter un pion au point de coordonnées (x , y)
Supprimer un pion au point de coordonnées (x , y)
Déplacer un pion au point de coordonnées (x , y) vers (x2, y2) Vérifier si les coordonnées (x , y) sont sur la carte
Vérifier si une case est occupée
Récupérer une case au point de coordonnées (x , y) ou en fonction de son numéro dʼordonnancement
Récupérer le nombre de pion sur la grille Vider la grille
Cette grille étant constituée de cases, afin de facilité la gestion dʼaffichage des pions une interface
«ICase» sera créée : Ajouter un pion Supprimer un pion Récupérer un pion
Ce jeu dʼinterfaces a aussi besoin dʼune gestion des exceptions particulières afin dʼéviter tout effet malin.
Une dernière interface, permettant dʼafficher la représentation du pion sur la grille est «IIcone», suivant la bibliothèque graphique utilisée, celle-ci sera construite de façon différente. Ici nous avons :
Une méthode permettant dʼenvoyer lʼicône du pion Une méthode permettant de récupérer lʼicône du pion
Un objet voulant être représenté sur la carte devra alors implanter notre interface, cʼest le cas de nos robot. Ainsi en plus de lʼinterface «IAutomate», le robot devra être doté dʼ«IIcone».
Lʼunité de commande quant à elle doit permettre à lʼutilisateur dʼenvoyer les paramètres de course suite à une action (Clic sur un bouton), «ICommande» :
Récupérer le nombre de courses à effectuer Récupérer le nombre de tours à effectuer Récupérer les coordonnées initiales de la proie Récupérer les coordonnées initiales du prédateur
Afin de faciliter lʼaccès aux méthodes de ces différentes interfaces lors de leurs implantations, une interface «IFenetrePrincipale» sera créée.
Etant lʼinterface graphique principale de notre application, cʼest elle qui servira de source dʼévénements lors dʼun clic sur bouton pour démarrer ou arrêter notre course.
«IFenetrePrincipale» implantera donc «IControleur», «IInformations», et «IGrille» mais aussi
«IListeners» permettant de:
Ajouter un écouteur Supprimer un écouteur
Récupérer la liste des écouteurs Les deux actions à écouter seront :
Le clic sur un bouton «Démarrer»
Le clic sur un bouton «Arrêter»
Naturellement une interface dʼécoute doit être créée afin de répondre à lʼévénement.
«IFenetrePrincipaleListener» déclarera les actions énumérées ci-dessus. Celle-ci sera implantée dans notre dernière unité de contrôle.
‣CONTRÔLE
Lʼunité de contrôle gère le déroulement des courses poursuites, elle effectue les initialisations et actions nécessaires pour assurer le bon déroulement des courses poursuites. A ce titre elle échange des informations avec la grille, les robots, lʼunité de visualisation et lʼunité de commande.
Lʼunité de contrôle aura donc accès à notre carte, nos deux robots et notre fenêtre principale, il devra aussi être écouteur de celle-ci, lʼinterface «IFenetrePrincipaleListener» devra alors lui être implantée.
Nous avons aussi décidé de gérer lʼessentiel des exceptions pouvant être déclarer par les interfaces dont il aura accès afin de pouvoir gérer lʼéchange des erreurs entre la carte et la fenêtre principale.
‣DIAGRAMME DE COMPOSITION ET DʼIMPLANTATION
Afin de rendre plus claire la mise en place de notre architecture, nous avons établi le diagramme de composition et dʼimplantation de nos objets interfaces. Bien entendu celui-ci sera étoffé au fur et à mesure de nos besoins.
IV.S
TRUCTURE DES DONNÉES‣LA CARTE
La carte qui accueillera nos robots est un rectangle divisé en cases, chaque case est identifiée par une abscisse et une ordonnée dont les valeurs sont des coordonnées entières.
Un tableau a deux dimensions semble être la solution la plus naturelle, cependant la manipulation dʼune telle structure devient vite rigide et fastidieuse pour les raisons suivantes :
Un tableau est un composant statique, la conception dʼun tableau dynamique reste envisageable cependant sa complexité spatiale et temporelle reste gourmande à grande échelle.
Un tableau occupe souvent de la mémoire inutilement (Cases vides).
Les erreurs de dépassement de bord obligent le développeur à conditionner son code. Ainsi plus un tableau aura de dimensions, plus le nombres éventuels de dépassement sera important.
Nous avons alors élaborer une méthode beaucoup plus souple, la liste dʼadjacence. Il sʼagit dʼun tableau nʼayant quʼune seule dimension contenant la liste des voisins de chaque case.
Encore un tableau me direz vous ? Oui cependant le tableau nʼest constitué que dʼune dimension ayant pour longueur le nombre de cases sur la carte, les effets de bord sʼen retrouvent limités.
La liste des voisins est créée à partir dʼune liste chaînée. Lʼavantage dʼune telle structure réside dans sa souplesse dʼutilisation.
LISTE CHAÎNÉE
Une liste est constituée de nœuds, chaque nœud à en attribut un objet quelconque ainsi que la référence de son successeur.
Il y a donc un ordre dans toute liste caractérisée par une tête de liste (premier nœud de la liste) et une fin correspondant au ième nœud nʼayant aucune référence de successeur.
Lʼordre lexicographique de notre liste ne nous intéresse guère puisque le numéro dʼune case nʼa aucune influence sur son ordonnancement.
La liste des voisins dʼune case sera donc construite sans se soucier de ce détail, lʼordre se fera par ajout.
La structure de notre liste sera alors la suivante :
LISTE DʼADJACENCE
Maintenant que nous savons créé une liste chaînée, voyons comment lʼappliquer dans notre carte.
Nous prendrons alors comme exemple une carte de taille m * n ou : m = 3
n = 3.
Afin dʼélaborer lʼalgorithme qui nous permettra de concevoir une telle liste à partir de m et n, nous allons nous appuyer sur la théorie des graphes.
GRAPHE
Nous allons créer une matrice dʼadjacence «MatriceAdj», son principe est simple. Nous créons une matrice booléenne de taille k * k ou k est le nombre de cases présentent dans la carte.
Lorsque deux cases i et j sont voisines alors MatriceAdj(i , j) = 1
Nous avons deux états initiales :
MatriceAdj(0, 1) et MatriceAdj(0 , n)
Ce procédé laisse apparaître deux symétries en vert qui vont nous permettre dʼalléger notre algorithme.
En effet les traits bleus correspondent à la limite indispensable qui doit être atteinte pour créer notre liste dʼadjacence lorsque lʼon parcours la matrice ligne par ligne, les restes des valeurs étant déduites par symétrie.
La ligne qui nous intéresse de prime abords est B1, en effet si lʼon regarde lʼindice de ligne pour B1 est 3. Nous ne croyons pas au hasard et ceci peut être généralisé comme ceci :
B1 = (m * n - 1) / 2
Maintenant si nous regardons notre motif, celui-ci est constitué de quatre diagonales, si lʼon parcours notre matrice à lʼaide de boucle, une seule sera nécessaire puisque nous avons pour tout i ne dépassant pas (m * n - 1) / 2 alors MatriceAdj(i , i + 1) = 1 exception faite pour deux des diagonales marquées régulièrement de 0.
Cette régularité attire notre oeil et lʼon devine aisément après quelques tests que si : (i + 1) % n = 0 alors MatriceAdj(i , i + 1) = 0
Enfin si nous regardons nos deux symétries en vert, on devine aisément que : MatriceAdj(i , j) = MatriceAdj(j , i )
Pour les effets de bord cela se complique un peu, en effet pour tout i allant de 0 à B1 : la diagonale D1 : MatriceAdj(i , j) = MatriceAdj(m*n - i - 1, m*n - i - 2)
la diagonale D2 : MatriceAdj(i , j ) = MatriceAdj(m*n - i - 1, m*n - n - i - 1)
La particularité de ce graphe nous permet dʼabstraire notre algorithme de façon prodigieuse, si nous nʼavions pas pris des notes de façon rigoureuse lors de son développement sa compréhension en serait resté difficile.
Pseudo-Code :
nbColonnes = nombre de colonnes nbLignes = nombre de lignes nbCases = nombre de cases
Pour tout i allant de 0 à (nbCases - 1) / 2
Si (i + 1) % nbColonnes est différent de 0 alors
" Ajouter à la case i le voisin (i +1)
" Ajouter à la case (i + 1) le voisin i
" Ajouter à la case (nbCases - i - 2) le voisin (nbCases - i - 1)
" Ajouter à la case (nbCases - i - 1) le voisin (nbCases - i - 2)
Fin Si
Si i est plus petit que nbColonnes*(nbColonnes-2)
" Ajouter à la case i le voisin (i + nbColonnes)
" Ajouter à la case (i + nbColonnes) le voisin i
" Ajouter à la case (nbCases - nbColonnes - i -1) le voisin (nbCases-i-1)
" Ajouter à la case (nbCases-i-1) le voisin (nbCases - nbColonnes - i -1)
Fin Si
Diagramme temporel :
DICTIONNAIRE
Maintenant que nous avons notre algorithme nous permettant de définir la carte du projet, nous allons maintenant nous intéresser au placement des objets sur celle-ci.
Si nous avions poursuivi sa création à lʼaide dʼun tableau à deux dimensions, lʼajout dʼun objet se serait passé de la manière suivante :
maCarte(0, 2) = monObjet
Et cʼest bien ici la stupidité dʼun telle structure ... en effet quʼen est t-il des autres cellules ? Bien entendu celles-ci restent vides et occupent de la mémoire inutilement ...
Afin de palier à ce problème nous avons eu lʼidée de créer une sorte de «Dictionnaire» composé dʼune liste de «Clé / Valeur».
Cette structure fonctionne comme un liste composée de nœud à la différence quʼun nœud est doté dʼun attribut «Clé» en plus.
De façon générale la valeur spécifiée est un objet quelconque, cependant dans notre cas nous utiliserons un objet de type «Coordonnees» implantant lʼinterface «ICoordonnees».
Ainsi à chaque fois quʼun objet voudra rejoindre une carte au point de coordonnées(x , y), nous lʼajouterons dans le dictionnaire ce qui nous donnera :
<monObjet1, Coordonnees(0 , 2)>
<monObjet2, Coordonnees(2 , 4)>
<monObjet3, Coordonnees(1 , 3)>
Lʼinterface «IDictionnaire» déclarera une liste de méthodes en adéquation avec notre projet : Ajouter une nouvelle valeur et sa clé associée
Supprimer une valeur en fonction de sa clé
Vérifier si une clé est présente dans le dictionnaire Vérifier si une valeur est présente dans le dictionnaire Récupérer une valeur en fonction en fonction de sa clé
Récupérer le nombre de valeurs contenues dans le dictionnaire
Nous pouvons désormais donner la composition finale de notre carte, celle-ci sera donc complétée :
Dʼune liste dʼadjacence pour la gestion des champs d'accessibilité et des déplacements.
Dʼun dictionnaire pour la position des éléments sur la carte.
‣ROBOTS
Dans cette partie nous allons nous intéresser à lʼutilisation des matrices dʼaccessibilité des robots ainsi quʼa la prise de décision lorsque la proie ou le prédateur effectue un tour.
MATRICE DʼACCESSIBILITÉ
Comme nous lʼavons vu lors du chapitre précédent, nos robots sont équipés dʼune matrice booléenne dont la cellule centrale représente le robot.
Quel est lʼintérêt dʼune telle structure ? Nous pouvons la voir comme la représentation dʼun champ dʼaction, lorsquʼune cellule est «Vraie» alors lʼaction est applicable, à contrario si la cellule est
«Fausse» ou que lʼon sort de la matrice alors la cellule cible est hors de portée.
Afin que la case centrale soit la représentation du robot, il nous faut une matrice dont les lignes et les colonnes soient impaires.
Dans notre exemple, nous avons développé un petit algorithme permettant de fixé un pas (ci-contre 2) correspondant à la portée maximale autorisée par le robot.
Si nous plaçons notre robot sur la carte, et que nous utilisons notre système pour savoir quelles sont les cases qui lui sont accessibles, une série de calculs mathématiques devient nécessaire.
Cʼest ici que prend tout lʼintérêt des méthodes de notre interface «ICoordonnees».
En effet, pour savoir si une case est accessible par le robot la manipulation de coordonnées vectorielles devient nécessaire.
Sur la carte notre robot est positionné en case 36, nous souhaitons savoir si il a accès à la case 30.
Une classe implantant lʼinterface «ICoordonnees»
nous permettra de convertir les numéros des cases en coordonnées suivant la largeur de la carte :
Case(36) = Coordonnees(5 , 1) Case(30) = Coordonnees(4 , 2)
Maintenant nous allons déterminer le vecteur V 36 vers 30 : V = (4 - 5 , 2 - 1) = (-1 , 1)
Une fois V trouvé, nous calculons les coordonnées de la cellule correspondante sur la matrice dʼaccessibilité :
Centre de la matrice = Coordonnees(2 , 2) P = (2 - 1, 2 + 1) = (1, 3)
P au point de coordonnées (1 , 3) vaut-il «Vraie» ? La réponse est oui, notre robot a accès à la case 30.
PRISE DE DÉCISION
Pour la prise de décision de la proie et du prédateur nous avons essayé de nous calquer sur un élément naturel qui est lʼinstinct.
Cet instinct nous lʼavons élaboré à lʼaide dʼune matrice dʼaccessibilité que nous avons interprétée comme étant une zone de recherche pour le prédateur où la proie est le centre de cette matrice.
Plus le pas du champ sera faible et plus son instinct sera affuté.
Le prédateur est sur la case 12, il sait que la proie se situe dans son champ dʼinstinct.
Il va alors choisir une case parmi celles qui lui sont accessibles et patrouiller jusquʼà ce quʼil la trouve.
Nous construisons ainsi une véritable intelligence artificielle, le prédateur nʼeffectue aucun déplacement aléatoire inutile.
Il serait intéressant dʼimplanter la même système pour la proie tentant de fuir son ennemi, nous aurions alors à faire ici à une véritable course poursuite.
PROBLÈME DU PLUS COURS CHEMIN
Afin dʼassurer la pérennité des courses poursuites, et si lʼon envisage dʼagrémenter notre carte dʼobstacles, trouver le plus court chemin entre deux cases dans un temps relativement court deviens nécessaire.
En effet le but étant que notre prédateur dévore notre proie, nous avons pensé à implanter lʼalgorithme du plus court chemin de Dijkstra.
Notre algorithme prendra comme paramètre : la liste dʼadjacence de nos cases
une liste de cases à parcourir définies grâce au champ de vision un point de départ
un point dʼarrivée