628 lines
67 KiB
Plaintext
628 lines
67 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": 12,
|
|
"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": 13,
|
|
"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",
|
|
" if x == 0 and y == 0:\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": 17,
|
|
"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": 18,
|
|
"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",
|
|
" # for now we use Manhattan distance, although\n",
|
|
" # it is probably not entirely correct, given that\n",
|
|
" # we can also move diagonally in the grid\n",
|
|
" # TODO a problem for future me\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",
|
|
" # for now we use Manhattan distance, although\n",
|
|
" # it is probably not entirely correct, given that\n",
|
|
" # we can also move diagonally in the grid\n",
|
|
" # TODO a problem for future me\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": 19,
|
|
"id": "ece3a6c8-aa1d-49a8-9f4c-06ebff72f991",
|
|
"metadata": {
|
|
"editable": true,
|
|
"slideshow": {
|
|
"slide_type": ""
|
|
},
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Breadth First Search : took 1.953 ms, visited 561 nodes, cost 14015.00\n",
|
|
"Dijkstra's Algorithm : took 2.933 ms, visited 2963 nodes, cost 1029.00\n",
|
|
"Greedy Best First Search: took 0.273 ms, visited 120 nodes, cost 7022.00\n",
|
|
"A* : took 0.485 ms, visited 355 nodes, cost 1030.00\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"image/png": "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3WxSCOG+MY3C7FLpF6WgGfWMaRTmMiFczS4tLw0DuahBASXoPDW7tOTcln0etrjykb9Sbthh2MGCYwvsghWAs6azLDi2gB2GHT553BkzZqDT6fjoo4+46aabaNy4MU2bNqVnz57MmTOHG264vONmUlISy5Yt46mnniIsPJwXX3oJgNWrV3NtUhIhNWrQpGlTpv7nP5hMl7cmz8rK4sGHHiI6JobImjXpdeON7Nmzx64dixYtok+fPnTr1o0XXngBo9Fou+/XX3+lY8eOnD592u6c1157jQcffLDY51ejRg2ioqKoV68e1157LTfffDMHDhywK7Nnzx4efPBBrr/+evr378+sWbPIycmx3f/FF18wZMgQunTpQp8+fZgwYQIAU6ZM4ddff+XTTz8lKSmJpKQkTp486bItwcHBREVF2f1A0SGhwte5a9euvPfeexgMBiZPnsxNN93E9ddfz5AhQ1i1ahUAt956KwDXtG+PqtPRo6fzzJNfff01119/Pf83fjwtWrSgefPmDBo0iNfnzbMrt3nzZu6++266dOnCrbfeyjvvvGP3en788ceMGDGCrl270r9/f15++WUuXrxou3/16tX06NGDLVu2MGzYMLp06UJaWhp5eXnMmzeP/v3707lzZ4YMGcLKlSvtHnv//v2MGjWK66+/nnvvvZeUlBSX11OI6qZhiHtfXIorF1A31K063C1X1VTLHhZN08gx5Ti9z2gx2t22aBY+Tvu42Po+SfuEq0Kvcmt4KFAJRHHV11fIuXPn2LZtG4888ojLIQnHehYuXMijjz7K+++9h06n49tvv+XuUaOYO2cOXbt25a+//uKhhx8G4PnnnkPTNG4ZMIDatWrx9VdfERkZydsLF3LjTTfx2WefERkZybp161i4cCFPP/00bdu2Zc2aNXz22WfExcUBcM0111C/fn3WrFnD3XffDVh7HdauXVuqzQUzMzP58ccfadWqle3Yn3/+yRNPPMFDDz3E5MmTOXv2LK+88gozZ87k+eef548//mD27NlMnTqVq6++GoPBwK5duwAYP348qampNGnSxNZzUlJvibsKrvOTTz6JTqfjrbfe4siRI8ydO5eaNWty7NgxW1C3aNEixowZw7rvvuOqq64i0EXCr5iYGJYsWcK+ffvsrkFhW7du5bnnnmP8+PG0bduWEydOMG3aNAAeeOABwPqe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",
|
|
"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
|
|
}
|