Partage
  • Partager sur Facebook
  • Partager sur Twitter

Recursive Backtracking Labyrinthe (algo)

    4 janvier 2017 à 11:38:50

    Bonjour,

    J'essaye d'améliorer le jeu de labyrinthe proposé sur le cours de Pygame d'OC. Je trouvais dommage d'avoir à créer moi-même les maps alors que des algorithmes existent pour en générer automatiquement !

    J'ai donc parcouru certains threads du forum + d'autres sources, et je me suis arrêté sur l'algorithme du Recursive Backtracking.

    Je fais appel à vous pour savoir si mon implémentation en python est bonne. J'ai découpé mon code en deux entités (pour le moment, je me concentre uniquement sur la génération du labyrinthe : la gestion des personnages / ennemis / bonus viendra après), le "niveau", et les "cellules". En voici les codes :

    Niveau :

    # -*- coding: utf-8 -*-
    import os
    import pygame
    from entities.banana import *
    from entities.wall import *
    from entities.empty import *
    from entities.perso import *
    from entities.start import *
    from entities.ennemy import *
    from entities.cell import *
    from pygame.locals import *
    from random import *
    from constantes import background, wall, banana, start
    
    class Level_new():
    	"""Classe définissant un Labyrinthe généré automatiquement
    	grâce à l'algorithme du recursive backtracking. 
    	"""
    	step = 32
    	def __init__(self, size): #constructeur d'exercice : numéro de niveau
    		self.tuple_level = []
    		self.size = size
    		y = 0
    		while (y < self.size):
    			x=0
    			while (x < self.size):
    				self.tuple_level.append(Cell(x,y))
    				x = x + 1
    			y = y + 1
    
    	def go_in_cell_arround(self, current_cell, cells_around):
    		if not cells_around: #Si pas de cells autour
    			return current_cell #On reste sur la cell courante
    		for cell in cells_around:
    			next_cell = choice(cells_around)
    		if (next_cell.x_pos == current_cell.x_pos + 1):
    			current_cell.remove_wall('left', next_cell) #on pète les murs de gauche de la cellule + celui de droite de la prochaine
    		elif (next_cell.x_pos == current_cell.x_pos -1):
    			current_cell.remove_wall('right', next_cell) # idem
    		elif (next_cell.y_pos == current_cell.y_pos +1):
    			current_cell.remove_wall('down', next_cell) # idem bas et haut
    		else:
    			current_cell.remove_wall('up', next_cell) # idem haut et bas
    
    		next_cell.visited = True # on s'assure que la prochaine cellule soit marquée visitée
    		return next_cell
    
    	def visit_cells(self):
    		
    		starting_cell = self.tuple_level[3]#choice(self.tuple_level) # on choisit une cell pour commencer le laby
    		starting_cell.visited = True # on la rend visitée
    		path_visited = [starting_cell]
    		current_cell = starting_cell
    		current_index = self.tuple_level.index(current_cell)
    		print (path_visited)
    		while path_visited: # tant qu'on n'est pas revenu au point de départ
    			cells_around = []
    			if (current_cell.x_pos == 0 and current_cell.y_pos == 0): # pour en haut à gauche,  cases à côté = en à droite et en dessous
    				print ('cellule en haut à gauche')
    				cells_around = [self.tuple_level[current_index +1], self.tuple_level[current_index + self.size]]
    			elif (current_cell.x_pos == self.size -1 and current_cell.y_pos == self.size -1): # pour en bas à droite, à gauche et en haut
    				cells_around = [self.tuple_level[current_index - 1], self.tuple_level[current_index -self.size]]
    			elif (current_cell.x_pos == 0 and current_cell.y_pos == self.size -1): # pour en bas à gauche
    				cells_around == [self.tuple_level[current_index + 1], self.tuple_level[current_index -self.size]]
    			elif (current_cell.y_pos == 0 and current_cell.x_pos == self.size -1): # pour en haut à droite
    				print ('cellule en haut à droite')
    				cells_around = [self.tuple_level[current_index -1], self.tuple_level[current_index +self.size]]
    			elif (current_cell.x_pos == 0): # pour les murs de gauche
    				cells_around = [self.tuple_level[current_index +1], self.tuple_level[current_index + self.size], self.tuple_level[current_index - self.size]]
    			elif (current_cell.x_pos == self.size -1): # pour les murs de droite
    				cells_around = [self.tuple_level[current_index -1], self.tuple_level[current_index + self.size], self.tuple_level[current_index - self.size]]
    			elif (current_cell.y_pos == 0): # pour les murs d'en haut
    				cells_around = [self.tuple_level[current_index -1], self.tuple_level[current_index + 1], self.tuple_level[current_index + self.size]]
    			elif (current_cell.y_pos == self.size -1): # pour les murs d'en bas
    				cells_around = [self.tuple_level[current_index -1], self.tuple_level[current_index + 1], self.tuple_level[current_index - self.size]]
    			else:
    				print ('au milieu')
    				cells_around = [self.tuple_level[current_index -1], self.tuple_level[current_index +1], self.tuple_level[current_index + self.size], self.tuple_level[current_index - self.size]]
    			print ('current cell : X {}, Y {}'.format(current_cell.x_pos, current_cell.y_pos))
    				
    			for i, cell in enumerate(cells_around):
    				print ('position des cells à côté : X {}, y {} (num {} dans la liste). Visitée : {}'.format(cell.x_pos, cell.y_pos, i, cell.visited))
    				if (cell.visited == True): # si les cells autour ont déjà été visitées
    					cells_around.remove(cell) # on les supprime de la liste
    			next_cell = self.go_in_cell_arround(current_cell, cells_around) # on se déplace jusqu'à la prochaine cell qui devient la cell courante
    			next_index = self.tuple_level.index(next_cell)  #on modifie l'index de la cell courante
    			if path_visited[-1] == next_cell: # Si on est resté sur la même cell (pas de cells autour)
    				path_visited.pop() # On supprime cette cell du chemin
    				current_cell = path_visited[-1] # on prend la précédente cell en tant que cell courante
    				current_index = self.tuple_level.index(current_cell) #on modifie l'index
    			else: #si on a bien changé de cell
    				current_cell = next_cell #on prend la prochaine cell en tant que cell courante
    				current_index = next_index #et on modifie l'index
    				print ('nous sommes maintenant en X {}, Y {}'.format(current_cell.x_pos, current_cell.y_pos))
    		return self.tuple_level
    
    
    
    



    La cellule :

    # -*- coding: utf-8 -*-
    import os
    import pygame
    from entities.entity import *
    from pygame.locals import *
    from constantes import *
    
    class Cell(Entity):
    	"""Classe définissant une banane
    	"""
    
    	step = 32
    
    	def __init__(self, x_pos, y_pos): #constructeur de wall
    		Entity.__init__(self)
    		self.solid = False
    		self.wall_up = True
    		self.wall_down = True
    		self.wall_left = True
    		self.wall_right = True
    		self.visited = False
    
    
    	def remove_wall(direction, opposite_cell)
    		if direction == 'up':
    			self.wall_up = False
    			opposite_cell.wall_down = False
    		elif direction == 'down':
    			self.wall_down = False
    			opposite_cell.wall_up = False
    		elif direction == 'left':
    			self.wall_left = False
    			opposite_cell.wall_right = False
    		elif direction == 'right':
    			self.wall_right = False
    			opposite_cell.wall_left = False
    		return self
    
    
    

    J'ai donc un level qui contient un tableau de cells. Chaque cell a 7 attributs : la présence d'un mur en haut, bas, gauche ou droite, un statut (visitée ou non) et une position X et Y.

    Pour la création du labyrinthe (méthode visit_cells()) :

    • Je pars du principe que tous les murs sont fermés.
    • Je choisis une cellule au hasard
    • Je change son statut à "visité"
    • j'ajoute cette cellule au "chemin parcouru"
    • Tant que le "chemin parcouru" n'est pas vide :
    • je détermine quelles sont les cellules autour d'elle (en faisant attention aux bordures du niveau)
    • Je supprime de la liste des cellules alentour celles qui ont déjà été visitées
    • J'essaye de visiter une des cellules restantes
    • Si il n'y en a pas, je reste sur la cellule en cours ; je la supprime du chemin parcouru, et je recule d'une case
    • Si on a bien changé de cellule, la nouvelle cellule devient la cellule courante.

    Que pensez-vous de l'algorithme ? Y-a-t-il des choses à modifier dans celui-ci ?

    Il y a très probablement des erreurs de syntaxe liées au fonctionnement des classes (avec self) que j'essayerai de corriger par moi-même.

    Merci :ange:


    EDIT : une des choses qui me gêne personnellement, c'est qu'une cellule puisse détruire le mur d'une cellule adjacente : comment faire autrement ?

    EDIT 2 : correction de certaines erreur de syntaxe dans la méthode  visit_cells. J'ai un problème, ma méthode s'exécute bien, mais je finis par faire une boucle infinie et je navigue entre deux cellules en permanence ... Une idée ?

    EDIT 3 : après débug, j'ai réussi à obtenir un script qui me génère effectivement des labyrinthes "parfaits". Je reste ouvert à d'éventuelles remarques concernant la syntaxe ! (cf. fichiers modifiés par le dernier commit sur mon repo github).

    -
    Edité par ftassy 4 janvier 2017 à 16:25:24

    • Partager sur Facebook
    • Partager sur Twitter
    Avide de connaissances en électronique/dév/admin sys/db/réseau !

    Recursive Backtracking Labyrinthe (algo)

    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
    • Editeur
    • Markdown