# --- Exercice 4 : Pathfinding sur grille avec A* ---
class GridProblem(Problem):
"""Problème de pathfinding sur une grille 2D avec obstacles.
La grille est une liste de listes d'entiers :
0 = cellule libre
1 = obstacle (mur)
L'état est un tuple (row, col).
Les déplacements sont les 4 directions cardinales (haut, bas, gauche, droite).
"""
DIRECTIONS = {
'H': (-1, 0), # Haut
'B': ( 1, 0), # Bas
'G': ( 0, -1), # Gauche
'D': ( 0, 1), # Droite
}
def __init__(self, grid, start, goal):
super().__init__(start, goal)
self.grid = grid
self.rows = len(grid)
self.cols = len(grid[0])
def actions(self, state):
"""Retourne les directions valides depuis state (sans sortir ni toucher un mur)."""
row, col = state
valid = []
for action, (dr, dc) in self.DIRECTIONS.items():
nr, nc = row + dr, col + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols and self.grid[nr][nc] == 0:
valid.append(action)
return valid
def result(self, state, action):
"""Retourne le nouvel état après déplacement."""
row, col = state
dr, dc = self.DIRECTIONS[action]
return (row + dr, col + dc)
def manhattan_distance_grid(state, goal):
"""Distance Manhattan pour la grille."""
return abs(state[0] - goal[0]) + abs(state[1] - goal[1])
def visualize_grid_search(grid, result, start, goal):
"""Visualise la grille avec le chemin et les nœuds explorés."""
rows, cols = len(grid), len(grid[0])
explored_set = set(result.explored_order)
path_set = set(result.path)
fig, ax = plt.subplots(figsize=(cols * 0.7 + 1, rows * 0.7 + 1))
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
color = '#2C3E50' # mur : gris foncé
elif (r, c) == start:
color = '#27AE60' # départ : vert
elif (r, c) == goal:
color = '#E74C3C' # arrivée : rouge
elif (r, c) in path_set:
color = '#F39C12' # chemin : orange
elif (r, c) in explored_set:
color = '#85C1E9' # exploré : bleu clair
else:
color = '#ECF0F1' # libre : blanc cassé
rect = plt.Rectangle((c, rows - 1 - r), 1, 1,
facecolor=color, edgecolor='#BDC3C7', linewidth=0.5)
ax.add_patch(rect)
# Légende
legend_items = [
mpatches.Patch(color='#27AE60', label='Départ'),
mpatches.Patch(color='#E74C3C', label='Arrivée'),
mpatches.Patch(color='#F39C12', label='Chemin'),
mpatches.Patch(color='#85C1E9', label='Exploré'),
mpatches.Patch(color='#2C3E50', label='Obstacle'),
]
ax.legend(handles=legend_items, loc='upper right', fontsize=8)
ax.set_xlim(0, cols)
ax.set_ylim(0, rows)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title(
f"A* sur grille {rows}×{cols} | "
f"Chemin : {len(result.path)-1} étapes | "
f"Nœuds explorés : {result.nodes_expanded}",
fontweight='bold'
)
plt.tight_layout()
plt.show()
# --- Grille de test ---
grid = [
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 1, 0],
[0, 0, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
start = (0, 0)
goal = (5, 7)
grid_problem = GridProblem(grid, start, goal)
h_grid = lambda s: manhattan_distance_grid(s, goal)
# Résoudre avec A*
result_grid = a_star_search(grid_problem, h_grid)
print(f"Problème de pathfinding sur une grille {len(grid)}×{len(grid[0])}")
print(f"Départ : {start}, Arrivée : {goal}")
print(f"Solution trouvée : {result_grid.found}")
if result_grid.found:
print(f"Longueur du chemin : {len(result_grid.path) - 1} étapes")
print(f"Nœuds explorés : {result_grid.nodes_expanded}")
print(f"Chemin : {result_grid.path}")
visualize_grid_search(grid, result_grid, start, goal)