Génération d'un labyrinthe avec recherche du chemin le plus court avec astar

Description

ce programme permet de:

- générer un labyrinthe aléatoire
- utilisation de l'algorithme astar pour trouver le chemin le plus court (même si dans le cas du labyrinthe parfait il y a un seul chemin possible l'algorithme utilisé trouve le meilleur chemin possible)
La sélection de la destination est faite avec le souris

Dépendances : pygame

Source / Exemple :


# -*- coding: utf8 -*-
"""
Laby , par Mehdi Cherti 2010 (mehdidc): 
	- generation d'un labyrinthe
	- utilisation de l'algorithme astar pour trouver le chemin le plus court (selection de la destination avec la souris)
"""
from  random import randint , choice
import pygame
from pygame.locals import *
import def_const
from math import sqrt

const = def_const.def_const ()
""" definitions des constantes """

# Couleurs
const.white  = (255 , 255 , 255)
const.pink   = (255 , 0 , 255)
const.black  = (0 , 0 , 0)
const.yellow = (255 , 255 , 0)

#directions 
const.right = 0
const.left = 1
const.up = 2
const.down = 3

# autres

const.wc = 20 # width d'un seul carreau du laby
const.hc = 20# height d'un seul carreau du laby

const.perso_radius = const.wc/2 # rayon du personnage (qui est un cercle)
const.time_perso = 50 # temps en ms avant chaque test du clavier pour les touches qui concernent le perso
const.time_perso_poll = 50 # temps en ms avant chaque mise a jour de la position du perso
""" fin definitions des constantes"""

class Point:
	def __init__ (self,xy):
		self.x = xy[0]
		self.y = xy[1]

""" Definition de la classe cellule de labyrinthe"""
class cellule:
	def __init__(self):
		self.state = False
		self.portes = [True , True , True , True] # Droite , Gauche , Haut , Bas
		
""" distance : retourne la distance euclidienne entre deux points """
def distance (p1 , p2):
	return sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)

""" classe Personnage """
class Perso:
	def __init__ (self,lab):
		self.x = 1
		self.y = 1
		self.chemin =None
		self.laby = lab
		self.color = const.yellow
	""" affichage """	
	def show(self,buffer):
		a,b = const.perso_radius , const.perso_radius
		pygame.draw.circle (buffer, self.color , (a+self.laby.sx+self.x*self.laby.wc,b+self.laby.sy+self.y*self.laby.hc) , 
		const.perso_radius)
		
	""" mouvement selon la direction"""
	def move (self,dir):
			
		if not self.laby.get_cell (self.x , self.y).portes[dir]:
			if dir == const.right and self.x+1 < self.laby.w :
				self.x += 1
			if dir == const.left and self.x-1 >=0:
				self.x -= 1
			if dir == const.up and self.y-1 >=0:
				self.y -= 1
			if dir == const.down and self.y+1 < self.laby.h:
				self.y += 1
				
	""" fonction utilisée par astar pour traiter une des cases adjacentes"""
	def traitement (self ,  liste_fermee , liste_ouverte , x ,  y , dir ,parent ,dest):
		if parent.portes[dir] : return
		c = self.laby.get_cell (x,y)
		if c in liste_fermee : return
		
		if c in liste_ouverte:
			G = distance ( (x,y) , (parent.x , parent.y))
			c.dir = self.laby.notdir (dir)
			if G < c.G:
				c.G = G
				c.F = c.H + c.G
				c.parent = parent
		else:
			c.parent = parent
			c.dir = self.laby.notdir (dir)
			c.G = distance ( (x,y) , (parent.x , parent.y))
			c.H = distance ( (x,y) , (dest.x ,dest.y))
			c.F = c.G + c.H
			liste_ouverte.append (c)
	""" retourne , après avoir trouvé le chemin avec astar , un tableau contenant le chemin"""
	def  get_astar (self, csource , cdest):
		
		source = self.laby.get_cell (csource[0] , csource[1])
		dest = self.laby.get_cell (cdest[0] , cdest[1])
		cur=dest
		chemin = []
		while cur and (cur.x != source.x or cur.y != source.y):
			if cur.x == cur.parent.x-1 :
				chemin.append (const.right)
			if cur.x == cur.parent.x+1:
				chemin.append (const.left)
			if cur.y == cur.parent.y+1:
				chemin.append (const.up)
			if cur.y == cur.parent.y-1:
				chemin.append (const.down)
			cur = cur.parent
		
		ret = []
		id = len(chemin) - 1
		while id>=0:
			ret.append (self.laby.notdir(chemin[id]))
			id-=1
		return ret
		
	""" mise a jour de la position du personnage """
	def poll(self):
		if self.chemin:
			self.move (self.chemin.pop (0))
			
	""" permet de se deplacer selon le chemin """
	def aller(self,chemin):
		self.chemin = chemin
			
	"""
	algorithme de pathfinding Astar , desti represente le tuple de la destination
	"""
	def astar (self , desti):
		dest = Point(desti)
		liste_ouverte = []
		liste_fermee = []

		debut = self.laby.get_cell (self.x , self.y)
		liste_ouverte.append (debut)
		debut.F = distance ((self.x,self.y) , (dest.x,dest.y))
		debut.G = 0
		debut.H = debut.F
		debut.parent = None
		
		
		while 1:
			if len(liste_ouverte) <= 0 : 
				break
			min , min_id= liste_ouverte[0].F,0
					
			for id,v in enumerate(liste_ouverte[1:]):
				if v < min :
					min = v
					min_id = id +1
			liste_fermee.append (liste_ouverte[min_id])
			
			if liste_ouverte[min_id].x == dest.x and liste_ouverte[min_id].y == dest.y : break
			
			self.traitement (liste_fermee,liste_ouverte,liste_ouverte[min_id].x +  1 , liste_ouverte[min_id].y , const.right , liste_ouverte[min_id] , dest)
			self.traitement (liste_fermee,liste_ouverte,liste_ouverte[min_id].x -  1 , liste_ouverte[min_id].y , const.left , liste_ouverte[min_id] , dest)
			self.traitement (liste_fermee,liste_ouverte,liste_ouverte[min_id].x  , liste_ouverte[min_id].y-1 , const.up , liste_ouverte[min_id] , dest)
			self.traitement (liste_fermee,liste_ouverte,liste_ouverte[min_id].x  , liste_ouverte[min_id].y+1 , const.down , liste_ouverte[min_id] , dest)
			liste_ouverte.remove (liste_ouverte[min_id])

""" classe du labyrinthe """
class laby:
	def __init__ (self,w,h,sx=0,sy=0):
		self.w = w
		self.h = h
		self.cellules = []
		self.wc = const.wc
		self.hc = const.hc
		self.sx = sx
		self.sy = sy
		""" initialisation des cellules , pour chaque cellule , il initialise sa position dans le labyrinthe """
		for v in range(self.w*self.h):
			a = cellule()
			a.x = v % self.w
			a.y = v / self.w
			self.cellules.append (a)
	""" retourne la cellule correspondante à la position (x,y) """
	def get_cell (self,x,y):
		return self.cellules[x +  y * self.w]
	""" retourne direction de sens contraire d'une direction """
	def notdir(self,dir):
		if dir == const.right : return const.left
		if dir == const.left : return const.right
		if dir == const.up : return const.down
		if dir == const.down : return const.up
		
	""" generation du labyrinthe """
	def generate_laby (self,x=-1,y=-1):
		if x==-1:
			x = randint(0,self.w-1)
			y = randint(0,self.h-1)
		cell_act = self.get_cell (x,y)
		
		if not cell_act.state :
			cell_act.state = True
			tab = []
			if x+1<self.w and not self.get_cell(x+1,y).state : tab.append((x+1,y,const.right))
			if x-1>=0  and not self.get_cell(x-1,y).state    : tab.append((x-1,y,const.left))
			if y-1>=0  and not self.get_cell(x,y-1).state   : tab.append((x  ,y-1,const.up))
			if y+1<self.h and not self.get_cell(x,y+1).state : tab.append((x  ,y+1,const.down))
			if tab:
				
				while tab:
					C = choice (tab)
					if not self.get_cell(C[0] , C[1]).state:
						
						cell = self.get_cell (C[0] , C[1])
						cell_act.portes[C[2]] = False
						cell.portes[self.notdir(C[2])] = False
						self.generate_laby (C[0] , C[1])
					tab.remove (C)
				return True
			else : 
				return False
		
	""" affiche le labyrinthe """
	def show(self,buffer):
		W,H = self.wc , self.hc
		sx,sy = self.sx , self.sy
		for y in range(self.h-1):
			for x in range(self.w-1):
				c = self.get_cell (x,y)
				if c.portes[const.right] :
					pygame.draw.line (buffer , const.white , (sx+(x+1)*W,sy+y*H),(sx+(x+1)*W,sy+(y+1)*H))
				if c.portes[const.down] :
					pygame.draw.line (buffer , const.white , (sx+(x)*W,sy+(y+1)*H),(sx+(x+1)*W,sy+(y+1)*H))

		x = self.w - 1

		for y in range(self.h-1):
			c = self.get_cell (x , y)
			if c.portes[const.down]:
				pygame.draw.line (buffer , const.white , (sx+x*W,sy+(y+1)*H),(sx+(x+1)*W,sy+(y+1)*H) )

		y = self.h - 1
		for x in range(self.w-1):
			c = self.get_cell (x , y)
			if c.portes[const.right]:
				pygame.draw.line (buffer , const.white , (sx+(x+1)*W,sy+(y)*H),(sx+(x+1)*W,sy+(y+1)*H) )
		pygame.draw.rect (buffer , const.pink , (sx , sy , W * self.w , H * self.h),2)
		

if __name__ == "__main__":
	
	mylaby = laby(20,20) # création d'un labyrinthe de 20x20 cellules
	pygame.init ()
	pygame.display.set_mode ( (640,480))
	screen = pygame.display.get_surface ()
	
	mylaby.generate_laby() # géneration aléatoire du labyrinthe

	perso = Perso (mylaby)
	keys = None
	move_time = 0
	perso_time = 0

	while True:
		screen.fill (const.black)
		events = pygame.event.get ()
		
		for event in events:
		
			""" click de la souris """
			if event.type == MOUSEBUTTONDOWN:
				mx , my = pygame.mouse.get_pos ()
				x = (mx - mylaby.sx) / mylaby.wc
				y = (my - mylaby.sy) / mylaby.hc
				if x>=0 and y>=0 and x < mylaby.w and y < mylaby.h:
					perso.astar ((x,y)) # trouve le chemin avec astar
					chemin = perso.get_astar ((perso.x,perso.y) , (x,y)) # reçoit le chemin en tableau qui donne les directions à prendre sequentiellement
					perso.aller (chemin) # ordonne au personnage d'aller au chemin sans arrêter la boucle principale
					
		keys = pygame.key.get_pressed ()
		
		""" Mouvement du personnage """
		if pygame.time.get_ticks () - move_time >= const.time_perso:
			move_time = pygame.time.get_ticks ()	
			if keys[pygame.K_RIGHT]:
				perso.move (const.right)
			if keys[pygame.K_LEFT]:
				perso.move (const.left)
			if keys[pygame.K_UP]:
				perso.move (const.up)
			if keys[pygame.K_DOWN]:
				perso.move (const.down)
			
		if keys[pygame.K_ESCAPE] :
			break
			
		""" mise a jour de la position du personnage """
		if pygame.time.get_ticks () - perso_time >= const.time_perso_poll:
			perso_time = pygame.time.get_ticks ()
			perso.poll ()
			
		""" affichage """
		mylaby.show (screen)
		perso.show (screen)
		pygame.display.flip ()

Codes Sources

A voir également

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.