621 lines
68 KiB
Plaintext
621 lines
68 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "16f8fedb-ac10-450c-b5c7-f820a985902d",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"# Pathfinding demo\n",
|
|
"\n",
|
|
"Pathfinding is the task of finding shortest (or any) path from one point to the other [1]. Majority of practically used pathfinding methods are based on graph search, e.g. representing the map as a graph (e.g. grid of nodes covering the map) and finding (shortest) path between nodes on this graph. Edges between the nodes may have some cost, which represents length or difficulty of getting from one node to the other.\n",
|
|
"\n",
|
|
"## Brief overview of methods\n",
|
|
"\n",
|
|
"### Graph-based\n",
|
|
"\n",
|
|
"Before we can use graph-based search, we have to map the graph nodes and edges to the positions in the world. This may be as simple as creating regular grid, or more efficiently create nodes only in some critical points in the world map. We can also use 3D space (navmeshes in games: jumps, climbing, ...).\n",
|
|
"The less nodes there are, the faster the search. \n",
|
|
"\n",
|
|
"* Depth-first search\n",
|
|
"* Breadth-first search\n",
|
|
" * does not take the cost into account\n",
|
|
" * can be used to create flow fields and distance maps\n",
|
|
" * useful for efficiently calculating paths for many agents with one destination\n",
|
|
"* Dijkstra\n",
|
|
" * similar to BFS, but takes edge cost into account\n",
|
|
" * prioritizes search to the direction of lesser cost\n",
|
|
"* Greedy Best-First search (GBFS)\n",
|
|
" * similar to BFS, but uses some heuristic to prioritize search\n",
|
|
" * this may be e.g. manhattan distance to the destination\n",
|
|
" * may get a bit \"stuck\" if there are obstacles\n",
|
|
"* A*\n",
|
|
" * combines GBFS and Dijkstra\n",
|
|
" * priority is the sum of heuristic and the cost-so-far\n",
|
|
" * usually the best option \n",
|
|
"\n",
|
|
"There are many possible modifications and optimizations to these methods. Graph-based methods may also be combined with non-graph ones (e.g. having one unit in RTS search the path using A*, and all the other units within a group attracted to it using potential field).\n",
|
|
"\n",
|
|
"### Non-graph based\n",
|
|
"\n",
|
|
"* Gradient descent\n",
|
|
" * Optimizes paths by following the steepest descent in a potential field\n",
|
|
"* Potential field methods\n",
|
|
" * Simulate attractive forces toward goals and repulsive forces from obstacles\n",
|
|
"* Straight-line or Euclidean paths\n",
|
|
" * Simply connect points directly, often with collision checks; basic for open spaces but may require smoothing for obstacles.\n",
|
|
"\n",
|
|
"## Selecting a method\n",
|
|
"\n",
|
|
"Selecting a method depends on several factors:\n",
|
|
"\n",
|
|
"* use case\n",
|
|
" * what \"world\" are we navigating in: is it possible to represent it efficiently as a graph?\n",
|
|
" * if we do just one-off calculation, or if we intend to do that very often\n",
|
|
" * if we do \"one source, one destination\", \"one source, all destinations\", \"all sources, one destination\" or \"all sources, all destinations\" - see [this article](https://www.redblobgames.com/pathfinding/tower-defense/) \n",
|
|
" * if there are moving obstacles\n",
|
|
"* performance requirements: how fast we want to calculate the path, how often we do it\n",
|
|
"* memory requirements: some methods are more memory-intensive\n",
|
|
"\n",
|
|
"## Sources\n",
|
|
"\n",
|
|
"[[1] Wikipedia on pathfinding](https://en.wikipedia.org/wiki/Pathfinding)\n",
|
|
"\n",
|
|
"[Redblobgames](https://www.redblobgames.com/) has excellent set of articles about pathfinding and other related topics for game development.\n",
|
|
"\n",
|
|
"## List of implemented methods\n",
|
|
"\n",
|
|
"This Python demo implements few of the described methods.\n",
|
|
"\n",
|
|
"1. Breadth-first search\n",
|
|
"2. Dijkstra\n",
|
|
"3. Greedy Best-First search (GBFS)\n",
|
|
"4. A*\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "fbdf9d2c-d050-4744-b559-abc71e550725",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"#\n",
|
|
"# Imports\n",
|
|
"#\n",
|
|
"\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import numpy as np\n",
|
|
"import time\n",
|
|
"import random\n",
|
|
"from typing import Optional, NewType, Any\n",
|
|
"from abc import ABC, abstractmethod\n",
|
|
"from queue import Queue, PriorityQueue\n",
|
|
"from dataclasses import dataclass, field"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "c704cf15-95fa-49c1-af1b-c99f7b5c8b95",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"#\n",
|
|
"# Type and interfaces definition\n",
|
|
"#\n",
|
|
"\n",
|
|
"Point2D = NewType(\"Point2D\", tuple[int, int])\n",
|
|
"# type Point2D = tuple[int, int] # tuple(x, y)\n",
|
|
"type Path = list[Point2D]\n",
|
|
"\n",
|
|
"class Map:\n",
|
|
" \"\"\"\n",
|
|
" 2D map consisting of cells with given cost\n",
|
|
" \"\"\"\n",
|
|
" # array not defined as private, as plotting utilities work with it directly\n",
|
|
" array: np.ndarray\n",
|
|
" _visited_nodes: int\n",
|
|
"\n",
|
|
" def __init__(self, width: int, height: int) -> None:\n",
|
|
" assert width > 0\n",
|
|
" assert height > 0\n",
|
|
" rows = height\n",
|
|
" cols = width\n",
|
|
" self.array = np.zeros((rows, cols), dtype=np.float64)\n",
|
|
" self._visited_nodes = 0\n",
|
|
"\n",
|
|
" def Randomize(self, low: float = 0.0, high: float = 1.0) -> None:\n",
|
|
" self.array = np.random.uniform(low, high, self.array.shape)\n",
|
|
"\n",
|
|
" def IsPointValid(self, point: Point2D) -> bool:\n",
|
|
" x, y = point\n",
|
|
" y_max, x_max = self.array.shape\n",
|
|
" x_in_bounds = (0 <= x < x_max) \n",
|
|
" y_in_bounds = (0 <= y < y_max) \n",
|
|
" return x_in_bounds and y_in_bounds\n",
|
|
" \n",
|
|
" def GetNeighbours(self, center_point: Point2D) -> list[Point2D]:\n",
|
|
" \"\"\"\n",
|
|
" Get list of neighboring points (without actually visiting them)\n",
|
|
" \"\"\"\n",
|
|
" points: list[Point2D] = []\n",
|
|
" x_center, y_center = center_point\n",
|
|
" for x in range(-1,2):\n",
|
|
" for y in range(-1,2):\n",
|
|
" diagonal = x == y # includes center 0,0\n",
|
|
" if diagonal:\n",
|
|
" continue\n",
|
|
" p = Point2D((x + x_center, y + y_center))\n",
|
|
" if self.IsPointValid(p):\n",
|
|
" points.append(p)\n",
|
|
" return points\n",
|
|
" \n",
|
|
" def GetPointCost(self, point: Point2D) -> float:\n",
|
|
" x, y = point\n",
|
|
" row, col = y, x\n",
|
|
" return self.array[(row, col)]\n",
|
|
" \n",
|
|
" def GetPathCost(self, path: Path) -> float:\n",
|
|
" return sum([self.GetPointCost(p) for p in path])\n",
|
|
"\n",
|
|
" def ResetVisitedCount(self) -> None:\n",
|
|
" self._visited_nodes = 0\n",
|
|
"\n",
|
|
" def GetVisitedCount(self) -> int:\n",
|
|
" return self._visited_nodes\n",
|
|
"\n",
|
|
" def Visit(self, point: Point2D) -> float:\n",
|
|
" \"\"\"\n",
|
|
" Visit the node and return its cost\n",
|
|
" \"\"\"\n",
|
|
" if not self.IsPointValid(point):\n",
|
|
" raise ValueError(\"Point out of bounds\")\n",
|
|
" self._visited_nodes += 1\n",
|
|
" return self.GetPointCost(point)\n",
|
|
"\n",
|
|
" def CreateMaze(self, wall_probability: float = 0.3) -> None:\n",
|
|
" \"\"\"\n",
|
|
" Note: generated with Grok\n",
|
|
" Generate a simple maze on the map.\n",
|
|
" - Borders are set as walls (cost 1000).\n",
|
|
" - Internal cells are randomly set to 1 (path) or 1000 (wall) based on wall_probability.\n",
|
|
"\n",
|
|
" Args:\n",
|
|
" wall_probability (float): Probability (0-1) that an internal cell becomes a wall.\n",
|
|
" \"\"\"\n",
|
|
" rows, cols = self.array.shape\n",
|
|
"\n",
|
|
" # Set borders to walls (cost 1000)\n",
|
|
" self.array[0, :] = 1000 # Top row\n",
|
|
" self.array[-1, :] = 1000 # Bottom row\n",
|
|
" self.array[:, 0] = 1000 # Left column\n",
|
|
" self.array[:, -1] = 1000 # Right column\n",
|
|
"\n",
|
|
" # Set internal cells randomly\n",
|
|
" for y in range(1, rows - 1): # Skip borders\n",
|
|
" for x in range(1, cols - 1):\n",
|
|
" if random.random() < wall_probability:\n",
|
|
" self.array[y, x] = 1000 # Wall\n",
|
|
" else:\n",
|
|
" self.array[y, x] = 1 # Normal tile\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "043a1f1c-a7a7-4f24-b69c-c6c809830111",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"#\n",
|
|
"# Drawing utilities\n",
|
|
"#\n",
|
|
"\n",
|
|
"class Visualizer:\n",
|
|
" _axes: Optional[plt.Axes]\n",
|
|
" #_cmap: plt.Colormap\n",
|
|
" _cmap_counter: int\n",
|
|
"\n",
|
|
" def __init__(self):\n",
|
|
" self._axes = None\n",
|
|
" self._cmap = plt.get_cmap('tab10')\n",
|
|
" self._cmap_counter = 0\n",
|
|
"\n",
|
|
" def DrawMap(self, m: Map):\n",
|
|
" M, N = m.array.shape\n",
|
|
" _, ax = plt.subplots()\n",
|
|
" ax.imshow(m.array, cmap='gist_earth', origin='lower', interpolation='none')\n",
|
|
" self._axes = ax\n",
|
|
"\n",
|
|
" def DrawPath(self, path: Path, label: str = \"Path\"):\n",
|
|
"\n",
|
|
" \"\"\"\n",
|
|
" Draw path on a map. Note that DrawMap has to be called first\n",
|
|
" \"\"\"\n",
|
|
" assert self._axes is not None, \"DrawMap must be called first\"\n",
|
|
" xs, ys = zip(*path)\n",
|
|
" color = self._cmap(self._cmap_counter)\n",
|
|
" self._cmap_counter += 1\n",
|
|
" self._axes.plot(xs, ys, 'o-', color=color, label=label)\n",
|
|
" self._axes.plot(xs[0], ys[0], 'o', color='lime', markersize=8) # starting point\n",
|
|
" self._axes.plot(xs[-1], ys[-1], 'o', color='magenta', markersize=8) # end point\n",
|
|
" self._axes.legend()\n",
|
|
"\n",
|
|
"\n",
|
|
"#\n",
|
|
"# Utilities and helper classes\n",
|
|
"#\n",
|
|
"\n",
|
|
"@dataclass(order=True)\n",
|
|
"class PrioritizedItem:\n",
|
|
" \"\"\"\n",
|
|
" Helper class for wrapping items in the PriorityQueue,\n",
|
|
" so that it can compare items with priority\n",
|
|
" \"\"\"\n",
|
|
" item: Any = field(compare=False)\n",
|
|
" priority: float\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "859c64f4-e65c-4905-a775-c6f17542eac8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#\n",
|
|
"# Pathfinding implementations\n",
|
|
"#\n",
|
|
"\n",
|
|
"class PathFinderBase(ABC):\n",
|
|
" name: str\n",
|
|
" _map: Optional[Map]\n",
|
|
" _elapsed_time_ns: int\n",
|
|
" _visited_node_count: int\n",
|
|
"\n",
|
|
" def __init__(self) -> None:\n",
|
|
" self._map = None\n",
|
|
" self._elapsed_time_ns = 0\n",
|
|
" self._visited_node_count = 0\n",
|
|
"\n",
|
|
"\n",
|
|
" def SetMap(self, m: Map) -> None:\n",
|
|
" self._map = m\n",
|
|
"\n",
|
|
" def CalculatePath(self, start: Point2D, end: Point2D) -> Optional[Path]:\n",
|
|
" \"\"\"\n",
|
|
" Calculate path on a given map.\n",
|
|
" Note: map must be set first using SetMap\n",
|
|
" \"\"\"\n",
|
|
" assert self._map is not None, \"SetMap must be called first\"\n",
|
|
" self._map.ResetVisitedCount()\n",
|
|
" start_time = time.perf_counter_ns()\n",
|
|
" res = self._CalculatePath(start, end)\n",
|
|
" stop_time = time.perf_counter_ns()\n",
|
|
" self._elapsed_time_ns = stop_time - start_time\n",
|
|
" self._visited_node_count = self._map.GetVisitedCount()\n",
|
|
" return res\n",
|
|
"\n",
|
|
" @abstractmethod\n",
|
|
" def _CalculatePath(self, start: Point2D, end: Point2D) -> Optional[Path]:\n",
|
|
" \"\"\"\n",
|
|
" This method must be implemented by the derived classes\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" def GetStats(self) -> tuple[int, int]:\n",
|
|
" \"\"\"\n",
|
|
" Return performance stats for the last calculation:\n",
|
|
" - elapsed time in nanoseconds,\n",
|
|
" - number of visited nodes during search\n",
|
|
" \"\"\"\n",
|
|
" return self._elapsed_time_ns, self._visited_node_count\n",
|
|
"\n",
|
|
"\n",
|
|
"class BFS(PathFinderBase):\n",
|
|
" \"\"\"\n",
|
|
" Iterative breadth-first search\n",
|
|
" Finds optimal path and creates flow-field, does not take the node cost into account.\n",
|
|
" This would be good match for static maps with lots of agents with one\n",
|
|
" destination.\n",
|
|
" Compared to A*, this is more computationally expensive if we only want\n",
|
|
" to find path for one agent.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" name = \"Breadth First Search\"\n",
|
|
" # flow field and distance map\n",
|
|
" _came_from: dict[Point2D, Point2D]\n",
|
|
" _distance: dict[Point2D, float]\n",
|
|
"\n",
|
|
" def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]:\n",
|
|
" frontier: Queue[Point2D] = Queue()\n",
|
|
" frontier.put(start_point)\n",
|
|
" self._came_from: dict[Point2D, Optional[Point2D]] = { start_point: None }\n",
|
|
" self._distance: dict[Point2D, float] = { start_point: 0.0 }\n",
|
|
"\n",
|
|
" # build flow field\n",
|
|
" early_exit = False\n",
|
|
" while not frontier.empty() and not early_exit:\n",
|
|
" current = frontier.get()\n",
|
|
" for next_point in self._map.GetNeighbours(current):\n",
|
|
" if next_point not in self._came_from:\n",
|
|
" frontier.put(next_point)\n",
|
|
" self._distance[next_point] = self._distance[current] + 1.0\n",
|
|
" _ = self._map.Visit(next_point) # visit only to track visited node count\n",
|
|
" self._came_from[next_point] = current\n",
|
|
" if next_point == end_point:\n",
|
|
" # early exit - if you want to build the whole flow field, remove this\n",
|
|
" early_exit = True\n",
|
|
" break\n",
|
|
" # find actual path\n",
|
|
" path: Path = []\n",
|
|
" current = end_point\n",
|
|
" path.append(current)\n",
|
|
" while self._came_from[current] is not None:\n",
|
|
" current = self._came_from[current]\n",
|
|
" path.append(current)\n",
|
|
" path.reverse()\n",
|
|
" return path\n",
|
|
"\n",
|
|
"\n",
|
|
"class DijkstraAlgorithm(PathFinderBase):\n",
|
|
" \"\"\"\n",
|
|
" Dijsktra's algorithm (Uniform Cost Search)\n",
|
|
" Like BFS, but takes into account cost of nodes\n",
|
|
" (priority for the search being the distance from the start)\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" name = \"Dijkstra's Algorithm\"\n",
|
|
"\n",
|
|
" def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]:\n",
|
|
" frontier: PriorityQueue[PrioritizedItem] = PriorityQueue()\n",
|
|
" came_from: dict[Point2D, Optional[Point2D]] = {start_point: None}\n",
|
|
" cost_so_far: dict[Point2D, float] = {start_point: 0.0}\n",
|
|
"\n",
|
|
" frontier.put(PrioritizedItem(start_point, 0.0))\n",
|
|
" while not frontier.empty():\n",
|
|
" current = frontier.get().item\n",
|
|
" if current == end_point:\n",
|
|
" # early exit - remove if you want to build the whole flow map\n",
|
|
" break\n",
|
|
" for next_point in self._map.GetNeighbours(current):\n",
|
|
" new_cost = cost_so_far[current] + self._map.Visit(next_point)\n",
|
|
" if next_point not in cost_so_far or new_cost < cost_so_far[next_point]:\n",
|
|
" cost_so_far[next_point] = new_cost\n",
|
|
" priority = new_cost\n",
|
|
" frontier.put(PrioritizedItem(next_point, priority))\n",
|
|
" came_from[next_point] = current\n",
|
|
" # build the actual path\n",
|
|
" path: Path = []\n",
|
|
" current = end_point\n",
|
|
" path.append(current)\n",
|
|
" while came_from[current] is not None:\n",
|
|
" current = came_from[current]\n",
|
|
" path.append(current)\n",
|
|
" path.reverse()\n",
|
|
" return path\n",
|
|
" \n",
|
|
"\n",
|
|
"class GBFS(PathFinderBase):\n",
|
|
" \"\"\"\n",
|
|
" Like Dijsktra's Algorithm, but uses some heuristic as a priority \n",
|
|
" instead of the cost of the node\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" name = \"Greedy Best First Search\"\n",
|
|
"\n",
|
|
" @staticmethod\n",
|
|
" def heuristic(a: Point2D, b: Point2D) -> float:\n",
|
|
" x_a, y_a = a\n",
|
|
" x_b, y_b = b\n",
|
|
" return abs(x_a - x_b) + abs(y_a - y_b)\n",
|
|
"\n",
|
|
" def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]:\n",
|
|
" frontier: PriorityQueue[PrioritizedItem] = PriorityQueue()\n",
|
|
" came_from: dict[Point2D, Optional[Point2D]] = {start_point: None}\n",
|
|
" \n",
|
|
" frontier.put(PrioritizedItem(start_point, 0.0))\n",
|
|
" # create the flow field\n",
|
|
" while not frontier.empty():\n",
|
|
" current = frontier.get().item\n",
|
|
" if current == end_point:\n",
|
|
" # early exit\n",
|
|
" break\n",
|
|
" for next_point in self._map.GetNeighbours(current):\n",
|
|
" if next_point not in came_from:\n",
|
|
" priority = self.heuristic(end_point, next_point)\n",
|
|
" frontier.put(PrioritizedItem(next_point, priority))\n",
|
|
" _ = self._map.Visit(next_point) # visit only to track visited node count\n",
|
|
" came_from[next_point] = current\n",
|
|
" # create the actual path\n",
|
|
" path: Path = [end_point]\n",
|
|
" while came_from[current] is not None:\n",
|
|
" current = came_from[current]\n",
|
|
" path.append(current)\n",
|
|
" path.reverse()\n",
|
|
" return path\n",
|
|
"\n",
|
|
"\n",
|
|
"class A_star(PathFinderBase):\n",
|
|
" \"\"\"\n",
|
|
" Combines Dijsktra's Algorithm and GBFS:\n",
|
|
" priority is the sum of the heuristic and distance from the start\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" name = \"A*\"\n",
|
|
"\n",
|
|
" @staticmethod\n",
|
|
" def heuristic(a: Point2D, b: Point2D) -> float:\n",
|
|
" x_a, y_a = a\n",
|
|
" x_b, y_b = b\n",
|
|
" return abs(x_a - x_b) + abs(y_a - y_b)\n",
|
|
"\n",
|
|
" def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]:\n",
|
|
" frontier: PriorityQueue[PrioritizedItem] = PriorityQueue()\n",
|
|
" came_from: dict[Point2D, Optional[Point2D]] = { start_point: None }\n",
|
|
" cost_so_far: dict[Point2D, float] = { start_point: 0.0 }\n",
|
|
"\n",
|
|
" frontier.put(PrioritizedItem(start_point, 0.0))\n",
|
|
" while not frontier.empty():\n",
|
|
" current = frontier.get().item\n",
|
|
" if current == end_point:\n",
|
|
" # early exit\n",
|
|
" break\n",
|
|
" for next_point in self._map.GetNeighbours(current):\n",
|
|
" new_cost = cost_so_far[current] + self._map.Visit(next_point)\n",
|
|
" if next_point not in cost_so_far or new_cost < cost_so_far[next_point]:\n",
|
|
" cost_so_far[next_point] = new_cost\n",
|
|
" priority = new_cost + self.heuristic(end_point, next_point)\n",
|
|
" frontier.put(PrioritizedItem(next_point, priority))\n",
|
|
" came_from[next_point] = current\n",
|
|
" # create the actual path\n",
|
|
" path: Path = [end_point]\n",
|
|
" current = end_point\n",
|
|
" while came_from[current] is not None:\n",
|
|
" current = came_from[current]\n",
|
|
" path.append(current)\n",
|
|
" path.reverse()\n",
|
|
" return path\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "ece3a6c8-aa1d-49a8-9f4c-06ebff72f991",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Breadth First Search : took 2.202 ms, visited 595 nodes, cost 31016.00\n",
|
|
"Dijkstra's Algorithm : took 2.658 ms, visited 2020 nodes, cost 2048.00\n",
|
|
"Greedy Best First Search: took 0.327 ms, visited 109 nodes, cost 31016.00\n",
|
|
"A* : took 1.477 ms, visited 1066 nodes, cost 2048.00\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"image/png": "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PPzwwwQHB7Nq1SqWLVvGP//5T64dO5affvqJ226/nWZhYfYa8IiICF5/7TUSEhLYvXs3t91+OxEREdz3z38C8P777zN37lyWLlnCOS1a8Pnnn/Pee++RkJAAwMSJEzlw4ACnTp2y1wKZTCb7pGEvvPAC06dPp3Xr1rzwwgs89NBDrFy50me1P9Wv+fHHH6esrIxly5ZhNBo5cOAAoaGhxMbGsnDhQu6//34+/PBDmjVr5nIekZYtW/Ljjz+SnZ1NfHy802Oq3tvk5GTS09N5/PHHCQ0NZfjw4YAt8Xr44YeJjo5m//79zJ8/n2bNmnHzzTfby9m/fz8ffPABH37wAXq9HqvVylVXX01hYSFvvfkmHTp0YO/evQ7zmJw+fZqnnnqKN994A51Ox00338w///lPli9f7pN7qiVNs45QCCF8qKQcJn6c593BH693+7AK5BSU0PXf7o+rtHxUDMZavBN/++23XHLJJQAUFxcTFRXFf/7znxrrIV1//fVcfvnl9u1XX32VGTNmcPnll9OuXTvatWvH3n37WPbyy/aE5aE5c+zHt23blpnp6bz//vv2hOWZZ5/llltuYcqUKez48Uf+/ve/8/3339trWcLCwggJCaGsrMxpk83EiRPp378/ALfddhvjx4/n8OHDtHUz0+vSpUt58cUX7dt33HEH119/vdNjq19zTk4Ol19+OR07dgSgVatW9scqE7wWLVq47cMydepU7rvvPkaOHEmbNm244IILSE1N5YorrrDf86r3FiAxMZEDBw6wcuVKe8Lyt7/9zV5mQkICBw8eZN26dQ4JS2lpKW+++SbR0dGArT/n999/z95ffqFz584AtG/f3iG+srIyXnjhBTp06ADAnXfeyaOPPuryepoySVgCUH3XAYL6rwXkixi0wF9rIp2N5DXlG927d+eBBx4AbGuwfPDBB0yfPp20tDSHGoCUlDMTWp44cYLc3FweffRR5s+fb99fXl5OeHg4OypquL/++mveeecdDh06RHFxMeXl5TRr1sz++J49e7jyyivt2wAXXHAB27d71zxemTgADLriCgCio6LofvHFTo8PDg7mn//8J5OrdG6NioqiefPmAHRo357uF19My4qRMqNHjXIo675//pM77ryTPXv2cMUVVzB2zBi6du0KQKGXk41GRUXx2muvsX//fn788Ud+/vln5s2bxyeffMKzzz5Lfn6+23tbydW9BdhRUYMTFxdH5qFDZFZM07H2s8+IiYmhsKjI4Z5Xyjh4EKPRaE9WAOLj4sjL8zL5bmIkYRFCnPVC9LaaDm+UmVpzyxs7PB73+qTu9GpXc8jpTzt31fjdtREaGuowUWaXLl0YOHAgH3/8sUO/iqpNHNaKJuw5c+Zw/vnnO5RXWUuwe/du5syZw2233UafPn0IDw/nq6++YsWKFbUL0I2qTT+VzWBWD83rUVFRDomOO5UJQKUpU6Zw5ZVXsnbtWtatW8cTTzzBokWLuPuuu2oZuS3Z6tixI+PGjWPnzp1MnTqVH3/8kXbt2gG+ubfVm6VCQkI8xlW9OU1RFHy8prFmSMIihDjrKYridbNMn07RxEUayS0wO+3HogBxJiMDOkU77cNiNPh22LOiKOh0Oszmmou0VmrZsiUxMTFkZWUxbNgwp8fs2rWLuLg4br31Vvu+6nNktW3blt27d3P11Vfb9+3Zs8fhmKCgIMrLtdOnqnXr1kybNo1p06Yx+8EHeeWVV7j7rrsIrqixq0uslUlKcXGxz+6tMx07diQvL4+DBw/aB66czSRhEUKIWtDrFB4Zfi53vP0TCjgkLZWpyMNXn+u3+VhKS0vty5YUFhby/vvvc/r0aXu/FlemTp3KokWLaNasGf369aOsrIy9e/dSWFjIhAkTaN26NTk5OXz11VekpKTw7bff1piY7frrr2fevHmkpKTQrVs3vvjiC/788097p1uw9c/43//+R0ZGBs2bN3doFmloM+65h2FDh9K5c2dOnDjBxo0bObdLFwCSkpJQFIVvv/2W1NRUQkJC7OvdVPXEE08QFRVFz549iYmJ4dixY7z22mucc845XHDBBYBv7q0z3bt356KLLuL+++/nnnvuoVWrVmRkZKAoCv369fPpvWoKJGHxVpUObaa+yZzYsFtGCjWSyo+Bc/R6eoaGsaP4dJObME6n2BYyjI9QyC5U2ZJZXuvJ3io/D0MNcGmSvk5liLoZen4cz994Uc15WExGHr7av/OwbNu2zf5NvlmzZiQlJfHEE0/QvXt3t+eNGjUKo9HIW2+9xZIlSwgNDaVDhw7ccMMNgG205o033siTTz5JWVkZqamp3Hrrrbz88sv2MoYMGUJWVhZLliyhtLSUgQMHMnbsWLZt2+bwe3bs2MGkSZM4ffo0L774osvRNf5WXl7OXXffzeHDh4mMjGTolVfy9NNPA7aOsXPnzuXZZ5/l3//+N1dddZXToc29evXi008/5aOPPiI/P5/mzZtzwQUX8Pzzz9v70vji3rqycOFCnnnmGebMmYPZbKZVq1bcVYcmrUCgqAHS2FVQUIDJZOLEsWP1mv3QWce+qBG2ieN0VSaOs5aEk7fG4pe5WLTQOVELMTgzKDy8xsRx2WVlfps4zh+dbkd3MfDMUCOtTWeS4EP5VqbXYtK30V0MFRPH1b2Mhqal11RSUhIvvviix8nHnKnaqbMuM9066zx5NnLV0bahBcLz4Yt7Wd/74CkGs9nMgYwM2rZuXaOvTkFBAedERZGfn+/289vvawk1dVEjehA7xowS5PhhqAQXETvGTNSIHo0U2dlnUHg4ixMSaa537KUYazCwOCGRQY1Y9eyt0V0MfDgulMRIxw+1xEiFD8eFMrqL50rPyjJimtW9DOEbep1Cn/YtGdktgT7tnc+7IoTwDXlnc0enI2a47RZVn9dJUWzrCcUMD+LYWp00D/mZDpgdEwvUnGRLpyhYVZXZMbFsKCrSbPOQToFnhhor/u38Gp67yshvx0+7bNrRKfDcVe7LWDzUyCfpRdI8JIQIKJKwuNE8NQVdiOtmBkUBJaSQ5qkpnNyyx+Vxov66h4YR72ZdDZ2iEB8URPfQMM3Ofjugjd6hGag6naIQH6Gw54661xTpFIU2JoUBbfQy+60QIqBIwuJGUHQz4JSXxwl/ijZ4N1mFt8c1hvgI75oLCsxWl9PEh+gh0ui5Jdfb3yWEEE2FJCxulB31nKzU5jhRd0ct3tUWeHtcY8gu9K6NZuS7xS5rRy5N0rNpsucE2dvfJYQQTYV0unXj5Na9WEvCcTWOSlXBWhLBya17Gzaws9CO4tNkl5VhdfFkWFWV7LIydmi0OQhgS2Y5h/Ktbq8hM9/KlkzXSZcvyhBCiKZIEhZ3rFby1tiGiFb/fKjczltTJh1uG4AVWJCXC1Bj2unKD+8Febma7XALttWVp39hrvi382uY8YXZbWdZX5QhhBBNkSQsHhxbvZ3clUbUMseOkGppBLkrjX6Zh0U4t76oiBlHsjhZbSrtXIuFGUey/DIPi6+t+tXCte8Xk1XgmFEcLlC59v1ir+ZQ8UUZQgjR1EgfFi8cW72dvzaFkbLEtqx31nI9JzZ8JzUrjWB9UREKOTyT2Io/S0qYl5vb5Ga6XfWrhU/Si9j2t2b0StQzf0sJD28sqVWtSGUZ9Z0tVwghmgqpYfFWleQkf1u6JCuNqPIz+UR5OT80sWSlklWFv4ptV/LrMWudEg2rCpsPlvPuHgubD0qyIhz17NnTYb2aqttHjhyhZ8+epKenN05wDSwjIwOdXs/OnTv9/ru8vbe33347Tz31lN/jCSSSsAghRF1ZyyHjW9jzoe3/Vv92dp47dy49e/akZ8+e9OnThyuvvJI777yTTz/9FGu1L1Gff/65wwJ51bfrY+TIkbz99ts+KQugXfv2Xi0G6M7hw4cJMRo5NyXFN0HVUWxsLJ9//jkdOnQAYMeOHfTs2ZPCwsJGjSsQSJOQcKq+67b4Yw2exqCFGAJFoLymKtdcaZ69hTa/PEew+aj9sVJjNJnn3cnJ+AEuz6/Pui9RLVsy9MormT5jBlarlb/++ott27bx1FNP8fXXX/PUU09hqFhnq/o6SXVZN6k+ysvLURQFnc759+Kqa9eUlpby2++/E1HLdeCq3su0N95g3HXX8c2WLWzdupXU1NS6BV5LVWMoLS0luNprrLCgAIALu3WzL5YIEBERQUxMjCbWAQLtrO3kjtSwCCFELTXP3kKHHXMJqpKsAASZj9Jhx1yaZ2/x2+8OCQkhKiqKmJgYunTpwi233MKiRYv473//y5o1a+zHuWsSqs5qtfLYY48xduxYsrOzAVi2bBnDhw+nX79+DBs2jEWLFgG2pozs7Gz+85//2Gt7AFavXs3AgQPZsmUL48aNIzU1lezsbH755RfuvPNOBg0axGWXXcZtt93Gr7/+6vL6ysrKePLJJxk6dCipqamMHDmS119/3e09UVWVtLQ0Jk6cyA033MCrr73m8T5++umndE5Opn///kybNo01a9bUqAnZsGED48aNo1+/fowcOZLly5c7lDFy5Egemz+fW265hebnnMNtt93m0PyUkZHB5VdcAUCLli3R6fXccsstDvf9vvvvp2VUFPEJCcydN8+hfJ1ez0svvcSIESNoFh5OynnnsW3bNvbv38/Ayy8nPCKCfqmpHD582OP1BgKpYRFCCFVFV2728thy2uxZCkD1+YQVbH2s2vyylIKoi0BxMvNyabWJJoPCai5WVks9e/akU6dObNy4kVGjRtXq3LKyMh566CEOHz7Myy+/TIsWLfj66695++23mT9/Ph06dOD48eP89ttvADz55JPceOONjB49usbvMpvNpKWlMWfOHEwmEy1atODIkSNcffXVzJo1C4AVK1Ywffp0Vq5cSbNmNSdBfPfdd/nmm29YsGABcXFx5Obmkpub6/YaNm7cyOnTpxk0aBCtWrWiT9++PLN4MREREU6Pz8jI4Lpx4/jHP/5Bnz59+O2333jmmWccjtm3bx+zZ89m6tSpDB48mJ9//pmFCxdiMpkYMWKE/bhFixbx0EMPMWfOnBq/p3Xr1nz4wQdce911/LpvH5GRkYSGhtoff/PNN7nnnnv437ZtbNu2jVtuvZXUfv0YPHiw/ZjH5s/nqUWLeOqpp3jggQeYMHEi7du354H776dNmzb8bcoUnnzySZ599lm39ygQSMLiJYNeR8pBK+cUQe9L2vLGV79hKfe+u6dBgcltw0kKNXCw2EJaRhEW6SRZJ5Vv7efo9fQMDWtyo4TAtohhi1DblXSJ0qFTkE6zjUhXbubiL4b7pCwFCDYf4+Ivr3F+wBeOm+oDhyG4/st7tG3blv3799fqnOLiYmbMmEFJSQkvvfQS4RUrnufk5NCyZUt69+6NwWAgLi6O8847DwCTyYRerycsLKxGM5PFYuH++++nc+fO9n2VNTCVZs+ezfr16/nxxx8ZMMDWdPbpp5/aH8/NzaV169ZceOGFKIpCfHy8x+t47bXXGD9+PHq9nvPOO4+OHTvy3nvvMWXKFKfHv/jSSyQnJ/N/Tz7Jjh9/pG3btvzxxx+8VqVmZsWKFfTs2dNeRlJSEgcOHOCtt95ySFguHziQWffea9/OyMiw/1uv19OiRQsAYmJiHJqEALp27cojDz8MQKdOnXju+ef5esMGh4Rl8uTJjBs3DoD77ruPfqmpPDRnDldeeSUA/7j7bm659VaP9ygQSJOQF+4bkcLGTm2Y+7aV6Z9aufdP2HxhZ+4b4V3nrjnJJnZ37cjM4FaMLY9jZnArdnftyJxkk58jDzyDwsOZGxsHQPuQEN5o04Z17TswKLzuCwY2tNFdDGRMD6dXou3b95wBIWRMD2d0F/n+IOqu+oSK3pgzZw7FxcUsXbrUnqwAXHHFFZSUlHDNNdfw2GOPsXHjRiwWz/P7BAUF0alTJ4d9f/31FwsWLGDs2LFcdtllDBw4kNOnT5OTk+O0jOHDh/P7779z7bXXsmjRIv73v/+5/Z0nT55k5apVTJwwwb5vwoQJvJ6W5vKc39LT6dGjh8O+lGqddTMyMujWrZvDvm7dunHo0CHKq8wF1b1aObVxwQUXOGzHx8WRl5fnsK9rlWNiY2NrnBcbG0tJSQlFTWAeqvqSd0gP7huRwqTfan5/NxVh2z8ihSdXu56af06yiQnEoVarbVbNeiYQB8kwPz3f12EHpEHh4SxOSKxRDR9rMLA4IbFJTB43uouBD8eF1tifGKnw4bhQmfitkVj1Rn4cusbzgUD48Z/p/MODHo/7refjFLXsWmP/RRde6LgjKMyr3+tJRkYGiYmJtTonNTWVzz//nN27dzvUhMTFxfHhhx/y3Xff8cMPP7Bw4ULeeustli1bZu/U60xISAhKteatefPmceLECWbOnElcXBzBwcHceuutlJWVOS2jS5cufPzxx/z3v//l+++/Z/bs2fTq1YuFCxc6Pf7td97BbDbTp29f+z5VVbFarezdu7dGIlL5ePU4nR3jzT5nzVreCqq2Ar2iKKjVRntVPaYyZmf76pKwNjVSw+KGQa/jmiO2F0/1l7YOW1v1yGwrBr3z22hQYKIxumLLWWu37XGDLKzrkQ6YHWP7dlH9jUZXsT07JlbTL2idAs8MNVb82/k1LB5qRCevh4anKFgNoV79FMT0oNQYjauPBxUoMUZTENPD6fkEN3P8qWf/FYAffvjB1hFz4MBanTd27Fjuuusu7r33Xnbs2OHwmNFo5NJLL2XWrFm8+OKL7N69297kFBQUVGMYtSs7d+7k+uuvJzU1lQ4dOhAUFMTJkyfdnhMeHs6QIUN46KGHePzxx9mwYQP5+c6/2L322mvMnDmTn3780f6z86efGDhwIK+56Kyb3KUL27c7zlK+d6/jF8927dqxa9cuh30///wzbdq0Qa/3flX4ylFD5eWyvld9SQ2LG5MHduScQ64f1wEtCmFTn/ZYS2t22GuGHrXA3S1WUM0GJrcN55UD2q4ZaGzdQ8OIr/ZtpCqdohAfFET30DB+0OgCiAPa6Gltcp1S6RSFNiaFAW30LldrFhqg6Mk870467JiLiuNXkcok5tB5dzrvcOsDJSUlHDt2zGFYc1paGv379+fqq6+udXnjx4+nvLycmTNn8swzz3DhhReyevVqrFYr5513Hkajkc8//5yQkBDi4mzNsfHx8fz0008MGTKE4ODgGn0zqmrVqhWfffYZ5557LqdOneLZZ58lJCTE5fFvv/02UVFRdO7cGUVR+Prrr2nZsqXTDrQ7d+7kxx9/ZPlbb9GlSxeHx64fP56H/vUvFjz+eI3zbr/tNv7zn/9w/wMP0Lt3b3777Tf7CKvKL0QTJ05k0qRJvPLKKwwePJjdu3fz/vvvc//993u8p1UlJSWhKApr1qzhqquuIjQ01KH5TXhPy19IG11cM+/yuRbHDEQVhNf4CS2oWfXvTFKo5I2eRBu8e/P39rjGEB/h3Tdpb48Tjedk/AD+6D6XMnsNqk2pMZo/us91Ow9LfX3x5ZcMGzaMkSNH8o9//IPt27dz77338tRTT9Xqm39VN954I7fddhszZsxg165dRERE8PHHHzNlyhRuvPFGfvjhB55++ml7YlI5tHn06NEOHUSdefjhhykoKGDixIk88sgjjB8/3t4R1ZnQ0FDeeOMNbr75ZiZNmsSRI0d45plnnM7n8uprr5GSklIjWQEYNWoUf/31F6tXr67xWLt27fjg/fdZtWoVN954Ix999BG3VnRcrWxu6dKlCwsWLOCrr77i+uuv56WXXuL222936HDrjcTERObOncvsBx8kLj6eu+++u1bnizMUNUAavgoKCjCZTJw4dozIWk4+VFXViaWmDOrMzEOec7rfE0r560TNWQxbBQWRWOA5lqdLDzvUsNR3giwtTHbm60m+eoaG8UabNh7PmZSZqdkalkuT9Gya7Lm9+7K0U1LD4oQvX1NJSUm8+OKL9Z9MTS0n4vhugkr+oiykBYUtL/BYs9IYE4WVlpaSmprK0qVL6d27d71/v1b48l6+9tprfPTRR6xdu7bBY6ivpjBxnNls5kBGBm1bt8ZoNDo8VlBQwDlRUeTn57v9/Jav9m6kbdzPLRd2xlTkvCrKCpyMgLHfZDgd4mxQYHfXMFSznpp9WABUFGM5ab9Jc5AnO4pPk11WRqzBUKP/B4BVVcm1WNih0WQFYEtmOYfyrSRGKi6v4XCBbRFD0UQoegqjLmzsKNwqKipi48aN6HQ62rZt29jhaMbzL7xAzx49OJKdzc8//8xbb71lHz4stEmahNywlFv5JEFnnwyqKiu2FOTTeJ3L+VgsKiy3z4RZvQRb63dxUJnMv+EFK7AgzzZ5VPVKQWvF9oK8XE3Px2JVYfoX5op/O7+GGV+Y5fUgfGrZsmUsXbqUu+66yz4sVsDvv//OqNGjGT9+PK+++ioTJkxg6tSpjR2WcEMSFg+eXL2XNzrrMFdrZTkZAW901rkd0gy2IcsryEExOn5rVoKsgIqxMJSveyXKE+GF9UVFzDiSxclqve1zLZYmMaQZYNWvFq59v5isAses5HCBKkOahV/MnDmTL7/8kptuuqmxQ9GU/zz9NFmHD7N161Y++ugjpkyZ4nbItmh8Pv+cXLBgAT179rQv7DRq1CiPy2xv2rQJRVFq/Lhbb6IhPbl6LweMtgWsfmoHT7WHy376zWOyUml+ej4X/Lyfp0sP85E+h6dLD3PBL7/zoS4XFJXY/AhJWry0vqiIubm2Caf+LClhUmYmg//8o0kkK5VW/Wqh7TNFfJ9lS7zmbymh3TNFkqwIIYQbPk8nN2/ezJ133knPnj2xWCzMmTOHIUOGsHfvXo8T7KSnpzt0uImOjnZzdMOq7HJwpKXC6x8fwFqLafnB1jxUfejyw/tOwrlwLbH2pOWK77N8FHHgqqybOFFertkOtp5YVfir2HYlvx6zSjOQEEJ44POE5YsvHBfKeP3114mJiWHHjh1ccsklbs91ttZCoHOWtKjlFhS9VE0KIYQQlfz+qVg5O6G7cfeVLrroIsxmMykpKTz00ENuZ20sKSmhpKTEvl1QUFD/YH2grkMvC16eR/aSD4nNj2DLpZ244vusOncg9fWQ4sY4Xyu0cC8lBm3RwjBWLajvffDFUNymMJzXG764jsaOoSHuo1+7TaiqysyZM+nfvz/nn3++y+Pi4+NZtmwZH330EStXriQ5OZkrrriCb775xuU5CxYswGQy2X9at27tj0toMJFTHyH+7mulT4sQQgjhhF9rWO666y5+/vlnvv32W7fHJScnk5ycbN/u27cvhw4dYtGiRS6bkWbPns3MmTPt2wUFBQGRtAD2mpbKPi1aHqorhBBCNAS/fYm/++67+fTTT9m4cSOtWrWq9fl9+vTh999/d/l4SEgIkZGRDj+BIHLqI3yoyOghIUTTMXfePC7SQNNKQ2jXvj2Ln3mmscNoVDt27KBnz54UFtac4d2ffF7Doqoqd999N6tWrWLTpk20a9euTuX89NNPxMfH+zi6uquc5yvhuMotl7Tlja9+czlhXH25Gj2kU2By23CSQg0cLLaQllGEpYFHl+iwLUQYbdBz1FLOjuLTtaoBqu/5cGbO4HP0enqGhtWpjMamU6BFqO1KukTp0CnISKEmqNxazo95P3K0+BjRoVFcHHMxep1/17PKyclh0aJFbN26lby8PMLDw2ndujXDhg3j6quvrjHteVMxcuRIsrOzAdDpdLRo0YJ+/foxffp0n30hvf322+ncuTP33nuv2+MGXn45mzdvrrG/tKSE77/7zuOIV3cyMjJo36EDP+7YwYUXXuj22A0bNvDmm2+SkZGBqqrExsbSt29f7rnnnjr//qbM5wnLnXfeydtvv80nn3xCREQEOTm2OTNMJhOhobbFAGfPnk1WVhZvvvkmAIsXL6Zt27acd955lJaWsnz5cj766CM++ugjX4dXJ/eNSKFdhu0j8aIDcBFw64Wd+STB88RxdVU9adneI4nQ0iDUEgOUA8Fwb1cLy81HmZ/ufNl1XxsUHs7smFiHVZOzy8pYkJfr1Two9T2/soy5sbYVY9uHhPBGmza1LqOxje5i4JmhRvvKzXMGhHBz1yCmf2GWuViakPWZ61n4w5Pkns6174sNi+X+nvcxqM0gv/zOP//8k/4DBmA0Grnjjjvo2LEj5eXlZGZm8umnnxIVFcWll17q9FyLxaL5idFuv/12Ro0ahdVqJTMzk8cff5xFixbx73//u8FjmTJlCv+eN89hn8Fg8DjdRllZmX0Bxfr47rvvePDBB7nzzjsZMGAAiqJw4MABfvjhh3qX7U55eTmKojhdbLKx+TyiF154gfz8fC677DLi4+PtP++99579mOzsbDIzM+3bpaWlzJo1i65duzJgwAC+/fZb1q5dy5gxY3wdXq3dNyKFSb9ZMVYbJGEqgkm/WblvRIrffvfD+07amocqZsRVSxy/ualmPROIY06yyW8xVBoUHs7ihERiq73hxRoMLE5IZJCH5dLre37VMppXW5G2NmU0ttFdDHw4LpTESMe1hBIjFT4cF8roLtr+QBE26zPXc+/mWQ7JCkDe6Tzu3TyL9Znr/fJ777zzTgwGA2+++SaDBw+mXbt2dOzYkcsvv5zFixc79Pnr2bMnH330Effeey8DBgzg1VdfBeCbb77hpptuIjU1lWuuuYaXX34Zi+VMolxUVMT8+fMZMmQIl112GX//+9/57bffHOJIS0sjLj6eSJOJv02Zgtlstj/2zTffEBwSYv+yWuneWbO49LLL3F5fWFgYUVFRxMTE0KNHD6666qoaE4ju2rWL2267jf79+3P11VezaNEiiouL7Y9/8MEHjBkzhtTUVK688kruv/9+AObOncuPP/7Iu+++S8+ePenZsydHjhxxG0tcXJzDD9RsEtLp9bz44ouMGjWK8IgIHps/nxMnTjBx4kRiYmMJa9aMzsnJvP766wC079ABgIu7d0en1zPw8sud/v5vv/2WCy+8kJtuuom2bduSlJTEZZddxj//+U+H4zw9nytWrOD6669nwIABXH311TzxxBOcPn1m/qrVq1czcOBAtmzZwrhx40hNTSU7O5vS0lKeffZZrr76avr168eYMWP45JNPHH73vn37uPnmm+nfvz+p/ft7nCS2vvzSJORJWlqaw/Z9993Hfffd5+tQ6s2g13HNEVvNSvWl6nTY1rcZmW3lab3r9YTq69+/nuS686JRy3ROorCtcjTRGM1CJd9vzUM6YHaMbQ2S6ov26RQFq6oyJyaWn4uLnTbN6ICH6nF+9TIUF2XMjollQ1GRZpuHdAo8M9RY8W/n17B4qJFP0oukeaiBqapKqerd0O3CkkKe+H4hao31wbDvW/jDQnrH9vaqeSjUEFrjNe3M8ePH+WrdOubPn2+vra6uejnLli3jzjvv5J577kGv17Nt2zYefvhhZs2axYUXXkhWVhaPP/44AFOnTkVVVWbMmEFkZCSLFy8mPDyclStXcscdd/DRRx9hMplYt24dy5Yt47mlSxkwYABvLV/OkiVLaN++PQCXXHIJ7du3563ly/nnrFmArXZnxYoVLKj4Xd7Iy8vj22+/dRhhun//fv7xj39w++2389BDD3HixAn+7//+jyeffJJHHnmEvXv38tRTTzFv3jy6du1KQUEBP/30EwCzZs0iMzOTDh06cPvttwNwzjnneB2PO3PnzePx+fN5+umn0ev1/Ovhh9m7bx+frV1LVFQU+/fvtydV3/3vf/Tu04d1X33FeeedR3BwMAcyMmqU2bJlS7788kv2799Px44dnf5eT88n2F4Ts2bNIj4+niNHjrBw4UKeffZZHnjgAXs5ZrOZtLQ05syZg8lkokWLFjzyyCPs3r2bWbNm0alTJ44cOcLJkycdfv8LL7zA9OnTOeecc1i6dCl/mzKFb7ds8cEddU6+zrkxeWBHzjnk+nEd0KLQdtwr639zfWB9Ymgbjlrm7k1PQTUbmNw2vMZMur7SPTTMoRmnOp2iEBsUxKaOnepUfn3PrywjPiiI7qFhmp39dkAbvb0ZyBmdotDGpDCgjZ7NB2XF5oZUqpYybd807w7e5/mQ3NN5pL7f36vi/nf9NsKCwjwet3//flRVJblzZ4f9gwYNorRinpzrrruOu+++2/7YlVdeyciRI+3bjzzyCJMmTWL48OEAtGrVittvv50lS5YwdepUtm/fzv79+/nqq68Irpg3Z8aMGWzevJmvv/6aMWPG8M477zBy5EimTJkCwGOPPsrXX3/tUMty6623kpaWZk9Y1q5dy+nTpz2uhrx06VJefPFFrFYrJSUlnH/++Q79Nd566y2uvPJKbrzxRgDatGnDrFmzuP3223nggQfIycnBaDTSv39/mjVrRnx8vH0Eanh4OEFBQRiNRqKiojze7xdeeMFeKwVw22238dSiRU6PveGGG7j11lvt24cyM7nwwgvp0aMHgMMq2ZVNSi1btrTX2jhLWMaPH8/OnTu54YYbiI+P5/zzz6dPnz4MHTrU/ty8/vrrbp9PwH6vABITE5k2bRpPPPGEQ8JisVi4//776Vzx2jp48CDr169n6dKl9O7d2152dX//+9/p3r07APffdx/DR4zAbDb7rR+VJCxuxDUzgBff123H+UdSaEWfFW+O85Nog3edCMtVZ985bfVAei++Qbo6vzZleBtrY4iP8Bx/bY4TZ6fqtShpaWmoqsq//vUve+JS6dxzz3XY3rdvH3v37rU3TwD25MBsNvPrr79SXFzMoEGOfXBKSkrIyrItG5KRkcHYsWMdHu/Tpw+bNm2yb0+eNIl//etf/O9//6NPnz68/vrrXHfddR47q950000MHz4cVVXJzc3l+eefZ8aMGSxbtgy9Xs++ffs4fPiww4zqqqpitVo5cuQIvXv3Jj4+nlGjRtG3b1/69u3LwIED6/QBOuHGG3nwwQft2+5mYe9R8aFdadq0aVx73XX89NNPDB48mFHXXEO/fv1q9ftDQ0NZvHgxhw8fZvv27ezZs4fFixfz7rvv8tprr2E0Gj0+n0ajke3bt/P6669z4MABTp06RXl5OSUlJRQXF9tr6oKCgujU6cwXxt9++w29Xm9PRlypek7lIJm8vDzatGlTq2v1liQsbuScsuBNNx/bcf5xsNgCXkwQerDYfzEctXj3bf/WQ4ec1m70DA3jDS9ewK7Or00Z3sbaGLILvWvn8fY44TvBSjAvnvuiV8eWx5Vz58Y7PR733MDn6B7reahvqMF58051HTt2tC0Km55O6yp/C5XffENCQmqWXa3pSFVVbrvtNqeziAcHB2O1WomKiuLFF2vei4iICK/iBNsyKyOGD+f1tDTat2/PZ59/zsYNGzyeV3US0DZt2jBz5kxuvfVWtm/fTu/evVFVlTFjxjB+/Pga58bFxREUFMRbb73Fjh07+O6773jppZd4+eWXeeONN2oVP0CkyeSyKaa66onYsGHDyDhwgLVr1/L1118zaPBg7rjjDhb93//VKgawPb+tWrVi1KhR3HLLLYwdO5avvvqKkSNHenw+s7OzmTFjBmPGjGHatGlERkaya9cuHn30UYd+LiEhIQ6JsLPXkjNVO3FXnm+1+q9RXhIWN9I27ueWCztjKnKetliBkxG24/wWQ0YR93a1oJr11OzDAqCiGMtJ+81/I2R2FJ8mu6yMWIOhRt8LAKuqkmuxsMNFslHf831VRmPbklnOoXwriZGKy2s4XKCyJVO7SVegUhSFEMW7N+kLE7oRGxZL3uk8p/1YFBRiw2Lol9DXp0OcW7ZsyeBBg3juuefo37+/y34s7iQnJ3Pw4EGXk2x26dKF48ePo9frSUhIcHpM27Zt2b17t8O+7777rsZxf/vb37jhxhtp1aoVHTp0IDU1tdbxVo5UqVyGJTk5mT/++MPtJKEGg4HevXvTu3dvpk6dysCBA/nhhx+4/PLLCQoK8usHalXR0dFMnjyZyZMn079/f+67/34W/d//2Ztzystr/3eekJCA0Wi0N795ej737duHxWJhxowZ9nu5fr3nDuEdO3bEarWyY8cOe5OQFmhv3JKGWMqtfJKgq+ja6siKLX34NN5/HW7BtsrzcvPRiq3qUaiAQnFQmV87aVqBBXm20RDVO1VbK7YX5OW6bDyrer61Duf7qozGZlVh+hfmin87v4YZX5ilw63G6XV67u9pGySgVPsSUbl9X8/7/DIfy3PPPYfFYuHmm2/mq6++4sCBA2RkZPDZZ5+RkZHhcSjqlClTWLt2LcuWLeOPP/7gwIEDfPXVV7zwwgsA9OrViwsuuIBZs2axbds2jhw5wq5du3jhhRfYu9c2hcP111/P6tWree211/jtt994ZO5cfvnllxq/68orr8RkMjF//nwmT57s1fWdPn2aY8eOcezYMX755ReeffZZmjdvTteuXQGYNGkSu3fvZuHChaSnp5OZmcnmzZv5v4qaiy1btvDuu++Snp5OdnY2a9euRVVVkpKSAFuzxZ49e+wdSP2VvDz8yCN88skn7N+/n19++YW1a9fam+diYmIIDQ3liy+/JDc3177eXnXLli3j2WefZceOHWRlZZGens6///1vLBYLvXr1Ajw/n4mJiZSXl/Pee+9x+PBhPvvsM1auXOkx/oSEBK6++moeffRRNm3aRFZWFjt27GDdunU+ukN1IwmLB0+u3ssbnXWYqzXLnIyANzr7bx6Wquan57OCHBSjY0auBFmpHPLs7xlx1xcVMeNIFgXV/sBzLRZmHMnyOAdK5fm5FsemK2/P91UZjW3Vrxaufb+YrALHrORwgcq17xfLPCxNxKA2g3jq0kXEhMU47I8Ni+GpSxf5bR6WDhUTjvXq1YvnnnuOG2+8kUmTJvH+++8zceJE/v73v7s9v2/fvvznP//hu+++Y9KkSdxyyy28/fbb9s6fiqKwePFiLr74Yh599FHGjh3LnDlzOHLkiH0B2yFDhjBlyhQemD2bHj17knnwINOm1eywrNPpmDRpEuXl5dx8001eXd9LL73EsGHDGDZsGPfccw+hoaEsXbrU3n+kU6dOvPTSSxw6dIjbbruNiRMn8tJLL9k70UZERLBx40buuOMOrrvuOlauXMljjz1Gh4qhxBMnTkSv1zNu3DgGDx5cY+i1rwQHB/PgnDl0u/BCLr3sMvR6Pe+8/TZgqwF6ZvFili1bRmKrVowaPdppGRdffDFZWVk88sgjXHfddfzjH//g+PHjLF261N6J19PzmZyczD333MObb77J9ddfz+eff86dd3puzgR44IEHuOKKK1i4cCHXXXcd8+fPdxg+3hgU1ZtxyE1AQUEBJpOJE8eO1WtWRFcryn7YM4GUgkh+agcbFFzOdOvPVW0NTma6fbhLc65VY0FVyDUVcsX3WZT5MYaxJhOPxsXzi7mYJ/OONspMt74ow1v+ej51Cmz7WzN6JeqZv6WEhzeWuKxZ0cJKyYEWQ1JSEi+++KJXo0Wqq7oqbWPMdAvaWN3Xm9V5p952G3m5uTXm7wBtXAPIas2+4uk+ms1mDmRk0LZ16xqdoAsKCjgnKor8/Hy3n9/Sh8VLlV0OjrRUeP3jA1j92AzkikWlxtBlZ9P4q+UWFL1/ntrKz9Rci6VOw4etUO9hx74oo7FZVfir2HY3fz1mlWagJkqv09Mzrmdjh6E5+fn5/PDDD7z99tt8vGpVY4cjAoQ0CQUA+4y4FQsm5k65GrVcmhaEEI1j1OjRXDNqFLfddhuDBw9u7HBEgJAaFh+rb7U31L3qu+DleWQv+ZD87dn8dmknrvg+S9OdUJsCXzyfWuCL5hThG1qo/vd3M4g3Q5iFqC2pYQkgkVMfIf7ua+01Lf7uiCuEEEI0FPk880IrWtHnwKMkZj3D7Ss/oeRUIXvYQytqTlXc2CKnPuLQPCRJixBCiEAgTUIefMM39Kc/ylEFlXIU9KionMd5ZJLJFrZwKc6Xc28szjriXvF9Fjono4xqu2Bi5fiHLuFBTGkXXusydIptTZ34CIXsQtskabXtcFrfMrQSQ4tQW0/uLlE6dArS8VYIIdyQhMWNymSlklLxcV11sqgBDOAbvuESLqlxfmOqnrRs75FEaGkQaknF2kTBcG9XC8vNR5mf7nziourmJJuYYIgBCyRgZGZwq1qVMbqLgWeGGh0WADyUb2X6F2av5x+pbxlajGHOgBBu7hpUqxiEEOJsI60FLrSilT1ZqT6bZaXK/f3pr8nmIfvooYrJ5dQSx/khVLOeCcQxJ9nksaw5ySYmEAcWx5eMt2WM7mLgw3GhJEY63svESIUPx4Uyuovn3Lm+ZQRKDEIIcTaShMWFL/gCpeI/dyqP+ZzPGyiy2vn3ryftM+LWXIvItj3RGI3BzWUaFNsxVc+pTRk6BZ4Zaqz4t1LtMdv24qFGdG5iqG8ZgRKDEEKcreTrnAtd6IKK6jFhAVBROZdzPR7XGCa3DUctczfzpoJqNrClR2tOqs6bI5orBtQCdy8VWxmfDG3Ob9aaUze3ilQcmk+q0ykKbUwK711r5HCB844c9S2jKcUwoI2ezQdlAUQhhKhKEhYXvKldqcuxDS0ptKLPigemgmZ4bhhyLzU2hKuS6j7zy7Up9Z/zpL5laCGG+AhtvpZETWp5Oad37KD86FH00dGEde+Oovf/1Py7du3itttuo1evXixZsqTG46tXrwZgxIgRfo9FiIYiCYsLasV/3tawOFtmXgsOFlvAi8/P45FFHLU6r2GJ1hloWRDusYyNR0r4JbOkxv42JoWbunoO4q2fS8nMd34f61tGU4ohu1CbryXhqHDdOnIfX4AlN9e+zxAbS+yDs4nw8+yuq1evZty4cXzyySfk5OTYF7t7++23ueaaa+zHnTp1io8//pgJEyb4NR4hGoIkLC78yq+cx3leHaugsI99fo6obtIyiri3qwXVrKdm/xMAFcVYzsDth10OTzYosLtrR49lXLfupNMydApclmQgMVKp0XcDwKqqHC5Qmfyx2eXQ3vqW0ZRi2JIpzUFaV7huHVkz7oFqa8da8vLImnEPiYv/47ekpbi4mPXr15OWlsbx48dZvXo1U6dOBWyrFd9111321YlXrlzJdddd55c4hGho0unWhaEM9armpPKYYQxroMhqx6LCcvPRiq3q12LbXm4+6nYulfqWYVVh+hfmin+r1R6zbc/4wnWi4IsyAiUG4SeqCmazVz/lhYXkzn+8RrJiL0dVyX18AeWFhVhPn67xU6NMZ+W4sW7dOtq0aUPbtm0ZNmwYa9asQa0oY8SIESxYsIAtW7awZcsWFixYwFVXXeWLOyREo5MaFhcOc5gtbGEAA1w2DVUmM1vYwmEON3SIXpufng/JtpE8qvnMU64Yy72eQ6W+Zaz61cK17xdXzD9y5l4eLlCZ4eX8I/UtI1BiEH5QUkKzKVO9OvR3L46x5Obye+8+Th9rVm371Csvg9Ho1e8G+OSTTxg2zPYFqW/fvpw+fZrvv/+e3r1789lnn/HBBx/Qv79tSobZs2dz3XXXSdIiAoIkLG5cyqUOk8dVJi5VExgtznTrzPz0fBYq+Y4z3f5Wu1lq61vGql8tfJJeVK8ZYutbRqDEIM5OGRkZ/PLLLzz55JMAGAwGBg8ezOrVq+nduzcnTpxg6dKlbKhYfHDmzJl8/PHHjRixEL4jCYsHl3AJrWjF53zOuZxrT1j2sY9hDNN0zUp1FhVeOVDUqGVYVeo9ZLe+ZQRKDMKHQkJsNR1eSLaUc3jaNI/HtXrxRcJ6dK+x/6edO2v8bm99+umnlJeXc/XVV9v3qaqKwWCgoKCgRufaZs2aSYdbETAkYfHCYQ5zARc0dhhCCH9RFK+bZZp164YhNhZLXp7z/ieKgiE2lmap/ZwPca5F809VFouFtWvXMmPGDHr37u3w2P33388XX3zBuHHjABnOLAKTJCzVlJeWNnYI9RYI1yACjxZel5UxmM1mMg4dol3bthjrkEDEPjjbNkpIURyTlorRX7GzH3A5H0v3iy+ufeDAxx9/TFFREY88/DAmk+OsST///DOff/45C594ok5lN4a63odAJPfCOzJKSAghaili8GASF/8HQ0yMw35DbKzfhjS/9tprDLriihrJCsDYMWPYuXMnP/74o89/rxBaITUsQghRBxGDBxN++eUNNtPtp59+6vKxiy++GGu59IkSgU0SFiGEqCNFr6dZr16NHYYQZwVpEhJCCCGE5vktYXn++edp164dRqOR7t27s2XLFrfHb968me7du2M0Gmnfvj0vvviiv0ITQgghRBPjl4TlvffeY8aMGcyZM4effvqJAQMGMGzYMDIzM50ef+DAAa666ioGDBjATz/9xIMPPsg//vEPPvroI3+EJ4QQQogmRlHVWi5k4YXevXtz8cUX88ILL9j3nXvuuYwaNYoFCxbUOP7+++/n008/Zd++MwsITps2jV27drFt2zavfmdBQQEmk4kTx44RGRlZ/4sQQgSs+g5rFkLUjtls5kBGBm1bt67xN1dQUMA5UVHk5+e7/fz2eQ1LaWkpO3bsYMiQIQ77hwwZwn//+1+n52zbtq3G8VdeeSXbt2+nrKzM6TklJSUUFBQ4/AghhFcq5kvxw/c1IYQTVqsVAMXJSvXe8vkooWPHjlFeXk5sbKzD/tjYWHJycpyek5OT4/R4i8XCsWPHiI+Pr3HOggULmDdvnu8CF0KcNYIMBnSKwrHjx4lq2bJeb6JCCNdUVaWsrIy8o0fR6XQEBQXVuSy/DWuu/gagqqrbNwVnxzvbX2n27NnMnDnTvl1QUEDr1q3rGq4Q4iyi1+tJjI8nKzubjKL6ra8lhPAsLDSU1omJ6HR1b9jxecISFRWFXq+vUZuSl5dXoxalUlxcnNPjDQYDLVu2dHpOSEgIIbVYNEwIIapq1qwZHdq1o8xicb4mkBCi/hQFvV6PQa+vd02mzxOW4OBgunfvzrp16xg9erR9/7p167jmmmucntO3b19Wr17tsO+rr76iR48e9ao+EkIId/R6PXo/zUwrhPAtvwxrnjlzJq+88gqvvfYa+/bt45577iEzM5NpFUuyz549m5tvvtl+/LRp0zh48CAzZ85k3759vPbaa7z66qvMmjXLH+EJIYQQoonxSx+W8ePHc/z4cf7973+TnZ3N+eefz2effUZSUhIA2dnZDnOytGvXjs8++4x77rmH5557joSEBJ599lnGjh3rj/CEEEII0cT4ZR6WxiDzsAghhBBNj7fzsATM4oeVeVdBYWEjRyKEEEIIb1V+bnuqPwmYhKWw4oKT2rVr5EiEEEIIUVuFhYWYTCaXjwdMk5DVauXIkSNEREQ4HTpVOU/LoUOHpMmonuRe+obcR9+Re+k7ci99Q+6j91RVpbCwkISEBLfztARMDYtOp6NVq1Yej4uMjJQXj4/IvfQNuY++I/fSd+Re+obcR++4q1mp5JdhzUIIIYQQviQJixBCCCE076xJWEJCQnjkkUdkOn8fkHvpG3IffUfupe/IvfQNuY++FzCdboUQQggRuM6aGhYhhBBCNF2SsAghhBBC8yRhEUIIIYTmScIihBBCCM07KxKW559/nnbt2mE0GunevTtbtmxp7JCanLlz56IoisNPXFxcY4fVJHzzzTeMGDGChIQEFEXh448/dnhcVVXmzp1LQkICoaGhXHbZZfzyyy+NE6zGebqXkydPrvE67dOnT+MEq2ELFiygZ8+eREREEBMTw6hRo0hPT3c4Rl6XnnlzH+U16TsBn7C89957zJgxgzlz5vDTTz8xYMAAhg0bRmZmZmOH1uScd955ZGdn2392797d2CE1CadOnaJbt24sXbrU6eNPPvkkTz/9NEuXLuWHH34gLi6OwYMH29fHEmd4upcAQ4cOdXidfvbZZw0YYdOwefNm7rzzTv73v/+xbt06LBYLQ4YM4dSpU/Zj5HXpmTf3EeQ16TNqgOvVq5c6bdo0h31dunRRH3jggUaKqGl65JFH1G7dujV2GE0eoK5atcq+bbVa1bi4OPWJJ56w7zObzarJZFJffPHFRoiw6ah+L1VVVSdNmqRec801jRJPU5aXl6cC6ubNm1VVlddlXVW/j6oqr0lfCugaltLSUnbs2MGQIUMc9g8ZMoT//ve/jRRV0/X777+TkJBAu3btuP766/nzzz8bO6Qm78CBA+Tk5Di8RkNCQrj00kvlNVpHmzZtIiYmhs6dOzN16lTy8vIaOyTNy8/PB6BFixaAvC7rqvp9rCSvSd8I6ITl2LFjlJeXExsb67A/NjaWnJycRoqqaerduzdvvvkmX375JS+//DI5OTn069eP48ePN3ZoTVrl61Beo74xbNgwVqxYwYYNG3jqqaf44YcfuPzyyykpKWns0DRLVVVmzpxJ//79Of/88wF5XdaFs/sI8pr0pYBZrdkdRVEctlVVrbFPuDds2DD7vy+44AL69u1Lhw4deOONN5g5c2YjRhYY5DXqG+PHj7f/+/zzz6dHjx4kJSWxdu1axowZ04iRadddd93Fzz//zLffflvjMXldes/VfZTXpO8EdA1LVFQUer2+xjeCvLy8Gt8cRO00a9aMCy64gN9//72xQ2nSKkdayWvUP+Lj40lKSpLXqQt33303n376KRs3bqRVq1b2/fK6rB1X99EZeU3WXUAnLMHBwXTv3p1169Y57F+3bh39+vVrpKgCQ0lJCfv27SM+Pr6xQ2nS2rVrR1xcnMNrtLS0lM2bN8tr1AeOHz/OoUOH5HVajaqq3HXXXaxcuZINGzbQrl07h8fldekdT/fRGXlN1l3ANwnNnDmTm266iR49etC3b1+WLVtGZmYm06ZNa+zQmpRZs2YxYsQI2rRpQ15eHo899hgFBQVMmjSpsUPTvKKiIvbv32/fPnDgADt37qRFixa0adOGGTNm8Pjjj9OpUyc6derE448/TlhYGDfeeGMjRq1N7u5lixYtmDt3LmPHjiU+Pp6MjAwefPBBoqKiGD16dCNGrT133nknb7/9Np988gkRERH2mhSTyURoaCiKosjr0gue7mNRUZG8Jn2pEUcoNZjnnntOTUpKUoODg9WLL77YYciZ8M748ePV+Ph4NSgoSE1ISFDHjBmj/vLLL40dVpOwceNGFajxM2nSJFVVbUNIH3nkETUuLk4NCQlRL7nkEnX37t2NG7RGubuXp0+fVocMGaJGR0erQUFBaps2bdRJkyapmZmZjR225ji7h4D6+uuv24+R16Vnnu6jvCZ9S1FVVW3IBEkIIYQQorYCug+LEEIIIQKDJCxCCCGE0DxJWIQQQgiheZKwCCGEEELzJGERQgghhOZJwiKEEEIIzZOERQghhBCaJwmLEEIIITRPEhYhhBBCaJ4kLEIIIYTQPElYhBBCCKF5krAIIYQQQvP+H8ksG8cvbgXPAAAAAElFTkSuQmCC",
|
|
"text/plain": [
|
|
"<Figure size 640x480 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
" # Define the map and start/stop points\n",
|
|
"m = Map(30,20)\n",
|
|
"#m.Randomize()\n",
|
|
"m.CreateMaze()\n",
|
|
"starting_point: Point2D = Point2D((29,19))\n",
|
|
"end_point: Point2D = Point2D((1,1))\n",
|
|
"\n",
|
|
"path_finder_classes: list[type[PathFinderBase]] = [\n",
|
|
" BFS,\n",
|
|
" DijkstraAlgorithm,\n",
|
|
" GBFS,\n",
|
|
" A_star,\n",
|
|
"]\n",
|
|
"\n",
|
|
"v = Visualizer()\n",
|
|
"v.DrawMap(m)\n",
|
|
"\n",
|
|
"for pfc in path_finder_classes:\n",
|
|
" path_finder = pfc()\n",
|
|
" path_finder.SetMap(m)\n",
|
|
" path = path_finder.CalculatePath(starting_point, end_point)\n",
|
|
" elapsed_time, visited_nodes = path_finder.GetStats()\n",
|
|
" if path is not None: \n",
|
|
" cost = m.GetPathCost(path)\n",
|
|
" print(f\"{path_finder.name:24}: took {elapsed_time/1e6:.3f} ms, visited {visited_nodes} nodes, cost {cost:.2f}\")\n",
|
|
" v.DrawPath(path, label=path_finder.name)\n",
|
|
" else:\n",
|
|
" print(f\"{path_finder.name}: No path found\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2ec9fb78-089d-4d51-9f16-087a04b4e8a4",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "b050caaa-d9b5-4a22-8e6d-aaccfaa4fb1b",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.7"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|