O código do Solver
Quando a busca da página anterior termina, o dicionário
parents tem o pai de cada célula descoberta, mas o Solver ainda precisa devolver o
caminho em letras, e decidir o que fazer se a saída nunca foi descoberta. Esta página escreve
esses dois trechos e mostra o arquivo mazegen/services/solver.py inteiro.
A saída inalcançável
if goal not in parents:
raise MazeError("internal: exit is not reachable from entry")
Se a fila esvaziou sem descobrir a saída, não existe caminho até ela. raise interrompe o método
e levanta (lança) a exceção: ela sobe para quem chamou o método, que pode capturá-la com
try/except. Num labirinto gerado pelo MazeGenerator, isso não acontece:
- o
Carverliga todas as células livres (parte 6); - o
MazeGeneratorgarante que a saída fica dentro da grade e fora do "42".
Se acontecer, é bug. O começo internal: da mensagem é a convenção do MazeError para "defeito
do código, não erro de quem usa" (parte 4). A alternativa,
devolver uma string vazia em silêncio, esconderia o defeito e gravaria um arquivo de saída
errado.
A reconstrução
letters: list[str] = []
link = parents[goal]
while link is not None:
previous, side = link
letters.append(side.letter)
link = parents[previous]
letters.reverse()
return "".join(letters)
linkcomeça com o par(pai, lado)da saída e vai de pai em pai. O laço para noNoneda entrada.previous, side = linkdesmonta a tupla em duas variáveis: o pai e o lado do passo.side.letteré a letra do lado ("N","E","S"ou"W").letters.reverse()inverte a lista no lugar: as letras foram lidas da saída para a entrada."".join(letters)cola as letras numa string só, sem nada entre elas. Somar as strings uma a uma dentro do laço (texto = texto + letra) criaria uma string nova a cada passo.
O arquivo completo
"""Breadth-first search for the shortest path of a maze."""
from collections import deque
from mazegen.entities.maze import Maze
from mazegen.errors import MazeError
from mazegen.value_objects.cell import Cell
from mazegen.value_objects.direction import Direction
class Solver:
"""Find the shortest path between two cells through the passages."""
def shortest_path(self, maze: Maze, start: Cell, goal: Cell) -> str:
"""Search breadth-first, neighbours in the order N, E, S, W.
A cell keeps the parent of its first discovery, so equal-length
paths always resolve to the same letters.
Args:
maze: Carved maze.
start: Entry cell.
goal: Exit cell.
Returns:
One letter N, E, S or W per step from start to goal.
Raises:
MazeError: The goal is not reachable, which points to a bug.
"""
parents: dict[Cell, tuple[Cell, Direction] | None] = {start: None}
queue: deque[Cell] = deque([start])
while queue:
current = queue.popleft()
if current == goal:
break
for side in maze.passages(current):
nxt = current.step(side)
if nxt not in parents:
parents[nxt] = (current, side)
queue.append(nxt)
if goal not in parents:
raise MazeError("internal: exit is not reachable from entry")
letters: list[str] = []
link = parents[goal]
while link is not None:
previous, side = link
letters.append(side.letter)
link = parents[previous]
letters.reverse()
return "".join(letters)
Experimente
O exemplo da página 2: abre à mão todas as paredes internas de uma grade
3x2 (open_wall derruba uma parede, parte 5) e pede o caminho
nos dois sentidos. Depois, um Maze 2x1 com todas as paredes fechadas, sem passar pelo Carver:
a saída não tem como ser alcançada. Na raiz do projeto:
.venv/bin/python - <<'EOF'
from mazegen.entities.maze import Maze
from mazegen.errors import MazeError
from mazegen.services.solver import Solver
from mazegen.value_objects.cell import Cell
from mazegen.value_objects.direction import Direction
from mazegen.value_objects.pattern42 import Pattern42
maze = Maze(3, 2, Pattern42.for_size(3, 2))
for x in range(3):
maze.open_wall(Cell(x, 0), Direction.SOUTH)
if x < 2:
maze.open_wall(Cell(x, 0), Direction.EAST)
maze.open_wall(Cell(x, 1), Direction.EAST)
print(maze.rows())
print(Solver().shortest_path(maze, Cell(0, 0), Cell(2, 1)))
print(Solver().shortest_path(maze, Cell(2, 1), Cell(0, 0)))
closed = Maze(2, 1, Pattern42.for_size(2, 1))
try:
Solver().shortest_path(closed, Cell(0, 0), Cell(1, 0))
except MazeError as error:
print("MazeError:", error)
EOF
[[9, 1, 3], [12, 4, 6]]
EES
NWW
MazeError: internal: exit is not reachable from entry
maze.rows()mostra as paredes de cada célula como números (9= Norte e Oeste fechados, por exemplo, parte 1); só a borda ficou fechada.EESeNWWsão os caminhos do desempate: vizinhas na ordem N, E, S, W, e vale o pai da primeira descoberta.- No
Mazefechado, oSolverlevanta oMazeError. Otry/excepto captura só para imprimir a mensagem numa linha.