632 lines
69 KiB
Plaintext
632 lines
69 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": 7,
|
|
"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": 8,
|
|
"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": 9,
|
|
"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": 10,
|
|
"id": "859c64f4-e65c-4905-a775-c6f17542eac8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#\n",
|
|
"# Method: depth-first search\n",
|
|
"#\n",
|
|
"\n",
|
|
"#\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": 11,
|
|
"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.941 ms, visited 561 nodes, cost 17012.00\n",
|
|
"Dijkstra's Algorithm : took 2.708 ms, visited 2925 nodes, cost 2027.00\n",
|
|
"Greedy Best First Search: took 0.268 ms, visited 120 nodes, cost 10019.00\n",
|
|
"A* : took 0.370 ms, visited 275 nodes, cost 2028.00\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"image/png": "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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
|
|
}
|