Un petit programme qui dessine successivement le trajet de recherche du chemin le plus court entre 2 cases d'une grille via l'algorithme A star.
Source / Exemple :
################################################################################
# #
# Mise en evidence de l'algorithme A star graphiquement #
# #
################################################################################
from Tkinter import *
from math import sqrt
class Noeud():
"""Chaque case du jeu est représentée par un objet noeud qui contient :
- sa position dans la grille
- son cout G : distance entre elle et son ascendant + cout G de son
ascendant
- son cout H : distance entre elle et le noeud final
- son cout F : somme de G et H"""
def __init__(self,x,y):
self.colonne = x
self.ligne = y
self.coutF = 0
self.coutG = 0
self.coutH = 0
self.parent = self
def Initialisation(event):
"""Création de la matrice(tabGrille) en fonction de la résolution
Mise en place de la grille
Mise en place de la case départ et finale et création de leur noeud
associé"""
global tabGrille,noeudDepart,noeudFinal,listeFermee,\
listeOuverte,echelle,resolution,caseDepart,caseArrivee,\
tabCasesChemin,tabCasesListeOuverte,tabCasesListeFermee
grilleJeu.delete(ALL)
tabGrille = []
resolution = 400 / echelle
for i in range(resolution):
tabGrille.append([[]]*resolution)
for ligne in range(resolution):
for colonne in range(resolution):
tabGrille[ligne][colonne] = 0
for i in range(resolution):
grilleJeu.create_line(i*echelle,0,i*echelle,400,fill='white')
for i in range(resolution):
grilleJeu.create_line(1,i*echelle,400,i*echelle,fill='white')
noeudDepart = Noeud(0,0)
caseDepart = grilleJeu.create_rectangle(noeudDepart.colonne*echelle,
noeudDepart.ligne*echelle,(noeudDepart.colonne*echelle)
+echelle,(noeudDepart.ligne*echelle)+echelle,
outline='white',fill='blue')
noeudFinal = Noeud(resolution-1,resolution-1)
caseArrivee = grilleJeu.create_rectangle(noeudFinal.colonne*echelle,
noeudFinal.ligne*echelle,(noeudFinal.colonne*echelle)
+echelle,(noeudFinal.ligne*echelle)+echelle,
outline='white',fill='green')
listeFermee = []
listeOuverte = []
tabCasesChemin = []
tabCasesListeOuverte = []
tabCasesListeFermee = []
##### Fonctions de l'algorithme ------------------------------------------------
def Algorithme(event):
"""Boucle while principale :
- on met le meilleur noeud de la liste ouverte dans la liste fermée
et on appelle la fonction qui va chercher ses voisins
- lorsque le meilleur noeud correspond au noeud final on sort de la
boucle pour afficher le chemin
- si le noeud final n'est pas atteint et si la liste des noeud à
explorer est vide : il n'y a pas de solution"""
global listeOuverte,listeFermee,noeudCourant,text,tabCasesListeOuverte,\
tabCasesListeFermee,intervalTemps
EffaceChemin('l')
intervalTemps = 250
tabCasesListeOuverte = []
tabCasesListeFermee = []
listeOuverte = []
listeFermee = []
###Initialistion du noeudCourant avec le noeud de départ
noeudCourant = noeudDepart
noeudCourant.coutH = Distance(noeudCourant,noeudFinal)
noeudCourant.coutF = noeudCourant.coutH
###On le met dans la liste ouverte
listeOuverte.append(noeudCourant)
while (not(noeudCourant.ligne == noeudFinal.ligne and \
noeudCourant.colonne == noeudFinal.colonne)\
and listeOuverte != []):
### On choisi le meilleur noeud ce sera le noeud courant
noeudCourant = MeilleurNoeud()
AjouterListeFermee(noeudCourant)
##### petite animation ###
### création d'un carré correspondant au noeud courant en marron
caseCourant = grilleJeu.create_rectangle(noeudCourant.colonne*echelle,
noeudCourant.ligne*echelle,(noeudCourant.colonne*echelle)+echelle,
(noeudCourant.ligne*echelle)+echelle,fill='maroon')
fen.update()
### on passe le noeud courant précédent en gris
fen.after(intervalTemps,CasesListeFermee())
### on ajoute l'actuel noeud courant à la liste des carrés les représentant
tabCasesListeFermee.append(caseCourant)
grilleJeu.lift(caseDepart)
#########################
### On va chercher les voisins du noeud courant
fen.after(intervalTemps,AjouterCasesAdjacentes(noeudCourant))
### on a atteint le noeud final
if noeudCourant.ligne == noeudFinal.ligne and noeudCourant.colonne == noeudFinal.colonne :
RemonterListe()
### On a pas atteint le noeud final et il n'y a plus de noeud à explorer
elif listeOuverte == []:
### simple animation
text = menu.create_text(115,5,text="--- PAS DE SOLUTION ---",anchor=NW,font="Century 10 normal bold",fill='red')
for i in range(2):
fen.update()
fen.after(250,Clignotement('hidden'))
fen.update()
fen.after(250,Clignotement('normal'))
fen.update()
fen.after(1000,Clignotement('hidden'))
def MeilleurNoeud():
"""Fonction qui renvoie le meilleur noeud de la liste ouverte en fonction
de son cout en F"""
cout = 5000000
noeud = None
for n in listeOuverte:
if n.coutF < cout:
cout = n.coutF
noeud = n
return noeud
def AjouterListeFermee(noeud):
"""Ajoute un noeud à la liste fermée et le supprime de la liste ouverte"""
global listeOuverte,listeFermee
listeFermee.append(noeud)
listeOuverte.remove(noeud)
def AjouterCasesAdjacentes(noeudCourant):
"""Fonction qui cherche tous les voisins possibles au noeud courant passé
en parametre."""
global listeOuverte,listeFermee
if choixDirections == 'huitPoints':
### les 8 déplacements possibles dans la matrice
deplacements = [(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1)]
elif choixDirections == 'quatrePoints':
deplacements = [(-1,0),(0,1),(1,0),(0,-1)]
for direction in deplacements:
coordSuivante = (noeudCourant.ligne + direction[0],
noeudCourant.colonne + direction[1])
###On vérifie qu'on sort pas de la matrice
if coordSuivante[0] >= 0 and coordSuivante[0] <= len(tabGrille)-1 and\
coordSuivante[1] >= 0 and coordSuivante[1] <= len(tabGrille[0])-1:
###On vérifie que le voisin n'est pas un obstacle (mur=2, vide=0)
if tabGrille[coordSuivante[0]][coordSuivante[1]] == 0:
###On crée un objet noeud au coordonnée du voisin
noeudTemp = Noeud(coordSuivante[1],coordSuivante[0])
###Le noeud courant sera son parent
noeudTemp.parent = noeudCourant
###On s'assure que ce voisin ne fait pas deja parti de la liste fermée
if not DejaPresentDansListe(noeudTemp,listeFermee):
###On calcule ses couts
noeudTemp.coutG = noeudTemp.parent.coutG + Distance(noeudTemp,noeudTemp.parent)
noeudTemp.coutH = Distance(noeudTemp,noeudFinal)
noeudTemp.coutF = noeudTemp.coutG + noeudTemp.coutH
n = DejaPresentDansListe(noeudTemp,listeOuverte)
###Si ce voisin est deja présent dans la liste ouverte
if n != False:
###On compare son cout G avec celui de la liste ouverte(n)
if noeudTemp.coutG < n.coutG:
###Si il est inférieur on remplace les couts et le parent de n par ceux du voisin récemment trouvé
n.parent = noeudCourant
n.coutG = noeudTemp.coutG
n.coutH = noeudTemp.coutH
n.coutF = noeudTemp.coutF
###Dans le cas contraire on ne change rien...
###Ce voisin n'est pas déja présent dans le liste ouverte
###donc on l'y ajoute
else:
listeOuverte.append(noeudTemp)
###animation
fen.after(intervalTemps ,CasesListeOuverte(noeudTemp))
fen.update()
def Distance(noeud1,noeud2):
"""Calcule des distances entre 2 noeuds suivant l'heuristique choisie"""
a = noeud2.ligne - noeud1.ligne
b = noeud2.colonne - noeud1.colonne
if choixHeuristique == 'racineDistEucli':
return sqrt((a*a) + (b*b))
elif choixHeuristique == 'distanceEucli':
return ((a*a) + (b*b))
elif choixHeuristique == "distManhattan":
return (abs(a)+abs(b))
def DejaPresentDansListe(noeud,liste):
"""Fonction qui cherche si un noeud est deja présent dans un liste"""
for n in liste:
if n.ligne == noeud.ligne and n.colonne == noeud.colonne:
return n
return False
### Fonctions d'animation -------------------------------------------------------
def Clignotement(etat):
"""Fonction qui passe un texte d'un etat à un autre suivant son argument"""
global text
menu.itemconfigure(text,state=etat)
def CasesListeOuverte(n):
"""Dessin d'un carré pour tous les noeuds ajoutés à la liste ouverte"""
tabCasesListeOuverte.append(grilleJeu.create_rectangle(n.colonne*echelle,
n.ligne*echelle,(n.colonne*echelle)+echelle,
(n.ligne*echelle)+echelle,fill='orange'))
def CasesListeFermee():
"""Passe le dernier élément de la liste des carrés représentants les noeuds
de la liste ferméé en gris"""
if tabCasesListeFermee!=[]:
grilleJeu.itemconfigure(tabCasesListeFermee[-1],fill='SteelBlue')
fen.update()
def RemonterListe():
"""Le but est atteint, cette fonction remonte le chemin d'ascendant en
ascendant en partant du dernier noeud courant choisi"""
global tabCasesChemin
chemin = []
tabCasesChemin = []
n = listeFermee[-1]
chemin.append(n)
n = n.parent
while n.parent != n:
chemin.append(n)
### dans la foulée on crée des carrés pour chaque noeud appartenant au chemin gagnant
tabCasesChemin.append(grilleJeu.create_rectangle(n.colonne*echelle,
n.ligne*echelle,(n.colonne*echelle)+echelle,
(n.ligne*echelle)+echelle,fill='red'))
n = n.parent
chemin.append(n)
grilleJeu.lift(caseDepart)
grilleJeu.lift(caseArrivee)
def EffaceChemin(event):
"""Efface toutes les cases correspondants au chemin gagnant à
la liste ouverte et à la liste fermée"""
if tabCasesChemin:
for caseRouge in tabCasesChemin:
grilleJeu.delete(caseRouge)
if tabCasesListeOuverte:
for caseOrange in tabCasesListeOuverte:
grilleJeu.delete(caseOrange)
if tabCasesListeFermee:
for caseGrise in tabCasesListeFermee:
grilleJeu.delete(caseGrise)
### Interface utilisateur ------------------------------------------------------
def Menu():
"""Création du menu"""
global textRacineDistEucli,textDistEucli,textDistManhattan,textQuatrePtsCardinaux,textHuitPtsCardinaux
menu.delete(ALL)
menu.create_text(10,10,text="Menu",anchor=NW,font="Century 10 normal bold",fill='maroon')
menu.create_text(10,25,text="T : Trouver Chemin ",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,40,text="L : Effacer Chemin",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,55,text="E : Tout Effacer ",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,80,text="- : Baisser la Resolution",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,95,text="+ : Monter la Resolution",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(210,25,text="M : Ajouter Mur ",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(355,28,365,38,outline='white',fill='black')
menu.create_text(210,40,text="Clic Droit : Effacer Mur",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(210,65,text="D : Deplacer Depart",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(355,68,365,78,outline='white',fill='blue')
menu.create_text(210,80,text="A : Deplacer Arrivee",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(355,83,365,93,outline='white',fill='green')
menu.create_text(210,105,text="Directions",anchor=NW,font="Century 10 normal bold",fill='white')
textQuatrePtsCardinaux = menu.create_text(210,120,text="4 : 4 points cardinaux",anchor=NW,font="Century 10 normal bold",fill='white')
textHuitPtsCardinaux = menu.create_text(210,135,text="8 : 8 points cardinaux",anchor=NW,font="Century 10 normal bold",fill='red')
menu.create_text(10,115,text="<- : Ralentir",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,130,text="-> : Accelerer",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_text(10,155,text="Noeuds liste ouverte",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(160,158,170,168,outline='white',fill='orange')
menu.create_text(10,170,text="Noeuds liste fermee",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(160,173,170,183,outline='white',fill='steelblue')
menu.create_text(10,185,text="Noeud courant",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(160,188,170,198,outline='white',fill='maroon')
menu.create_text(10,200,text="Chemin solution",anchor=NW,font="Century 10 normal bold",fill='white')
menu.create_rectangle(160,203,170,213,outline='white',fill='red')
menu.create_text(210,155,text="Choix de l'heuristique",anchor=NW,font="Century 10 normal bold",fill='white')
textRacineDistEucli = menu.create_text(210,170,text="1 : Racine Dist Euclidienne",anchor=NW,font="Century 10 normal bold",fill='red')
textDistEucli = menu.create_text(210,185,text="2 : Distance Euclidienne",anchor=NW,font="Century 10 normal bold",fill='white')
textDistManhattan = menu.create_text(210,200,text="3 : Distance Manhattan",anchor=NW,font="Century 10 normal bold",fill='white')
def Position(event):
"""Positionne un mur, la case départ ou la case d'arrivée aux coordonnées de la souris"""
global tabGrille,noeudDepart,noeudFinal
EffaceChemin('l')
if event.x > 0 and event.x < 400 and event.y > 0 and event.y < 400:
indice_colonne = event.x / echelle
indice_ligne = event.y / echelle
if mode == 'mur':
if (indice_colonne == noeudDepart.colonne and indice_ligne == noeudDepart.ligne) or\
(indice_colonne == noeudFinal.colonne and indice_ligne == noeudFinal.ligne):
return
else:
grilleJeu.create_rectangle(indice_colonne*echelle,indice_ligne*echelle,
(indice_colonne*echelle)+echelle,
(indice_ligne*echelle)+echelle,outline='white',fill='black')
tabGrille[indice_ligne][indice_colonne] = 2
if mode == 'depart':
if tabGrille[indice_ligne][indice_colonne] == 0:
grilleJeu.coords(caseDepart,indice_colonne*echelle,indice_ligne*echelle,
(indice_colonne*echelle)+echelle,(indice_ligne*echelle)+echelle)
grilleJeu.lift(caseDepart)
noeudDepart = Noeud(indice_colonne,indice_ligne)
if mode == 'arrivee':
if tabGrille[indice_ligne][indice_colonne] != 2:
grilleJeu.coords(caseArrivee,indice_colonne*echelle,indice_ligne*echelle,
(indice_colonne*echelle)+echelle,(indice_ligne*echelle)+echelle)
grilleJeu.lift(caseArrivee)
noeudFinal = Noeud(indice_colonne,indice_ligne)
def EffacerMur(event):
"""Efface un mur"""
global tabGrille
EffaceChemin('l')
if event.x > 0 and event.x < 400 and event.y > 0 and event.y < 400:
indice_colonne = event.x / echelle
indice_ligne = event.y / echelle
if (indice_colonne == noeudDepart.colonne and indice_ligne == noeudDepart.colonne) or\
(indice_colonne == noeudFinal.colonne and indice_ligne == noeudFinal.colonne):
return
else:
grilleJeu.create_rectangle(indice_colonne*echelle,indice_ligne*echelle,
(indice_colonne*echelle)+echelle,
(indice_ligne*echelle)+echelle,outline='white',fill='wheat')
tabGrille[indice_ligne][indice_colonne] = 0
def BougeDepart(event):
"""Passe la souris en mode déplacement de la case départ"""
global mode
EffaceChemin('l')
mode = 'depart'
def BougeArrivee(event):
"""Passe la souris en mode déplacement de la case arrivée"""
global mode
EffaceChemin('l')
mode = 'arrivee'
def Mur(event):
"""Passe la souris en mode création d'un mur"""
global mode
mode = 'mur'
def EchellePlus(event):
"""Baisse la résolution"""
global indice,echelle
indice += 1
if indice > len(tableauEchelle)-1:
indice = len(tableauEchelle)-1
echelle = tableauEchelle[indice]
Initialisation('e')
def EchelleMoins(event):
"""Agrandit la résolution"""
global indice,echelle
indice -= 1
if indice < 0:
indice = 0
echelle = tableauEchelle[indice]
Initialisation('e')
def Ralentir(event):
"""Ralentit la cadence"""
global intervalTemps
intervalTemps += 25
def Accelerer(event):
"""Accelère la cadence"""
global intervalTemps
intervalTemps -= 25
def ChoixHeuristique1(event):
global choixHeuristique
choixHeuristique = 'racineDistEucli'
menu.itemconfigure(textRacineDistEucli,fill='red')
menu.itemconfigure(textDistEucli,fill='white')
menu.itemconfigure(textDistManhattan,fill='white')
def ChoixHeuristique2(event):
global choixHeuristique
choixHeuristique = 'distanceEucli'
menu.itemconfigure(textDistEucli,fill='red')
menu.itemconfigure(textRacineDistEucli,fill='white')
menu.itemconfigure(textDistManhattan,fill='white')
def ChoixHeuristique3(event):
global choixHeuristique
choixHeuristique = 'distManhattan'
menu.itemconfigure(textDistManhattan,fill='red')
menu.itemconfigure(textDistEucli,fill='white')
menu.itemconfigure(textRacineDistEucli,fill='white')
def ChoixQuatrePtsCardinaux(event):
global choixDirections
choixDirections = 'quatrePoints'
menu.itemconfigure(textQuatrePtsCardinaux,fill='red')
menu.itemconfigure(textHuitPtsCardinaux,fill='white')
def ChoixHuitPtsCardinaux(event):
global choixDirections
choixDirections = 'huitPoints'
menu.itemconfigure(textQuatrePtsCardinaux,fill='white')
menu.itemconfigure(textHuitPtsCardinaux,fill='red')
### ----------------------------------------------------------------------------
fen = Tk()
fen.title("Algorithme A*")
fen.resizable(0,0)
tabGrille = []
grilleJeu = Canvas(fen,width=400,height=400,bg='wheat')
grilleJeu.grid(row=0,column=0)
menu = Canvas(fen,width=400,height=220,bg='DarkCyan')
menu.grid(row=1,column=0)
Menu()
grilleJeu.bind('<Button-1>',Position)
grilleJeu.bind("<B1-Motion>",Position)
grilleJeu.bind('<Button-3>',EffacerMur)
grilleJeu.bind("<B3-Motion>",EffacerMur)
fen.bind("t",Algorithme)
fen.bind('e',Initialisation)
fen.bind('l',EffaceChemin)
fen.bind('-',EchellePlus)
fen.bind('+',EchelleMoins)
mode = 'mur'
fen.bind('m',Mur)
fen.bind('d',BougeDepart)
fen.bind('a',BougeArrivee)
fen.bind('<Left>',Ralentir)
fen.bind('<Right>',Accelerer)
fen.bind('1',ChoixHeuristique1)
fen.bind('2',ChoixHeuristique2)
fen.bind('3',ChoixHeuristique3)
fen.bind('4',ChoixQuatrePtsCardinaux)
fen.bind('8',ChoixHuitPtsCardinaux)
tableauEchelle = [5,10,16,20,25,40]
indice = 1
choixHeuristique = 'racineDistEucli'
choixDirections = 'huitPoints'
echelle = tableauEchelle[indice]
Initialisation('e')
fen.mainloop()
Conclusion :
Toute idée d'amélioration sera la bienvenue.
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.