├── .gitignore ├── LICENSE ├── README.md ├── main ├── Algorithms.py ├── Colors.py ├── Grid.py ├── Maze.py ├── Node.py ├── Pathfinding.py ├── Testing.py ├── algorithms │ ├── AStar.py │ ├── BFS.py │ ├── BStar.py │ ├── BeamSearch.py │ ├── BellmanFord.py │ ├── BestFS.py │ ├── DFS.py │ ├── Dijkstra.py │ ├── FastMarching.py │ ├── FloodFill.py │ ├── FloydWarshall.py │ ├── Fringe.py │ ├── GBFS.py │ ├── GBLS.py │ ├── IDAStar.py │ ├── IDDFS.py │ ├── JPS.py │ ├── LPAStar.py │ ├── LexicographicBFS.py │ ├── RP.py │ ├── RandomLIFO.py │ ├── RandomWalk.py │ ├── SPFA.py │ ├── ThetaStar.py │ ├── __init__.py │ ├── bidirectional │ │ ├── AStar.py │ │ ├── BFS.py │ │ └── __init__.py │ └── random │ │ ├── grid_to_tree.md │ │ ├── tree.png │ │ └── tree.txt └── testing │ ├── AStar.py │ ├── BFS.py │ ├── BStar.py │ ├── BeamSearch.py │ ├── BellmanFord.py │ ├── BestFS.py │ ├── DFS.py │ ├── Dijkstra.py │ ├── FastMarching.py │ ├── FloodFill.py │ ├── FloydWarshall.py │ ├── Fringe.py │ ├── GBFS.py │ ├── GBLS.py │ ├── IDAStar.py │ ├── IDDFS.py │ ├── JPS.py │ ├── LPAStar.py │ ├── LexicographicBFS.py │ ├── RP.py │ ├── RandomLIFO.py │ ├── RandomWalk.py │ ├── SPFA.py │ ├── ThetaStar.py │ ├── __init__.py │ ├── bidirectional │ ├── AStar.py │ ├── BFS.py │ └── __init__.py │ └── results │ └── Generated_Maze-Example.csv ├── resources ├── AlgorithmExplanations.md ├── Best-First-Search.gif ├── OtherAlgorithms.md ├── Thoughts.md ├── astar_maze.gif ├── b_star.pdf ├── d-star.pdf ├── fast_marching.pdf ├── gabows.pdf ├── interesting_ideas.md ├── links.md └── theta_star.pdf ├── upload.sh └── website ├── TODO ├── package.json ├── public ├── favicon.ico ├── favicon.xcf ├── index.html ├── logo192.png ├── logo192.xcf ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── algorithms │ ├── a*.ts │ ├── b*.ts │ ├── beamsearch.ts │ ├── bellford.ts │ ├── bestfirstsearch.ts │ ├── bfs.ts │ ├── dfs.ts │ ├── dijkstra.ts │ ├── floodfill.ts │ ├── floydwarshall.ts │ ├── fmm.ts │ ├── fringe.ts │ ├── gbfs.ts │ ├── gbls.ts │ ├── helper.ts │ ├── ida*.ts │ ├── iddfs.ts │ ├── jps.ts │ ├── lbfs.ts │ ├── lpa*.ts │ ├── r-lifo.ts │ ├── spfa.ts │ └── theta*.ts ├── components │ └── Panel.tsx ├── css │ ├── index.css │ └── panel.css └── index.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vscode/ 3 | *.ipynb 4 | node_modules/ 5 | build/ 6 | *.yaml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pathfinding/Graph Search Algorithms ![License: GPL 3.0](https://img.shields.io/badge/License-GPL_3.0-red.svg) 2 | 3 | This is a Python (3.10+) implementation and visualization of various pathfinding algorithms. 4 | 5 | There is also a web version of this at [this hyperlink](https://gurknathe.github.io/Pathfinding-Algorithms). However, there are a number of features that are missing including node-to-node updating. So, the final grid will display basically immediately after you click Run. Additionally, not every algorithm is implemented yet. 6 | 7 | The graph used is a static, undirected, unweighted, strongly connected graph (aka, a grid that doesn't change while algorithm is running). 8 | 9 | The visualization is implemented using PyGame. Credits to [Tech With Tim](https://www.youtube.com/watch?v=JtiK0DOeI4A). 10 | 11 | A maze generator is implemented, it has at least one valid path from the start to end node. The implementation is based on [this](https://github.com/OrWestSide/python-scripts/blob/master/maze.py). 12 | 13 | ![A* + Maze](./resources/astar_maze.gif) 14 | 15 | See the explanations for the implemented algorithms [here](./resources/AlgorithmExplanations.md). 16 | 17 | Warning: There is an issue with PyGame that can cause the program to crash. On my system running Linux Mint, the update display function causes a segmentation fault. Non-Windows users seem to be the group affected by this, so if you're using Windows, you should be fine. 18 | 19 | ## Command line arguments 20 | 21 | There are three optional command line arguments: width, # rows, algorithm type. 22 | 23 | Disclaimer: Testing is done on default settings, there is no guarantee, as of now, that things work smoothly for different widths and rows. 24 | 25 | ``` 26 | width >= 2 27 | rows >= 2 28 | algorithm = astar, bfs, dfs, dijkstra, rand, yen [See Algorithms.py for full list] 29 | ``` 30 | 31 | ``` 32 | ./pathfinding.py <# rows> 33 | ``` 34 | 35 | ## Keydown Events 36 | 37 | While an algorithm isn't running: 38 | 39 | - Press T to test algorithms 40 | - Press B to go to previous algorithm in list 41 | - Press N to go to next algorithm in list 42 | - Press Q to exit 43 | - Press C to clear grid 44 | - Press G to generate a new maze 45 | - Left Click to place Start, then End, then Obstacles 46 | - Right Click to remove Start, End, or Obstacles 47 | 48 | After an algorithm has run: 49 | 50 | - W, Left Click, or Right Click to clear the Algorithm Mark-up 51 | 52 | While an algorithm is running: 53 | 54 | - Press S key to stop the algorithm 55 | 56 | ## Testing 57 | 58 | The testing function runs every implemented algorithm for the current grid. It outputs the time it took to run the algorithm and the number of node checks while running. The results are written into a CSV file, which can be found in the `main/testing/results` directory. An example output is given for a randomly generated maze, with default settings. 59 | 60 | The testing will take longer as the visitable nodes increases. For my system, and a randomly generated maze on default settings, it takes ~4 minutes to run and save the data. The Floyd-Warshall algorithm takes up the most time and can vary significantly in its execution time (e.g., most of the time I get a run time of ~260 seconds where as of writing this I record a run time of ~670 seconds). 61 | 62 | ## Node Types 63 | 64 | - Start: where the search algorithm will start 65 | - End: where the search algorithm is trying to get to 66 | - Obstacle: a position the algorithms avoid 67 | - Check/Uncheck: markup for visualizing the algorithm 68 | - Path: markup for visualizing the found path 69 | - Default: a position that can be traversed 70 | 71 | ## Algorithm Progress 72 | 73 | ``` 74 | Currently implemented algorithms (25/25): 75 | 76 | - A* 77 | - Beam Search 78 | - Bellman Ford's Algorithm 79 | - Best First Search 80 | - Bidirectional A* 81 | - Bidirectional search 82 | - Breadth First Search (BFS) 83 | - B* 84 | - Depth First Search (DFS) 85 | - Dijkstra's Algorithms 86 | - Fast Marching Method (FMM) 87 | - Flood Fill Algorithm 88 | - Floyd-Warshall's algorithm 89 | - Fringe search 90 | - Greedy Best First Search (GBFS) 91 | - Greedy Best Line Search (GBLS) 92 | - Iterative Deepening (IDA*) 93 | - Iterative Deepening DFS (IDDFS) 94 | - Jump Point Search (JPS) 95 | - Lexicographic BFS (LBFS) 96 | - Lifelong Planning A* (LPA*) 97 | - Random Walk 98 | - Random Walk with LIFO Queue 99 | - Shortest Path Faster Algorithm (SPFA) 100 | - Theta* 101 | 102 | Currently looking at: 103 | 104 | - None 105 | 106 | Planned algorithms (Going to look at them): 107 | 108 | - None 109 | 110 | ``` 111 | 112 | Possible incorrectly implemented algorithms: 113 | 114 | - [B\*](./resources/b_star.pdf) 115 | - [Jump Point Search](https://en.wikipedia.org/wiki/Jump_point_search) 116 | -------------------------------------------------------------------------------- /main/Algorithms.py: -------------------------------------------------------------------------------- 1 | from algorithms.AStar import a_star 2 | from algorithms.BeamSearch import beam_search 3 | from algorithms.BellmanFord import bell_ford 4 | from algorithms.BestFS import best_fs 5 | from algorithms.BFS import bfs 6 | from algorithms.bidirectional.BFS import bi_search 7 | from algorithms.bidirectional.AStar import bi_a_star 8 | from algorithms.BStar import b_star 9 | from algorithms.DFS import dfs 10 | from algorithms.Dijkstra import dijkstra 11 | from algorithms.FloodFill import flood_fill 12 | from algorithms.FloydWarshall import floyd_warshall 13 | from algorithms.FastMarching import fmm 14 | from algorithms.Fringe import fringe_search 15 | from algorithms.GBFS import gbfs 16 | from algorithms.GBLS import gbls 17 | from algorithms.IDAStar import ida_star 18 | from algorithms.IDDFS import iddfs 19 | from algorithms.JPS import jps 20 | from algorithms.LexicographicBFS import lbfs 21 | from algorithms.LPAStar import lpa_star 22 | from algorithms.RandomLIFO import rand_lifo 23 | from algorithms.RandomWalk import rand_walk 24 | from algorithms.SPFA import spfa 25 | from algorithms.ThetaStar import theta_star 26 | 27 | 28 | ALGORITHMS = [ 29 | "astar", 30 | "beam", 31 | "bellford", 32 | "bestfs", 33 | "bfs", 34 | "biastar", 35 | "bisearch", 36 | "bstar", 37 | "dfs", 38 | "dijkstra", 39 | "flood", 40 | "floyd", 41 | "fmm", 42 | "fringe", 43 | "gbfs", 44 | "gbls", 45 | "ida", 46 | "iddfs", 47 | "jps", 48 | "lbfs", 49 | "lpa", 50 | "rand", 51 | "rand_lifo", 52 | "spfa", 53 | "theta", 54 | ] 55 | 56 | run_algs = { 57 | "astar": "a_star(grid)", 58 | "beam": "beam_search(grid, 50)", 59 | "bellford": "bell_ford(grid, 1)", 60 | "bestfs": "best_fs(grid)", 61 | "bfs": "bfs(grid)", 62 | "biastar": "bi_a_star(grid)", 63 | "bisearch": "bi_search(grid)", 64 | "bstar": "b_star(grid)", 65 | "dfs": "dfs(grid)", 66 | "dijkstra": "dijkstra(grid)", 67 | "flood": "flood_fill(grid)", 68 | "floyd": "floyd_warshall(grid)", 69 | "fmm": "fmm(grid)", 70 | "fringe": "fringe_search(grid)", 71 | "gbfs": "gbfs(grid)", 72 | "gbls": "gbls(grid)", 73 | "ida": "ida_star(grid)", 74 | "iddfs": "iddfs(grid, len(grid.grid) * len(grid.grid[0]))", 75 | "jps": "jps(grid)", 76 | "lbfs": "lbfs(grid)", 77 | "lpa": "lpa_star(grid)", 78 | "rand": "rand_walk(grid)", 79 | "rand_lifo": "rand_lifo(grid)", 80 | "spfa": "spfa(grid)", 81 | "theta": "theta_star(grid)", 82 | } 83 | 84 | 85 | def algorithm(grid: object, algorithm: str, *args): 86 | """ 87 | Select and run the specified pathfinding algorithm. 88 | 89 | Args: 90 | grid (Grid): An object representing the current grid 91 | algorithm (str): name of the algorithm to use 92 | 93 | Returns: 94 | None 95 | """ 96 | if algorithm in run_algs: 97 | eval(run_algs[algorithm]) 98 | else: 99 | a_star(grid) 100 | -------------------------------------------------------------------------------- /main/Colors.py: -------------------------------------------------------------------------------- 1 | COLORS = { 2 | "RED": (255, 0, 0), 3 | "GREEN": (0, 255, 0), 4 | "BLUE": (0, 0, 255), 5 | "YELLOW": (255, 255, 0), 6 | "WHITE": (255, 255, 255), 7 | "BLACK": (0, 0, 0), 8 | "PURPLE": (128, 0, 128), 9 | "ORANGE": (255, 165, 0), 10 | "GREY": (128, 128, 128), 11 | "TURQUOISE": (64, 224, 208), 12 | } 13 | -------------------------------------------------------------------------------- /main/Grid.py: -------------------------------------------------------------------------------- 1 | from Node import Node 2 | import pygame 3 | 4 | 5 | class Grid: 6 | def __init__(self, win: object, ROWS: int, width: int): 7 | self.rows = ROWS 8 | self.width = width 9 | self.node_width = width // ROWS 10 | self.win = win 11 | self.grid = [] 12 | self.start = None 13 | self.end = None 14 | 15 | self.make_grid() 16 | 17 | def reset_grid(self): 18 | self.start = None 19 | self.end = None 20 | self.make_grid() 21 | 22 | def get_clicked_pos(self, pos: tuple): 23 | """ 24 | Get the position of the node that was clicked in the grid. 25 | 26 | Args: 27 | pos (tuple): A tuple containing the x and y coordinates of the mouse click. 28 | rows (int): The number of rows in the grid. 29 | width (int): The width of the grid in pixels. 30 | 31 | Returns: 32 | tuple: A tuple containing the row and column indices of the clicked node. 33 | """ 34 | # Unpack the x and y coordinates of the mouse click 35 | y, x = pos 36 | 37 | # Calculate the row and column index of the clicked node 38 | row = y // self.node_width 39 | col = x // self.node_width 40 | 41 | return row, col 42 | 43 | def make_grid(self): 44 | """ 45 | Create a 2D list representing the grid of the maze. 46 | 47 | Args: 48 | rows (int): The number of rows in the grid. 49 | width (int): The width of the grid in pixels. 50 | 51 | Returns: 52 | List[List[Node]]: A 2D list of Node objects representing the grid. 53 | """ 54 | grid = [] 55 | 56 | # Iterate through the rows and columns of the grid 57 | for i in range(self.rows): 58 | grid.append([]) 59 | for j in range(self.rows): 60 | # Create a new Node object for the current position and 61 | # add it to the grid 62 | node = Node(i, j, self.node_width, self.rows) 63 | grid[i].append(node) 64 | 65 | self.grid = grid 66 | 67 | def clear_grid(self): 68 | """ 69 | Clear the grid by creating a new 2D list of Node objects, 70 | keeping the start, end, and obstacles nodes from the 71 | original grid. 72 | 73 | Args: 74 | current_grid (List[List[Node]]): The original grid. 75 | rows (int): The number of rows in the grid. 76 | width (int): The width of the grid in pixels. 77 | 78 | Returns: 79 | List[List[Node]]: A 2D list of Node objects representing the cleared grid. 80 | """ 81 | 82 | # Iterate through the rows and columns of the grid 83 | for i in range(self.rows): 84 | for j in range(self.rows): 85 | # If the current node is not the start, end, or an obstacle, create a new Node 86 | # object for the current position and add it to the grid 87 | if ( 88 | not self.grid[i][j].is_start() 89 | and not self.grid[i][j].is_end() 90 | and not self.grid[i][j].is_obstacle() 91 | ): 92 | self.grid[i][j].reset() 93 | 94 | def draw_grid_lines(self): 95 | """ 96 | Draw the lines separating the nodes in the grid. 97 | 98 | Args: 99 | None 100 | 101 | Returns: 102 | None 103 | """ 104 | for i in range(self.rows): 105 | # Draw a horizontal line at the top and bottom of each node 106 | pygame.draw.line( 107 | self.win, 108 | (128, 128, 128), 109 | (0, i * self.node_width), 110 | (self.width, i * self.node_width), 111 | ) 112 | for j in range(self.rows): 113 | # Draw a vertical line at the left and right of each node 114 | pygame.draw.line( 115 | self.win, 116 | (128, 128, 128), 117 | (j * self.node_width, 0), 118 | (j * self.node_width, self.width), 119 | ) 120 | 121 | def draw(self): 122 | """ 123 | Draw the grid and nodes on the window. 124 | 125 | Args: 126 | None 127 | 128 | Returns: 129 | None 130 | """ 131 | self.win.fill((255, 255, 255)) 132 | 133 | # Draw nodes on grid 134 | for row in self.grid: 135 | for node in row: 136 | node.draw(self.win) 137 | 138 | self.draw_grid_lines() 139 | 140 | pygame.display.update() 141 | 142 | def __getitem__(self, key): 143 | return self.grid[key] -------------------------------------------------------------------------------- /main/Node.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from pygame import Rect 3 | from Colors import COLORS 4 | 5 | 6 | # Define colors for different node states 7 | DEFAULT = COLORS.get("WHITE") 8 | CHECKED = COLORS.get("RED") 9 | UNCHECKED = COLORS.get("GREEN") 10 | OBSTACLE = COLORS.get("BLACK") 11 | START = COLORS.get("ORANGE") 12 | END = COLORS.get("TURQUOISE") 13 | PATH = COLORS.get("PURPLE") 14 | 15 | 16 | class Node: 17 | def __init__(self, row: int, col: int, width: int, total_rows: int): 18 | """ 19 | Initializes a node with its position, size, and color. 20 | 21 | Args: 22 | row (int): row index of the node in the grid 23 | col (int): column index of the node in the grid 24 | width (int): width of the node 25 | total_rows (int): total number of rows in the grid 26 | """ 27 | self.row = row 28 | self.col = col 29 | self.x = row * width 30 | self.y = col * width 31 | self.color = DEFAULT 32 | self.neighbors = [] 33 | self.width = width 34 | self.total_rows = total_rows 35 | self.rect = Rect(self.x, self.y, self.width, self.width) 36 | 37 | # Flag for whether the node has been checked 38 | self.been_checked = False 39 | 40 | # Color for checked nodes that have been checked multiple times 41 | self.checked_color = None 42 | 43 | def get_pos(self): 44 | """ 45 | Returns the position (row, column) of the node in the grid. 46 | 47 | Returns: 48 | tuple: (row, column) of the node in the grid 49 | """ 50 | return self.row, self.col 51 | 52 | # Checking node states 53 | def is_default(self): 54 | return self.color == DEFAULT 55 | 56 | def is_checked(self): 57 | return self.color == CHECKED 58 | 59 | def is_unchecked(self): 60 | return self.color == UNCHECKED 61 | 62 | def is_obstacle(self): 63 | return self.color == OBSTACLE 64 | 65 | def is_start(self): 66 | return self.color == START 67 | 68 | def is_end(self): 69 | return self.color == END 70 | 71 | # Setting node states 72 | def check(self): 73 | """ 74 | Marks the node as checked and sets the checked flag to True. 75 | """ 76 | self.color = CHECKED 77 | self.been_checked = True 78 | self.checked_color = CHECKED 79 | 80 | def uncheck(self): 81 | self.color = UNCHECKED 82 | 83 | def make_obstacle(self): 84 | self.color = OBSTACLE 85 | 86 | def make_start(self): 87 | self.color = START 88 | 89 | def make_end(self): 90 | self.color = END 91 | 92 | def make_path(self): 93 | self.color = PATH 94 | 95 | def make_color(self, color: tuple): 96 | """ 97 | Makes the node the specified color. 98 | 99 | Args: 100 | color (tuple): tuple of (R, G, B) values representing the 101 | desired color of the node 102 | 103 | Returns: 104 | None 105 | """ 106 | self.color = color 107 | 108 | def mult_check(self, dec: int): 109 | """ 110 | Changes the color of a checked node that has been checked multiple times. 111 | 112 | Args: 113 | dec (int): value to change the color by 114 | 115 | Returns: 116 | None 117 | """ 118 | if self.checked_color[0] > dec: 119 | self.color = (self.checked_color[0] - dec, 0, self.checked_color[2] + dec) 120 | self.checked_color = self.color 121 | else: 122 | self.color = self.checked_color 123 | 124 | def reset(self): 125 | self.color = DEFAULT 126 | self.been_checked = False 127 | self.checked_color = None 128 | 129 | # Visualization functions 130 | def draw(self, win: object): 131 | """ 132 | Draws the node on the given window. 133 | 134 | Args: 135 | win (pygame.Surface): window to draw the node on 136 | 137 | Returns: 138 | None 139 | """ 140 | pygame.draw.rect(win, self.color, self.rect) 141 | 142 | def update_neighbors(self, grid: list): 143 | """ 144 | Updates the neighbors of the node in the grid. 145 | 146 | Args: 147 | grid (List[List[Node]]): 2D list of nodes representing the grid 148 | 149 | Returns: 150 | None 151 | """ 152 | self.neighbors = [] 153 | 154 | # BOTTOM CELL 155 | if ( 156 | self.row < self.total_rows - 1 157 | and not grid[self.row + 1][self.col].is_obstacle() 158 | ): 159 | self.neighbors.append(grid[self.row + 1][self.col]) 160 | 161 | # TOP CELL 162 | if self.row > 0 and not grid[self.row - 1][self.col].is_obstacle(): 163 | self.neighbors.append(grid[self.row - 1][self.col]) 164 | 165 | # LEFT CELL 166 | if self.col > 0 and not grid[self.row][self.col - 1].is_obstacle(): 167 | self.neighbors.append(grid[self.row][self.col - 1]) 168 | 169 | # RIGHT CELL 170 | if ( 171 | self.col < self.total_rows - 1 172 | and not grid[self.row][self.col + 1].is_obstacle() 173 | ): 174 | self.neighbors.append(grid[self.row][self.col + 1]) 175 | 176 | def __lt__(self, other: object): 177 | """ 178 | Comparison operator for the priority queue. 179 | 180 | Args: 181 | other (Node): node to compare to 182 | 183 | Returns: 184 | bool: False (nodes are not meant to be sorted) 185 | """ 186 | return False 187 | -------------------------------------------------------------------------------- /main/Testing.py: -------------------------------------------------------------------------------- 1 | import time 2 | import csv 3 | from os import system, name, path 4 | 5 | from testing.AStar import a_star 6 | from testing.BeamSearch import beam_search 7 | from testing.BellmanFord import bell_ford 8 | from testing.BestFS import best_fs 9 | from testing.BFS import bfs 10 | from testing.bidirectional.AStar import bi_a_star 11 | from testing.bidirectional.BFS import bi_search 12 | from testing.BStar import b_star 13 | from testing.DFS import dfs 14 | from testing.Dijkstra import dijkstra 15 | from testing.FloydWarshall import floyd_warshall 16 | from testing.FloodFill import flood_fill 17 | from testing.FastMarching import fmm 18 | from testing.Fringe import fringe_search 19 | from testing.GBFS import gbfs 20 | from testing.GBLS import gbls 21 | from testing.IDAStar import ida_star 22 | from testing.IDDFS import iddfs 23 | from testing.JPS import jps 24 | from testing.LexicographicBFS import lbfs 25 | from testing.LPAStar import lpa_star 26 | from testing.RandomLIFO import rand_lifo 27 | from testing.RandomWalk import rand_walk 28 | from testing.SPFA import spfa 29 | from testing.ThetaStar import theta_star 30 | from testing.RP import get_unvisited_nodes 31 | 32 | from Algorithms import ALGORITHMS, run_algs 33 | from Node import Node 34 | 35 | 36 | def testing(grid: object): 37 | """ 38 | Main function for testing 39 | 40 | Args: 41 | grid (Grid): An object representing the current grid 42 | 43 | Returns: 44 | None 45 | """ 46 | 47 | # Clear grid of any potential markup 48 | grid.clear_grid() 49 | 50 | for row in grid.grid: 51 | for node in row: 52 | node.update_neighbors(grid.grid) 53 | 54 | animation: list = [ 55 | "[■□□□□□□□□□]", 56 | "[■■□□□□□□□□]", 57 | "[■■■□□□□□□□]", 58 | "[■■■■□□□□□□]", 59 | "[■■■■■□□□□□]", 60 | "[■■■■■■□□□□]", 61 | "[■■■■■■■□□□]", 62 | "[■■■■■■■■□□]", 63 | "[■■■■■■■■■□]", 64 | "[■■■■■■■■■■]", 65 | ] 66 | completion: int = 0 67 | 68 | # Clear the terminal of all text 69 | clear() 70 | 71 | print("Testing Algorithms...") 72 | 73 | # Creates a new CSV file using the current time 74 | with open( 75 | path.join( 76 | path.dirname(__file__), 77 | f"testing/results/{int(time.time())}.csv", 78 | ), 79 | "w", 80 | newline="", 81 | ) as myfile: 82 | wr = csv.writer(myfile, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL) 83 | 84 | wr.writerow( 85 | [ 86 | "Algorithm", 87 | "Local Run Time", 88 | "Times Nodes Checked", 89 | "Path Length", 90 | "General Data", 91 | ] 92 | ) 93 | algo_data: list = [ 94 | [ 95 | "", 96 | "", 97 | "", 98 | "", 99 | f"Total Open Nodes: {len(get_unvisited_nodes(grid.start))}", 100 | ] 101 | ] 102 | 103 | for alg in ALGORITHMS: 104 | # Clear grid after every run 105 | grid.clear_grid() 106 | 107 | for row in grid.grid: 108 | for node in row: 109 | node.update_neighbors(grid.grid) 110 | 111 | # Run and get statistics for ran algorithm 112 | start_time = time.perf_counter() 113 | num_visited_nodes, path_size = algorithm(grid, alg) 114 | end_time = time.perf_counter() 115 | 116 | # Used for handling Theta* turn points 117 | # ! Will most likely change in the future when more data is being collected 118 | # ! from each algorithm 119 | if type(path_size) == tuple: 120 | algo_data.append( 121 | [ 122 | alg, 123 | end_time - start_time, 124 | num_visited_nodes, 125 | path_size[1], 126 | f"Turn points: {path_size[0]}", 127 | ] 128 | ) 129 | else: 130 | algo_data.append( 131 | [alg, end_time - start_time, num_visited_nodes, path_size] 132 | ) 133 | 134 | # Progress markup 135 | # ? Unreplicable error with printing the completion animation 136 | completion += 1 137 | index = int((completion / len(ALGORITHMS)) * len(animation)) 138 | 139 | if alg == ALGORITHMS[-1]: 140 | break 141 | 142 | print( 143 | f" {int(100 * (completion / len(ALGORITHMS)))}% {animation[index]} \r", 144 | flush=True, 145 | end="", 146 | ) 147 | 148 | print(f"100% {animation[9]} \rTesting Finished!\n", flush=True, end="") 149 | 150 | wr.writerows(algo_data) 151 | grid.clear_grid() 152 | 153 | 154 | def clear(): 155 | # For Windows 156 | if name == "nt": 157 | _ = system("cls") 158 | # For MacOS and Linux 159 | else: 160 | _ = system("clear") 161 | 162 | 163 | def algorithm(grid: object, algorithm: str): 164 | """ 165 | Select and run the specified pathfinding algorithm. 166 | 167 | Args: 168 | grid (Grid): An object representing the current grid 169 | algorithm (str): name of the algorithm to use 170 | 171 | Returns: 172 | int: The number of nodes visited in the algorithm 173 | int or tuple: Length of path found; number of turning points 174 | """ 175 | return eval(run_algs[algorithm]) 176 | -------------------------------------------------------------------------------- /main/algorithms/AStar.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import reconstruct_path, heuristic, check 4 | 5 | 6 | def a_star(grid: object): 7 | """ 8 | Perform an A* search from start to end. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | 17 | # Initialize counters and sets 18 | count = 0 19 | open_set = PriorityQueue() 20 | open_set.put((0, count, grid.start)) 21 | came_from = {} 22 | 23 | # Initialize dictionaries to store the g scores for each node 24 | g_score = {node: float("inf") for row in grid.grid for node in row} 25 | g_score[grid.start] = 0 26 | 27 | # Initialize a set to store the nodes in the open set 28 | open_set_hash = {grid.start} 29 | 30 | # Initialize a flag to track whether the search should continue 31 | run = True 32 | 33 | # Perform the search 34 | while not open_set.empty() and run: 35 | # Check for exit events 36 | run = check(pygame.event.get(), run) 37 | 38 | # Get the current node from the open set 39 | _, _, current = open_set.get() 40 | open_set_hash.remove(current) 41 | 42 | # End the search if the current node is the end node 43 | if current.is_end(): 44 | reconstruct_path(came_from, grid.end, grid.draw) 45 | grid.end.make_end() 46 | break 47 | 48 | # Check the neighbors of the current node 49 | for neighbor in current.neighbors: 50 | # Calculate the tentative g score for the neighbor 51 | temp_g_score = g_score[current] + 1 52 | 53 | # Update the g score for the neighbor if the tentative g score is lower 54 | if temp_g_score < g_score[neighbor]: 55 | came_from[neighbor] = current 56 | g_score[neighbor] = temp_g_score 57 | 58 | # Add the neighbor to the open set if it is not already there 59 | if neighbor not in open_set_hash: 60 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.end) 61 | count += 1 62 | open_set.put((f_score, count, neighbor)) 63 | open_set_hash.add(neighbor) 64 | 65 | if not neighbor.is_start() and not neighbor.is_end(): 66 | neighbor.uncheck() 67 | 68 | # Update the screen with the search progress 69 | grid.draw() 70 | 71 | # Check the current node if it is not the start node 72 | if not current.is_start(): 73 | current.check() 74 | -------------------------------------------------------------------------------- /main/algorithms/BFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import reconstruct_path, check, markup 3 | 4 | 5 | def bfs(grid: object): 6 | """ 7 | Perform a breadth-first search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid 11 | 12 | Returns: 13 | None: The function updates the screen with the search progress and path. 14 | """ 15 | 16 | # Initialize a list to store the nodes to visit (queue) 17 | nodes = [] 18 | nodes.append(grid.start) 19 | 20 | # Initialize a dictionary to store the predecessor of each node 21 | previous = {} 22 | 23 | # Initialize flags to track the search status 24 | found = False 25 | run = True 26 | 27 | # Perform the search 28 | while nodes and not found and run: 29 | # Check for exit events 30 | run = check(pygame.event.get(), run) 31 | 32 | # Get the next node to visit 33 | current = nodes.pop(0) 34 | 35 | # Skip the node if it has already been checked 36 | if current.is_checked(): 37 | continue 38 | 39 | # Draw the current node 40 | markup(grid.draw, current) 41 | 42 | # Check the neighbors of the current node 43 | for neighbor in current.neighbors: 44 | # Skip the neighbor if it has already been checked 45 | if not neighbor.is_checked(): 46 | # End the search if the neighbor is the end node 47 | if neighbor.is_end(): 48 | previous[neighbor] = current 49 | found = True 50 | reconstruct_path(previous, grid.end, grid.draw) 51 | break 52 | # Add the neighbor to the list of nodes to visit if it is not the end node 53 | else: 54 | previous[neighbor] = current 55 | nodes.append(neighbor) 56 | 57 | # Uncheck the child if it is not the start or end node 58 | # for markup 59 | if not neighbor.is_start() and not neighbor.is_end(): 60 | neighbor.uncheck() 61 | -------------------------------------------------------------------------------- /main/algorithms/BStar.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import reconstruct_path, check, heuristic 4 | 5 | 6 | def b_star(grid: object): 7 | """ 8 | Perform a B* search from start to end. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | 17 | # Initialize counters and sets 18 | count = 0 19 | open_set = PriorityQueue() 20 | open_set.put((0, count, grid.start)) 21 | came_from = {} 22 | 23 | # Initialize dictionaries to store the g scores for each node 24 | g_score = {node: float("inf") for row in grid.grid for node in row} 25 | g_score[grid.start] = 0 26 | 27 | # Initialize a set to store the nodes in the open set 28 | open_set_hash = {grid.start} 29 | 30 | # Initialize a flag to track whether the search should continue 31 | run = True 32 | 33 | # Perform the search 34 | while not open_set.empty() and run: 35 | # Check for exit events 36 | run = check(pygame.event.get(), run) 37 | 38 | # Get the current node from the open set 39 | _, _, current = open_set.get() 40 | open_set_hash.remove(current) 41 | 42 | # End the search if the current node is the end node 43 | if current.is_end(): 44 | reconstruct_path(came_from, grid.end, grid.draw) 45 | grid.end.make_end() 46 | break 47 | 48 | # Check the neighbors of the current node 49 | for neighbor in current.neighbors: 50 | # Calculate the tentative g score for the neighbor 51 | temp_g_score = g_score[current] + 1 52 | 53 | # Update the g and f scores for the neighbor if the 54 | # tentative g score is lower 55 | if temp_g_score < g_score[neighbor]: 56 | came_from[neighbor] = current 57 | g_score[neighbor] = temp_g_score 58 | 59 | # Add the neighbor to the open set if it is not already there 60 | if neighbor not in open_set_hash: 61 | f_score = temp_g_score + heuristic("d_manhattan", neighbor, grid.end) 62 | count += 1 63 | open_set.put((f_score, count, neighbor)) 64 | open_set_hash.add(neighbor) 65 | 66 | if not neighbor.is_start() and not neighbor.is_end(): 67 | neighbor.uncheck() 68 | 69 | # Update the screen with the search progress 70 | grid.draw() 71 | 72 | # Check the current node if it is not the start node 73 | if not current.is_start(): 74 | current.check() 75 | -------------------------------------------------------------------------------- /main/algorithms/BeamSearch.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | from .RP import reconstruct_path, heuristic, check, markup 4 | 5 | 6 | def beam_search(grid: object, beam_size: int): 7 | """ 8 | Perform a beam search from start to end with a given beam size. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | beam_size (int): The maximum number of nodes to consider at each step. 13 | 14 | Returns: 15 | None: The function updates the screen with the search progress and path. 16 | """ 17 | 18 | # Initialize the beam with the root node 19 | beam = [(0, grid.start)] 20 | 21 | # Initialize a dictionary to store the previous nodes for each node 22 | previous = {} 23 | previous[grid.start] = grid.start 24 | 25 | # Initialize a flag to track whether the search should continue 26 | run = True 27 | 28 | # Perform the search 29 | while beam and run: 30 | # Check for exit events 31 | run = check(pygame.event.get(), run) 32 | 33 | # Get the most promising node from the beam 34 | _, current = heapq.heappop(beam) 35 | 36 | # End the search if the current node is the end node 37 | if current.is_end(): 38 | reconstruct_path(previous, grid.end, grid.draw) 39 | grid.end.make_end() 40 | break 41 | 42 | # Draw the current node 43 | markup(grid.draw, current) 44 | 45 | # Expand the current node and add its children to the beam 46 | children = current.neighbors 47 | 48 | # Remove the previous node from the list of children 49 | if previous[current] in children: 50 | children.remove(previous[current]) 51 | 52 | # Add the children to the beam 53 | for child in children: 54 | # Skip the child if it has already been checked 55 | if not child.is_checked(): 56 | previous[child] = current 57 | heapq.heappush(beam, (heuristic("manhattan", child, grid.end), child)) 58 | 59 | # Uncheck the child if it is not the start or end node 60 | # for markup 61 | if not child.is_start() and not child.is_end(): 62 | child.uncheck() 63 | 64 | # Trim the beam to the desired size 65 | beam = beam[:beam_size] 66 | -------------------------------------------------------------------------------- /main/algorithms/BellmanFord.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import Queue 3 | from .RP import reconstruct_path, get_unvisited_nodes, check, markup 4 | 5 | 6 | def bell_ford(grid: object, accuracy: float): 7 | """ 8 | Perform a Bellman-Ford search from start to end with a given accuracy. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | accuracy (float): The fraction of nodes to visit at each step. (0, 1] 13 | 14 | Returns: 15 | None: The function updates the screen with the search progress and path. 16 | """ 17 | 18 | # Get the list of unvisited nodes 19 | nodes = get_unvisited_nodes(grid.start) 20 | 21 | # Initialize dictionaries to store the distance and predecessor for each node 22 | distance = {node: float("inf") for node in nodes} 23 | predecessor = {} 24 | 25 | # Set the distance of the start node to 0 26 | distance[grid.start] = 0 27 | 28 | # Calculate the number of nodes to visit at each step 29 | counter = int((len(nodes) - 1) * accuracy) 30 | 31 | # Initialize a flag to track whether the search should continue 32 | run = True 33 | 34 | # Perform the search 35 | while counter >= 0 and run: 36 | # Check for exit events 37 | run = check(pygame.event.get(), run) 38 | 39 | # Visit each node 40 | for current in nodes: 41 | # Check for exit events 42 | run = check(pygame.event.get(), run) 43 | 44 | # Exit the loop if the user interrupted 45 | if not run: 46 | break 47 | 48 | # Draw the current node 49 | markup(grid.draw, current) 50 | 51 | # Check the neighbors of the current node 52 | for neighbor in current.neighbors: 53 | # Update the distance and predecessor for the neighbor if the 54 | # current distance is shorter 55 | if distance[current] + 1 < distance[neighbor]: 56 | distance[neighbor] = distance[current] + 1 57 | predecessor[neighbor] = current 58 | 59 | # Uncheck the neighbor if it is not the start or end node 60 | # for markup 61 | if not neighbor.is_end() and not neighbor.is_start(): 62 | neighbor.uncheck() 63 | 64 | # Decrement the counter 65 | counter -= 1 66 | 67 | # Draw the path from the end node to the start node 68 | if run: 69 | reconstruct_path(predecessor, grid.end, grid.draw) 70 | -------------------------------------------------------------------------------- /main/algorithms/BestFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import heuristic, check, markup 4 | 5 | 6 | def get_checked_neighbors(node: object): 7 | """ 8 | Get the neighbors of a node that have been checked or are the start node. 9 | 10 | Args: 11 | node (Node): The node to check. 12 | 13 | Returns: 14 | List[Node]: The list of checked or start neighbors. 15 | """ 16 | 17 | checked = [] 18 | for neighbor in node.neighbors: 19 | if neighbor.is_checked() or neighbor.is_start(): 20 | checked.append(neighbor) 21 | return checked 22 | 23 | 24 | def reconstruct_path(current: object, costs: dict, draw: object): 25 | """ 26 | Reconstruct the path from the end node to the start node. 27 | 28 | Args: 29 | current (Node): The current node in the search. 30 | costs (Dict[Node, int]): The cost of reaching each node from the start. 31 | draw (function): A function used to draw the search on the screen. 32 | 33 | Returns: 34 | None: The function updates the screen with the path. 35 | """ 36 | 37 | # Continue until the current node is the start node 38 | while not current.is_start(): 39 | # Find the neighbor with the lowest cost 40 | current = min(get_checked_neighbors(current), key=lambda x: costs[x]) 41 | 42 | # Break if the current node is the start node 43 | if current.is_start(): 44 | break 45 | 46 | # Draw the path to the current node 47 | current.make_path() 48 | draw() 49 | 50 | 51 | def best_fs(grid: object): 52 | """ 53 | Perform a best-first search from start to end. 54 | 55 | Args: 56 | grid (Grid): An object representing the current grid 57 | 58 | Returns: 59 | None: The function updates the screen with the search progress and path. 60 | """ 61 | 62 | # Initialize the priority queue with the start node 63 | queue = PriorityQueue() 64 | queue.put((0, 0, grid.start)) 65 | count = 0 66 | 67 | # Initialize a dictionary to store the cost of reaching each node from the start 68 | costs = {grid.start: 0} 69 | 70 | # Initialize a flag to track whether the search should continue 71 | run = True 72 | 73 | # Perform the search 74 | while not queue.empty() and run: 75 | # Check for exit events 76 | run = check(pygame.event.get(), run) 77 | 78 | # Get the node with the lowest cost from the queue 79 | cost, _, current = queue.get() 80 | 81 | # End the search if the current node is the end node 82 | if current.is_end(): 83 | reconstruct_path(current, costs, grid.draw) 84 | grid.end.make_end() 85 | break 86 | 87 | # Draw the current node 88 | markup(grid.draw, current) 89 | 90 | # Check the neighbors of the current node 91 | for neighbor in current.neighbors: 92 | # Calculate the cost of reaching the neighbor 93 | cost = costs[current] + 1 94 | 95 | # Update the cost in the dictionary if it is lower than the current value 96 | if neighbor not in costs or cost < costs[neighbor]: 97 | costs[neighbor] = cost 98 | # Add the neighbor to the queue with the calculated cost as the priority 99 | count += 1 100 | queue.put( 101 | ( 102 | cost + heuristic("manhattan", neighbor, grid.end), 103 | count, 104 | neighbor, 105 | ) 106 | ) 107 | 108 | # Uncheck the child if it is not the start or end node 109 | # for markup 110 | if not neighbor.is_start() and not neighbor.is_end(): 111 | neighbor.uncheck() 112 | -------------------------------------------------------------------------------- /main/algorithms/DFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import reconstruct_path, check, markup 3 | 4 | 5 | def dfs(grid: object): 6 | """ 7 | Perform a depth-first search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid 11 | 12 | Returns: 13 | None: The function updates the screen with the search progress and path. 14 | """ 15 | 16 | # Initialize a stack to store the nodes to visit 17 | stack = [grid.start] 18 | 19 | # Initialize a dictionary to store the predecessor of each node 20 | previous = {} 21 | 22 | # Initialize flags to track the search status 23 | found = False 24 | run = True 25 | 26 | # Perform the search 27 | while len(stack) and not found and run: 28 | # Check for exit events 29 | run = check(pygame.event.get(), run) 30 | 31 | # Get the next node to visit 32 | current = stack.pop() 33 | 34 | # Skip the node if it has already been checked 35 | if current.is_checked(): 36 | continue 37 | 38 | # Draw the current node 39 | markup(grid.draw, current) 40 | 41 | # Check the neighbors of the current node 42 | for neighbor in current.neighbors: 43 | # Skip the neighbor if it has already been checked 44 | if not neighbor.is_checked(): 45 | # End the search if the neighbor is the end node 46 | if neighbor.is_end(): 47 | previous[neighbor] = current 48 | found = True 49 | reconstruct_path(previous, grid.end, grid.draw) 50 | break 51 | # Add the neighbor to the stack of nodes to visit if it is not the end node 52 | else: 53 | previous[neighbor] = current 54 | stack.append(neighbor) 55 | # Uncheck the neighbor if it is not the start or end node 56 | # for markup 57 | if not neighbor.is_start(): 58 | neighbor.uncheck() 59 | 60 | -------------------------------------------------------------------------------- /main/algorithms/Dijkstra.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import reconstruct_path, check, markup 4 | 5 | 6 | def dijkstra(grid: object): 7 | """ 8 | Perform Dijkstra's algorithm from start to end. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | 17 | # Initialize a priority queue to store the nodes to visit 18 | queue = PriorityQueue() 19 | 20 | # Set up the node values 21 | distance = {node: float("inf") for row in grid.grid for node in row} 22 | distance[grid.start] = 0 23 | 24 | # Holds the path from start to end 25 | previous = {} 26 | 27 | # Add the start node to the priority queue 28 | queue.put((0, 0, grid.start)) 29 | count = 0 30 | 31 | # Initialize a flag to track the search status 32 | run = True 33 | 34 | # Perform the search 35 | while not queue.empty() and run: 36 | # Check for exit events 37 | run = check(pygame.event.get(), run) 38 | 39 | # Get the next node to visit 40 | _, _, current_min = queue.get() 41 | 42 | # End the search if the current node is the end node 43 | if current_min.is_end(): 44 | reconstruct_path(previous, grid.end, grid.draw) 45 | break 46 | 47 | # Draw the current node 48 | markup(grid.draw, current_min) 49 | 50 | # Check the neighbors of the current node 51 | for neighbor in current_min.neighbors: 52 | # Don't recheck for performance 53 | if not neighbor.is_checked(): 54 | # edges between vertecies are not weighted 55 | # (using constant weight of 1) 56 | temp_value = distance[current_min] + 1 57 | if temp_value < distance[neighbor]: 58 | distance[neighbor] = temp_value 59 | previous[neighbor] = current_min 60 | 61 | # Add the neighbor to the priority queue 62 | count += 1 63 | queue.put((distance[neighbor], count, neighbor)) 64 | 65 | # Uncheck the neighbor if it is not the start or end node 66 | # for markup 67 | if not neighbor.is_end() and not neighbor.is_start(): 68 | neighbor.uncheck() 69 | -------------------------------------------------------------------------------- /main/algorithms/FastMarching.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | from .RP import check, reconstruct_path, markup 4 | 5 | def fmm(grid: object): 6 | """ 7 | Performs the Fast Marching Method on a uniform cost grid 8 | to solve the shortest path problem. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | # Initialize dictionary to store the cost for each node 17 | costs = {node: float("inf") for row in grid.grid for node in row} 18 | costs[grid.start] = 0 19 | 20 | # Record all the unvisited nodes in the grid 21 | far = [node for row in grid.grid for node in row if not node.is_start()] 22 | 23 | # Initialize the visited priority queue 24 | considered = [] 25 | heapq.heappush(considered, (0, grid.start)) 26 | 27 | came_from = {} 28 | 29 | run = True 30 | 31 | # Perform the search 32 | while considered and run: 33 | # Check for exit events 34 | run = check(pygame.event.get(), run) 35 | 36 | # Get the current node from the open set 37 | ccost, current = heapq.heappop(considered) 38 | 39 | # End the search if the current node is the end node 40 | if current.is_end(): 41 | reconstruct_path(came_from, grid.end, grid.draw) 42 | break 43 | 44 | # Draw the current node 45 | markup(grid.draw, current) 46 | 47 | # Check the neighbors of the current node 48 | for neighbor in current.neighbors: 49 | # If the neighbor hasn't been checked 50 | if neighbor not in considered and not neighbor.is_checked(): 51 | # Edges between nodes are not weighted 52 | # (using constant weight of 1) 53 | cost = ccost + 1 54 | if cost < costs[neighbor]: 55 | costs[neighbor] = cost 56 | came_from[neighbor] = current 57 | 58 | # If neighbor hasn't been checked or been visited before 59 | # add it to the queue 60 | if neighbor in far: 61 | heapq.heappush(considered, (cost, neighbor)) 62 | far.remove(neighbor) 63 | 64 | # Uncheck the neighbor if it is not the start or end node 65 | # for markup 66 | if not neighbor.is_start() and not neighbor.is_end(): 67 | neighbor.uncheck() -------------------------------------------------------------------------------- /main/algorithms/FloodFill.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | from .RP import check, markup 4 | 5 | def flood_fill(grid: object): 6 | # Create a dictionary to store distance values 7 | distances = { node: float("inf") for row in grid for node in row } 8 | distances[grid.end] = 0 9 | 10 | # Start from end 11 | queue = [(0, grid.end)] 12 | run = True 13 | 14 | while queue and run: 15 | # Check for exit events 16 | run = check(pygame.event.get(), run) 17 | 18 | dist, current = heapq.heappop(queue) 19 | 20 | if current.is_checked(): 21 | continue 22 | 23 | if current.is_start(): 24 | break 25 | 26 | markup(grid.draw, current) 27 | 28 | # Check every neighbor of the current cell and 29 | # add it to the queue if it hasn't been checked before 30 | for neighbor in current.neighbors: 31 | if not neighbor.is_checked(): 32 | distances[neighbor] = dist + 1 33 | heapq.heappush(queue, (dist + 1, neighbor)) 34 | 35 | if not neighbor.is_start() and not neighbor.is_end(): 36 | neighbor.uncheck() 37 | 38 | # Create path if the algorithm wasn't stopped 39 | if run and distances[grid.start] != float("inf"): 40 | current = grid.start 41 | found = False 42 | while not found: 43 | best = (float("inf"), None) 44 | for neighbor in current.neighbors: 45 | if neighbor.is_end(): 46 | found = True 47 | break 48 | if distances[neighbor] < best[0]: 49 | best = (distances[neighbor], neighbor) 50 | if not found: 51 | best[1].make_path() 52 | grid.draw() 53 | current = best[1] 54 | -------------------------------------------------------------------------------- /main/algorithms/Fringe.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import heuristic, markup, check 3 | 4 | def reconstruct_path(cache: dict, current: object, draw: object): 5 | """ 6 | Iterates through the cache and constructs the path using the data stored. 7 | 8 | Args: 9 | came_from (Dict[Node, Node]): A dictionary containing the nodes traversed 10 | during the pathfinding algorithm. 11 | current (Node): The end node of the path. 12 | draw (function): A function for drawing the maze. 13 | 14 | Returns: 15 | None 16 | """ 17 | while not current.is_start(): 18 | if not current.is_end(): 19 | current.make_path() 20 | _, current = cache[current] 21 | draw() 22 | 23 | def fringe_search(grid: object): 24 | """ 25 | Performs a Fringe Search over the grid. 26 | 27 | Args: 28 | grid (Grid): An object representing the current grid 29 | 30 | Returns: 31 | None: The function updates the screen with the search progress and path. 32 | """ 33 | # Initialize data structures 34 | fringe = [] 35 | cache = {} 36 | cache[grid.start] = (0, None) 37 | fringe.append(grid.start) 38 | 39 | f_limit = heuristic("manhattan", grid.start, grid.end) 40 | 41 | found = False 42 | run = True 43 | 44 | while not found and run and fringe: 45 | # Check for exit events 46 | run = check(pygame.event.get(), run) 47 | 48 | f_min = float("inf") 49 | 50 | # Check every node in the fringe 51 | for node in fringe: 52 | g, parent = cache[node] 53 | 54 | # Get f-score for current node 55 | f = g + heuristic("manhattan", node, grid.end) 56 | 57 | # Check if the f-score is greater than the allowed limit 58 | if f > f_limit: 59 | f_min = min(f, f_min) 60 | continue 61 | if node.is_end(): 62 | reconstruct_path(cache, node, grid.draw) 63 | found = True 64 | break 65 | 66 | markup(grid.draw, node) 67 | 68 | for child in node.neighbors: 69 | g_child = g + 1 70 | # If the child node has already been seen 71 | if child in cache: 72 | g_cached, c_parent = cache[child] 73 | if g_child >= g_cached: 74 | continue 75 | 76 | if child in fringe: 77 | fringe.remove(child) 78 | fringe.append(child) 79 | cache[child] = (g_child, node) 80 | 81 | if not child.is_start() and not child.is_end(): 82 | child.uncheck() 83 | fringe.remove(node) 84 | f_limit = f_min -------------------------------------------------------------------------------- /main/algorithms/GBFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import reconstruct_path, heuristic, check, markup 4 | 5 | 6 | def gbfs(grid: object): 7 | """ 8 | Perform a greedy best-first search from start to end. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | 17 | # Initialize priority queue with the start node 18 | Q = PriorityQueue() 19 | Q.put((heuristic("manhattan", grid.start, grid.end), 0, grid.start)) 20 | 21 | # Initialize counters and flags 22 | counter = 0 23 | run = True 24 | found = False 25 | 26 | # Initialize a dictionary to store the previous nodes for each node 27 | previous = {} 28 | 29 | # Perform the search 30 | while not Q.empty() and run and not found: 31 | # Check for exit events 32 | run = check(pygame.event.get(), run) 33 | 34 | # Get the current node from the queue 35 | _, _, current = Q.get() 36 | 37 | # Skip if the node has already been checked 38 | if current.is_checked(): 39 | continue 40 | 41 | # Draw the current node 42 | markup(grid.draw, current) 43 | 44 | # Check the neighbors of the current node 45 | for neighbor in current.neighbors: 46 | # Skip if the neighbor has already been checked 47 | if not neighbor.is_checked(): 48 | # End the search if the neighbor is the end node 49 | if neighbor.is_end(): 50 | previous[neighbor] = current 51 | found = True 52 | reconstruct_path(previous, grid.end, grid.draw) 53 | break 54 | 55 | # Markup for drawing neighbor 56 | if not neighbor.is_start() and not neighbor.is_end(): 57 | neighbor.uncheck() 58 | 59 | # Add the neighbor to the queue 60 | counter += 1 61 | distance = heuristic("manhattan", neighbor, grid.end) 62 | previous[neighbor] = current 63 | Q.put((distance, counter, neighbor)) 64 | -------------------------------------------------------------------------------- /main/algorithms/GBLS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from .RP import reconstruct_path, heuristic, check, markup 4 | 5 | 6 | def gbls(grid: object): 7 | """ 8 | Modified version of the greedy best-first search algorithm that explores neighbors in the direction of the last chosen node 9 | as long as the estimated distance to the goal is shorter than the current node. 10 | 11 | Args: 12 | grid (Grid): An object representing the current grid 13 | 14 | Returns: 15 | None 16 | """ 17 | 18 | # Initialize the priority queue with the starting node 19 | Q = PriorityQueue() 20 | Q.put((heuristic("manhattan", grid.start, grid.end), 0, grid.start)) 21 | 22 | # Initialize the counter and flags 23 | counter = 0 24 | run = True 25 | found = False 26 | 27 | previous = {} 28 | last_direction = None 29 | 30 | # Continue the search as long as there are nodes in the queue and the search 31 | # has not been stopped or the goal has not been found 32 | while not Q.empty() and run and not found: 33 | # Check for events that may stop the search 34 | run = check(pygame.event.get(), run) 35 | 36 | _, _, current = Q.get() 37 | 38 | if current.is_checked(): 39 | continue 40 | 41 | markup(grid.draw, current) 42 | 43 | # Choose the neighbors in the last direction first, 44 | # if a direction has been chosen 45 | if last_direction: 46 | neighbors = [ 47 | n 48 | for n in current.neighbors 49 | if n.get_pos()[0] - current.get_pos()[0] == last_direction[0] 50 | and n.get_pos()[1] - current.get_pos()[1] == last_direction[1] 51 | ] 52 | 53 | # Add the other neighbors to the list 54 | neighbors += [n for n in current.neighbors if n not in neighbors] 55 | # If no direction has been chosen, explore all neighbors 56 | else: 57 | neighbors = current.neighbors 58 | 59 | for neighbor in neighbors: 60 | if not neighbor.is_checked(): 61 | if neighbor.is_end(): 62 | previous[neighbor] = current 63 | found = True 64 | reconstruct_path(previous, grid.end, grid.draw) 65 | break 66 | 67 | # Markup for drawing neighbor 68 | if not neighbor.is_start() and not neighbor.is_end(): 69 | neighbor.uncheck() 70 | 71 | counter += 1 72 | distance = heuristic("manhattan", neighbor, grid.end) 73 | 74 | # Add the neighbor to the queue with the estimated 75 | # distance as the priority 76 | previous[neighbor] = current 77 | Q.put((distance, counter, neighbor)) 78 | 79 | # Save the direction to the neighbor 80 | last_direction = ( 81 | neighbor.get_pos()[0] - current.get_pos()[0], 82 | neighbor.get_pos()[1] - current.get_pos()[1], 83 | ) 84 | -------------------------------------------------------------------------------- /main/algorithms/IDAStar.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import heuristic, markup, check 3 | 4 | 5 | def reconstruct_path(draw: object, path: list): 6 | """ 7 | Reconstructs the path taken by the search algorithm and marks the 8 | path on the grid. 9 | 10 | Args: 11 | draw (function): The draw function to update the grid display. 12 | path (list): The list of nodes representing the path taken by 13 | the search algorithm. 14 | 15 | Returns: 16 | None 17 | """ 18 | for node in path: 19 | if not node.is_start() and not node.is_end(): 20 | node.make_path() 21 | draw() 22 | 23 | 24 | def ida_star(grid: object): 25 | """ 26 | Implements the IDA* search algorithm. 27 | 28 | Args: 29 | grid (Grid): An object representing the current grid 30 | 31 | Returns: 32 | None 33 | """ 34 | bound = heuristic("manhattan", grid.start, grid.end) 35 | 36 | # Stack for path being searched 37 | path = [grid.start] 38 | 39 | run = True 40 | 41 | while True and run: 42 | run = check(pygame.event.get(), run) 43 | 44 | result = search(grid.draw, path, 0, bound, grid.end) 45 | 46 | if result is True: 47 | path.reverse() 48 | reconstruct_path(grid.draw, path) 49 | break 50 | if result == float("inf"): 51 | break 52 | 53 | bound = result 54 | 55 | 56 | def search(draw: object, path: list, g: int, bound: float, end: object): 57 | """ 58 | Recursive search function used by the IDA* algorithm. 59 | 60 | Args: 61 | draw (function): The draw function to update the grid display. 62 | path (list): The list of nodes representing the path taken by the 63 | search algorithm. 64 | g (int): The cost of the path represented by the list of nodes. 65 | bound (float): The bound on the cost of the path. 66 | end (Node): The ending node. 67 | Returns: 68 | True (boolean): end was reached 69 | f (float): g-score + heuristic is greater than current depth (bound) 70 | min_val (float): minimum value for the path 71 | """ 72 | # Get last node in path 73 | node = path[-1] 74 | 75 | # Calculate the f-score 76 | f = g + heuristic("manhattan", node, end) 77 | 78 | if f > bound: 79 | return f 80 | if node.is_end(): 81 | return True 82 | 83 | markup(draw, node) 84 | 85 | min_val = float("inf") 86 | for neighbor in node.neighbors: 87 | if neighbor not in path: 88 | if not neighbor.is_start() and not neighbor.is_end(): 89 | neighbor.uncheck() 90 | 91 | path.append(neighbor) 92 | result = search(draw, path, g + 1, bound, end) 93 | 94 | if result is True: 95 | return True 96 | if result < min_val: 97 | min_val = result 98 | 99 | path.pop() 100 | return min_val 101 | -------------------------------------------------------------------------------- /main/algorithms/IDDFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import check, markup, reconstruct_path 3 | 4 | 5 | def depth_limit(draw: object, current: object, depth: int, visited: set, path: dict): 6 | """ 7 | Recursive DFS with a depth limit. 8 | 9 | Args: 10 | draw (function): Function to draw the current state of the search. 11 | current (Node): Current node being visited. 12 | end (Node): Goal node. 13 | depth (int): Current depth of the search. 14 | visited (set): Set of nodes already visited. 15 | path (dict): Dictionary of parent nodes, used to reconstruct the path. 16 | 17 | Returns: 18 | dict: Dictionary of parent nodes, used to reconstruct the path. 19 | Returns an empty dictionary if no path is found. 20 | """ 21 | if depth < 0: 22 | return {} 23 | if current.is_end(): 24 | return path 25 | 26 | visited.add(current) 27 | 28 | markup(draw, current) 29 | 30 | for neighbor in current.neighbors: 31 | if neighbor not in visited: 32 | if not neighbor.is_start() and not neighbor.is_end(): 33 | neighbor.uncheck() 34 | 35 | path[neighbor] = current 36 | 37 | result = depth_limit(draw, neighbor, depth - 1, visited, path) 38 | 39 | if result: 40 | return result 41 | 42 | return {} 43 | 44 | 45 | def iddfs(grid: object, length: int): 46 | """ 47 | Iterative deepening DFS. 48 | 49 | Args: 50 | grid (Grid): An object representing the current grid 51 | length (int): Length of the grid. 52 | 53 | Returns: 54 | None 55 | """ 56 | run = True 57 | 58 | for depth in range(0, length): 59 | if not run: 60 | break 61 | 62 | run = check(pygame.event.get(), run) 63 | 64 | visited = set() 65 | path = depth_limit(grid.draw, grid.start, depth, visited, {grid.start: None}) 66 | 67 | if path: 68 | reconstruct_path(path, grid.end, grid.draw) 69 | break 70 | -------------------------------------------------------------------------------- /main/algorithms/JPS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import heuristic, check, markup, reconstruct_path 3 | 4 | 5 | def jps(grid: object): 6 | """ 7 | Perform an Jump Point Search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid 11 | 12 | Returns: 13 | None: The function updates the screen with the search progress and path. 14 | """ 15 | open_set = set([grid.start]) 16 | closed_set = set() 17 | 18 | came_from = {} 19 | 20 | g_values = {grid.start: 0} 21 | f_values = {grid.start: heuristic("manhattan", grid.start, grid.end)} 22 | 23 | run = True 24 | 25 | while open_set and run: 26 | # Check for exit events 27 | run = check(pygame.event.get(), run) 28 | 29 | # Find the node with the lowest f-value in the open set. 30 | current = min(open_set, key=lambda node: f_values[node]) 31 | 32 | if current.is_end(): 33 | reconstruct_path(came_from, grid.end, grid.draw) 34 | break 35 | 36 | # Move current from open set to closed set 37 | open_set.remove(current) 38 | closed_set.add(current) 39 | 40 | markup(grid.draw, current) 41 | 42 | for neighbor in current.neighbors: 43 | # Check if neighbor is already closed 44 | if neighbor in closed_set: 45 | continue 46 | 47 | if neighbor not in open_set: 48 | g_value = g_values[current] + heuristic("manhattan", current, neighbor) 49 | 50 | g_values[neighbor] = g_value 51 | f_values[neighbor] = g_value + heuristic( 52 | "manhattan", neighbor, grid.end 53 | ) 54 | 55 | came_from[neighbor] = current 56 | open_set.add(neighbor) 57 | 58 | if not neighbor.is_start() and not neighbor.is_end(): 59 | neighbor.uncheck() 60 | -------------------------------------------------------------------------------- /main/algorithms/LPAStar.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | from .RP import heuristic, check 4 | 5 | 6 | def get_checked_neighbors(node: object): 7 | """ 8 | Get the neighbors of a node that have been checked or are the start node. 9 | 10 | Args: 11 | node (Node): The node to check. 12 | 13 | Returns: 14 | List[Node]: The list of checked or start neighbors. 15 | """ 16 | 17 | checked = [] 18 | for neighbor in node.neighbors: 19 | if neighbor.is_checked() or neighbor.is_start(): 20 | checked.append(neighbor) 21 | return checked 22 | 23 | 24 | def reconstruct_path(current: object, costs: dict, draw: object): 25 | """ 26 | Reconstruct the path from the end node to the start node. 27 | 28 | Args: 29 | current (Node): The current node in the search. 30 | costs (Dict[Node, int]): The cost of reaching each node from the start. 31 | draw (function): A function used to draw the search on the screen. 32 | 33 | Returns: 34 | None: The function updates the screen with the path. 35 | """ 36 | 37 | # Continue until the current node is the start node 38 | while not current.is_start(): 39 | # Find the neighbor with the lowest cost 40 | current = min(get_checked_neighbors(current), key=lambda x: costs[x]) 41 | 42 | # Break if the current node is the start node 43 | if current.is_start(): 44 | break 45 | 46 | # Draw the path to the current node 47 | current.make_path() 48 | draw() 49 | 50 | 51 | def update_vertex(grid: object, node: object, rhs: dict, g: dict, open_list: list): 52 | """ 53 | Updates rhs values, and open list 54 | 55 | Args: 56 | grid (Grid): An object representing the current grid. 57 | node (Node): Current node being checked. 58 | rhs (dict): Dictionary of the rhs values. 59 | g (dict): Dictionary of the g values. 60 | open_list (list): List of open nodes. 61 | 62 | Returns: 63 | open_list: List of open nodes. 64 | """ 65 | if node != grid.start: 66 | rhs[node] = float("inf") 67 | for neighbor in node.neighbors: 68 | if ( 69 | neighbor.is_checked() 70 | or neighbor.is_start() 71 | or neighbor.is_unchecked() 72 | or (neighbor.is_end() and neighbor.been_checked) 73 | ): 74 | rhs[node] = min(rhs[node], g[neighbor] + 1) 75 | 76 | open_list = [(key, value) for key, value in open_list if value != node] 77 | heapq.heapify(open_list) 78 | 79 | if g[node] != rhs[node]: 80 | heapq.heappush(open_list, (calc_key(grid.end, node, g, rhs), node)) 81 | return open_list 82 | 83 | 84 | def calc_key(end: object, node: object, g: dict, rhs: dict): 85 | """ 86 | Calculates the key for the priority queue 87 | 88 | Args: 89 | end (Node): The end node of the current grid. 90 | node (Node): Current node. 91 | g (dict): Dictionary of the g values. 92 | rhs (dict): Dictionary of the rhs values. 93 | 94 | Returns: 95 | tuple: (min of g & rhs + heuristic, min of g & rhs) 96 | """ 97 | return ( 98 | min(g[node], rhs[node]) + heuristic("manhattan", node, end), 99 | min(g[node], rhs[node]), 100 | ) 101 | 102 | 103 | def lpa_star(grid: object): 104 | """ 105 | Performs the LPA* algorithm from start to end. 106 | 107 | Args: 108 | grid (Grid): An object representing the current grid 109 | 110 | Returns: 111 | None: The function updates the screen with the search progress and path. 112 | """ 113 | rhs = {node: float("inf") for row in grid.grid for node in row} 114 | g = {node: float("inf") for row in grid.grid for node in row} 115 | open_list = [] 116 | 117 | rhs[grid.start] = 0 118 | topKey = calc_key(grid.end, grid.start, g, rhs) 119 | open_list.append((topKey, grid.start)) 120 | 121 | # Initialize a flag to track whether the search should continue 122 | run = True 123 | 124 | while ( 125 | topKey < calc_key(grid.end, grid.end, g, rhs) or rhs[grid.end] != g[grid.end] 126 | ) and run: 127 | # Check for exit events 128 | run = check(pygame.event.get(), run) 129 | 130 | # Check for no path found 131 | if open_list: 132 | topKey, node = heapq.heappop(open_list) 133 | else: 134 | break 135 | 136 | if g[node] > rhs[node]: 137 | g[node] = rhs[node] 138 | else: 139 | g[node] = float("inf") 140 | open_list = update_vertex(grid, node, rhs, g, open_list) 141 | 142 | # Update neighboring nodes 143 | for neighbor in node.neighbors: 144 | if ( 145 | not neighbor.is_start() 146 | and not neighbor.is_end() 147 | and not neighbor.is_checked() 148 | ): 149 | neighbor.uncheck() 150 | open_list = update_vertex(grid, neighbor, rhs, g, open_list) 151 | 152 | # Update the screen with the search progress 153 | grid.draw() 154 | 155 | # Check the current node if it is not the start node or end node 156 | if not node.is_start() and not node.is_end(): 157 | node.check() 158 | 159 | # Reconstruct path if at end 160 | if node == grid.end: 161 | reconstruct_path(node, rhs, grid.draw) 162 | -------------------------------------------------------------------------------- /main/algorithms/LexicographicBFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .RP import markup, check, reconstruct_path 3 | 4 | 5 | def LexBFS(draw: object, start: object): 6 | """ 7 | Implements the Lexicographic Breadth-First Search (LexBFS) algorithm. 8 | 9 | Args: 10 | draw (function): The draw function to update the grid display. 11 | start (start): The starting node. 12 | 13 | Returns: 14 | order (list): A list of nodes in the graph, in the order they were 15 | visited by LexBFS. 16 | levels (dict): A dictionary containing the level of each node in the 17 | breadth-first search tree. 18 | parent (dict): A dictionary containing the parent of each node in the 19 | breadth-first search tree. 20 | """ 21 | 22 | # Initialize the list to store the order of the vertices 23 | order = [] 24 | 25 | # Initialize the dictionaries to store the levels and parents of 26 | # the vertices 27 | levels = {} 28 | parent = {} 29 | 30 | # Set the level of the starting vertex to 0 and its parent to None 31 | levels[start] = 0 32 | parent[start] = None 33 | 34 | # Initialize the counter and the queue 35 | level = 0 36 | Q = [start] 37 | 38 | # Set the flag to run the algorithm 39 | run = True 40 | 41 | # Run the algorithm until the queue is empty or the flag is set to False 42 | while Q and run: 43 | # Check if the flag to run the algorithm is set to False 44 | run = check(pygame.event.get(), run) 45 | 46 | # Pop the first element from the queue 47 | current = Q.pop(0) 48 | 49 | order.append(current) 50 | 51 | # Mark the node as visited 52 | markup(draw, current) 53 | 54 | for neighbor in current.neighbors: 55 | # If neighbor has not been visited before 56 | if neighbor not in levels: 57 | if not neighbor.is_start() and not neighbor.is_end(): 58 | neighbor.uncheck() 59 | # Set the level of neighbor to the current level + 1 60 | levels[neighbor] = level + 1 61 | parent[neighbor] = current 62 | Q.append(neighbor) 63 | level = level + 1 64 | 65 | # If the flag to run the algorithm is set to False, return False 66 | # for all outputs 67 | if not run: 68 | return False, False, False 69 | return order, levels, parent 70 | 71 | 72 | def lbfs(grid: object): 73 | """ 74 | Solves the shortest path problem using LexBFS. 75 | 76 | Args: 77 | grid (Grid): An object representing the current grid 78 | 79 | Returns: 80 | None 81 | """ 82 | 83 | # Run LexBFS to get the order of the vertices 84 | order, levels, parent = LexBFS(grid.draw, grid.start) 85 | 86 | if order is not False: 87 | # Initialize the distances dictionary 88 | distances = {node: float("inf") for row in grid.grid for node in row} 89 | distances[grid.start] = 0 90 | 91 | # Iterate through the vertices in the order produced by LexBFS 92 | for current in order: 93 | # Update the distances of the neighbors of current 94 | for neighbor in current.neighbors: 95 | if distances[current] + 1 < distances[neighbor]: 96 | distances[neighbor] = distances[current] + 1 97 | parent[neighbor] = current 98 | 99 | reconstruct_path(parent, grid.end, grid.draw) 100 | -------------------------------------------------------------------------------- /main/algorithms/RandomLIFO.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from queue import LifoQueue 4 | from .RP import check, markup, reconstruct_path 5 | 6 | def rand_lifo(grid: object): 7 | """ 8 | Runs a Random search on the grid using a LIFO Queue 9 | to choose the next node to check. 10 | 11 | Args: 12 | grid (Grid): An object representing the current grid 13 | 14 | Returns: 15 | None: The function updates the screen with the search progress and path. 16 | """ 17 | # Initialize path and queue 18 | came_from = {} 19 | queue = LifoQueue() 20 | queue.put(grid.start) 21 | 22 | run = True 23 | 24 | # Run search till queue is empty 25 | while not queue.empty() and run: 26 | # Check for events that may stop the search 27 | run = check(pygame.event.get(), run) 28 | 29 | current = queue.get() 30 | 31 | # End search if target is found 32 | if current.is_end(): 33 | reconstruct_path(came_from, current, grid.draw) 34 | break 35 | 36 | markup(grid.draw, current) 37 | 38 | # Choose a random neighbor of the current node 39 | next_node = random.randint(0, len(current.neighbors) - 1) 40 | 41 | # Add every possible neighbor to the queue in the correct order 42 | for index, neighbor in enumerate(current.neighbors): 43 | if neighbor.is_checked(): 44 | continue 45 | came_from[neighbor] = current 46 | if index != next_node: 47 | queue.put(neighbor) 48 | if not neighbor.is_start() and not neighbor.is_end(): 49 | neighbor.uncheck() 50 | 51 | if not current.neighbors[next_node].is_checked(): 52 | queue.put(current.neighbors[next_node]) -------------------------------------------------------------------------------- /main/algorithms/RandomWalk.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from .RP import check, get_unvisited_nodes 4 | 5 | 6 | def reconstruct_path(came_from: list, current: object, draw: object): 7 | """ 8 | Reconstructs the path from the starting node to the current node, 9 | by following the `came_from` dictionary. 10 | 11 | Args: 12 | came_from (List[Node]): list containing which nodes were checked 13 | current (Node): current node 14 | draw (function): function for drawing the search path on the grid 15 | 16 | Returns: 17 | None 18 | """ 19 | 20 | for current in reversed(came_from): 21 | if not current.is_start(): 22 | current.make_path() 23 | draw() 24 | 25 | 26 | def rand_walk(grid: object): 27 | """ 28 | Generates a random walk from the starting node to the goal node. 29 | 30 | Args: 31 | grid (Grid): An object representing the current grid 32 | 33 | Returns: 34 | None 35 | """ 36 | 37 | # Initialize the list of previous nodes and the current node 38 | came_from = [] 39 | current = grid.start 40 | 41 | # Flag for continuing the search 42 | run = True 43 | 44 | # Flag for making the path 45 | make_path = True 46 | 47 | possible_nodes = get_unvisited_nodes(grid.start) 48 | 49 | maxed = [] 50 | 51 | # Continue the search until the search is stopped or the goal is reached 52 | while run and len(possible_nodes) - 1 > len(maxed): 53 | # Check for events that may stop the search 54 | run = check(pygame.event.get(), run) 55 | 56 | # Choose a random neighbor of the current node 57 | neighbor = random.randint(0, len(current.neighbors) - 1) 58 | 59 | # Add the current node to the list of previous nodes if it 60 | # has not already been added 61 | if current not in came_from: 62 | came_from.append(current) 63 | 64 | # Move to the chosen neighbor 65 | current = current.neighbors[neighbor] 66 | 67 | # If the current node is the goal node and the path has not 68 | # been made yet, reconstruct the path and make the goal node 69 | if current.is_end() and make_path: 70 | reconstruct_path(came_from, grid.end, grid.draw) 71 | grid.end.make_end() 72 | break 73 | 74 | # Markup for drawing path with color change 75 | if not current.is_start(): 76 | current.uncheck() 77 | 78 | grid.draw() 79 | 80 | if not current.is_start() and not current.been_checked: 81 | current.check() 82 | elif not current.is_start(): 83 | current.mult_check(3) 84 | 85 | if current.color[0] <= 3 and not current in maxed: 86 | maxed.append(current) 87 | -------------------------------------------------------------------------------- /main/algorithms/SPFA.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import Queue 3 | from .RP import check, markup, reconstruct_path 4 | 5 | def spfa(grid: object): 6 | """ 7 | Performs the Shortest Path Faster Algorithm for the given grid. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | None: The function updates the screen with the search progress and path. 14 | """ 15 | d = {node: float("inf") for row in grid.grid for node in row} 16 | d[grid.start] = 0 17 | 18 | queue = Queue() 19 | queue.put(grid.start) 20 | 21 | path = {} 22 | 23 | run = True 24 | 25 | while not queue.empty() and run: 26 | # Check for exit events 27 | run = check(pygame.event.get(), run) 28 | 29 | # Exit the loop if the user interrupted 30 | if not run: 31 | break 32 | 33 | u = queue.get() 34 | 35 | # Draw the current node 36 | markup(grid.draw, u) 37 | 38 | for v in u.neighbors: 39 | if d[u] + 1 < d[v]: 40 | # Update the distance and predecessor for the neighbor if the 41 | # current distance is shorter 42 | d[v] = d[u] + 1 43 | path[v] = u 44 | 45 | if v not in queue.queue: 46 | queue.put(v) 47 | 48 | # Uncheck the neighbor if it is not the start or end node 49 | # for markup 50 | if not v.is_end() and not v.is_start(): 51 | v.uncheck() 52 | 53 | if run: 54 | reconstruct_path(path, grid.end, grid.draw) -------------------------------------------------------------------------------- /main/algorithms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/main/algorithms/__init__.py -------------------------------------------------------------------------------- /main/algorithms/bidirectional/AStar.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from queue import PriorityQueue 3 | from ..RP import heuristic, check, markup, thread_construct 4 | 5 | 6 | def bi_a_star(grid: object): 7 | """ 8 | Performs a bidirectional search to find the shortest path between the 9 | start and end nodes. The search is visualized using the given draw object. 10 | 11 | Args: 12 | grid (Grid): An object representing the current grid 13 | 14 | Returns: 15 | None: The function updates the screen with the search progress and path. 16 | """ 17 | 18 | # Initialize two queues, one for each direction of the search 19 | count_start = 0 20 | count_end = 0 21 | 22 | open_set_start = PriorityQueue() 23 | open_set_end = PriorityQueue() 24 | 25 | open_set_start.put((0, 0, grid.start)) 26 | open_set_end.put((0, 0, grid.end)) 27 | 28 | came_from_start = {} 29 | came_from_end = {} 30 | 31 | g_score_start = {node: float("inf") for row in grid.grid for node in row} 32 | g_score_start[grid.start] = 0 33 | 34 | g_score_end = {node: float("inf") for row in grid.grid for node in row} 35 | g_score_end[grid.end] = 0 36 | 37 | # Initialize two sets to keep track of visited nodes, one for 38 | # each direction of the search 39 | visited_start = set() 40 | visited_end = set() 41 | 42 | # Add the start and end nodes to the path dictionaries 43 | came_from_start[grid.start] = grid.start 44 | came_from_end[grid.end] = grid.end 45 | 46 | run = True 47 | 48 | # Loop until one of the queues is empty 49 | while not open_set_start.empty() and not open_set_end.empty() and run: 50 | # Check for events and update the run flag accordingly 51 | run = check(pygame.event.get(), run) 52 | 53 | # Dequeue a node from each queue and process it 54 | f_score1, count1, node1 = open_set_start.get() 55 | 56 | # Check for redundant checks 57 | if node1 in visited_start: 58 | continue 59 | 60 | _, _, node2 = open_set_end.get() 61 | # Check for redundant checks 62 | if node2 in visited_end: 63 | open_set_start.put((f_score1, count1, node1)) 64 | continue 65 | 66 | # Check if the nodes have already been visited from the other direction 67 | if node1 in visited_end: 68 | # Construct two threads to reconstruct the path from the start and 69 | # end directions 70 | thread_construct( 71 | (came_from_start, node1, grid.start, grid.draw), 72 | (came_from_end, node1, grid.end, grid.draw), 73 | ) 74 | break 75 | if node2 in visited_start: 76 | # Construct two threads to reconstruct the path from the start and 77 | # end directions 78 | thread_construct( 79 | (came_from_start, node2, grid.start, grid.draw), 80 | (came_from_end, node2, grid.end, grid.draw), 81 | ) 82 | break 83 | 84 | # Mark up the nodes that are being processed 85 | markup(grid.draw, node1) 86 | markup(grid.draw, node2) 87 | 88 | # Add the neighbors of the dequeued nodes to their respective queues 89 | for neighbor in node1.neighbors: 90 | temp_g_score = g_score_start[node1] + 1 91 | 92 | if temp_g_score < g_score_start[neighbor]: 93 | came_from_start[neighbor] = node1 94 | g_score_start[neighbor] = temp_g_score 95 | if neighbor not in visited_start: 96 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.end) 97 | count_start += 1 98 | open_set_start.put((f_score, count_start, neighbor)) 99 | visited_start.add(node1) 100 | 101 | if not neighbor.is_start() and not neighbor.is_end(): 102 | neighbor.uncheck() 103 | 104 | for neighbor in node2.neighbors: 105 | temp_g_score = g_score_end[node2] + 1 106 | 107 | if temp_g_score < g_score_end[neighbor]: 108 | came_from_end[neighbor] = node2 109 | g_score_end[neighbor] = temp_g_score 110 | if neighbor not in visited_end: 111 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.start) 112 | count_end += 1 113 | open_set_end.put((f_score, count_end, neighbor)) 114 | visited_end.add(node2) 115 | 116 | if not neighbor.is_start() and not neighbor.is_end(): 117 | neighbor.uncheck() 118 | -------------------------------------------------------------------------------- /main/algorithms/bidirectional/BFS.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from ..RP import check, markup, thread_construct 3 | 4 | def bi_search(grid: object): 5 | """ 6 | Performs a bidirectional search to find the shortest path between the 7 | start and end nodes. The search is visualized using the given draw object. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid 11 | """ 12 | 13 | # Initialize two queues, one for each direction of the search 14 | queue_start = [grid.start] 15 | queue_end = [grid.end] 16 | 17 | # Initialize two sets to keep track of visited nodes, one for 18 | # each direction of the search 19 | visited_start = set() 20 | visited_end = set() 21 | 22 | # Initialize dictionaries to store the paths taken by each search direction 23 | start_path = {} 24 | end_path = {} 25 | 26 | # Add the start and end nodes to the path dictionaries 27 | start_path[grid.start] = grid.start 28 | end_path[grid.end] = grid.end 29 | 30 | run = True 31 | 32 | # Loop until one of the queues is empty 33 | while queue_start and queue_end and run: 34 | # Check for events and update the run flag accordingly 35 | run = check(pygame.event.get(), run) 36 | 37 | # Dequeue a node from each queue and process it 38 | node1 = queue_start.pop(0) 39 | # Check for redundant checks 40 | if node1 in visited_start: 41 | continue 42 | 43 | node2 = queue_end.pop(0) 44 | # Check for redundant checks 45 | if node2 in visited_end: 46 | queue_start.insert(0, node1) 47 | continue 48 | 49 | # Check if the nodes have already been visited from the other direction 50 | if node1 in visited_end: 51 | # Construct two threads to reconstruct the path from the start and 52 | # end directions 53 | thread_construct( 54 | (start_path, node1, grid.start, grid.draw), 55 | (end_path, node1, grid.end, grid.draw), 56 | ) 57 | break 58 | if node2 in visited_start: 59 | # Construct two threads to reconstruct the path from the start and 60 | # end directions 61 | thread_construct( 62 | (start_path, node2, grid.start, grid.draw), 63 | (end_path, node2, grid.end, grid.draw), 64 | ) 65 | break 66 | 67 | # Mark up the nodes that are being processed 68 | markup(grid.draw, node1) 69 | markup(grid.draw, node2) 70 | 71 | # Mark the nodes as visited 72 | visited_start.add(node1) 73 | visited_end.add(node2) 74 | 75 | loop_helper(node1, visited_start, queue_start, start_path) 76 | loop_helper(node2, visited_end, queue_end, end_path) 77 | 78 | 79 | def loop_helper(node, visited, queue, path): 80 | for neighbor in node.neighbors: 81 | if neighbor not in visited: 82 | queue.append(neighbor) 83 | path[neighbor] = node 84 | neighbor.uncheck() -------------------------------------------------------------------------------- /main/algorithms/bidirectional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/main/algorithms/bidirectional/__init__.py -------------------------------------------------------------------------------- /main/algorithms/random/grid_to_tree.md: -------------------------------------------------------------------------------- 1 | # Grid Graph to Tree from starting Node Algorithm (GGTN Algorithm): 2 | 3 | ## In words: 4 | 5 | The starting node is labeled with a value of 0. 6 | 7 | From the start node, label all neighbors in increasing order starting from 1 to n-neighbors 8 | (neighbors are the up, down, left, and right in this case; labeling was done counter-clockwise, 9 | starting from the up neighbor). 10 | 11 | Then, go to the smallest neighbor (1) and label all unvalued neighbors with "1" + 1 to 12 | n-neighbors (Example: a neighbor of 1 is labeled as 012 or 011 or 013). 13 | 14 | Repeat this, going from smallest to largest value neighbor, and continue with the newly 15 | labeled neighbors until the grid is fully labeled. 16 | 17 | ### From the start node you can then construct a tree based off of the labelings: 18 | 19 | For a 5x5 grid with start at 3 down from to left and 3 right from left 20 | 21 | ![text](./tree.png) 22 | 23 | To reconstruct the graph based on the tree you need to know the method of labeling (i.e. did you label them in a certain order [e.g. clockwise, counter-clockwise]) 24 | 25 | There is probably a better labeling system than this, but it's the simplest thing I could come up with without thinking too much on it. 26 | 27 | ## Pseudocode: 28 | 29 | ``` 30 | func grid_graph_to_tree(Start, Direction : tuple indicating starting direction and labeling rotation (e.g. (up, clockwise))): 31 | create a priority queue Q 32 | create a dictionary D to store node node_value pairs 33 | set all node values to infinity 34 | set Start node value to 0 35 | add Start to Q 36 | 37 | while Q is not empty: 38 | Current is popped from Q 39 | for every Neighbor of Current node gotten according to Direction: 40 | label neighbors with the number of Current with the digit in increasing order from 1 - n-neighbors concatenated to the end of the number (e.g. Current label = 021 -> Neighbor label = 0211, 0212, 0213, 0214, ..., 0218) 41 | 42 | create a tree T with start as the root 43 | digit_count = 1 44 | while D is not empty: 45 | find node N with smallest node_value 46 | add N as a child of the parent of N (parent = N.node_value[digit_count - 1]) 47 | remove N from D 48 | 49 | return T 50 | ``` -------------------------------------------------------------------------------- /main/algorithms/random/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/main/algorithms/random/tree.png -------------------------------------------------------------------------------- /main/algorithms/random/tree.txt: -------------------------------------------------------------------------------- 1 | _______________0___________ 2 | / | | \ 3 | 1 2 3 4 4 | / | \ / \ / \ | 5 | 11 12 13 21 22 31 32 41 6 | / \ \ \ | | | | 7 | 111 112 121 131 211 221 311 321 8 | | | | | 9 | 1111 1121 2111 3111 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /main/testing/AStar.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import heuristic, count_path 3 | 4 | 5 | def a_star(grid: object): 6 | """ 7 | Perform an A* search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | visited_nodes (int): Count of the number of nodes visited. 14 | path_size (int): Length of the path found. 15 | """ 16 | 17 | # Initialize counters and sets 18 | count = 0 19 | open_set = PriorityQueue() 20 | open_set.put((0, count, grid.start)) 21 | came_from = {} 22 | 23 | # Initialize dictionary to store the g scores for each node 24 | g_score = {node: float("inf") for row in grid.grid for node in row} 25 | g_score[grid.start] = 0 26 | 27 | # Initialize a set to store the nodes in the open set 28 | open_set_hash = {grid.start} 29 | 30 | visited_nodes: int = 0 31 | path_size: int = 0 32 | 33 | # Perform the search 34 | while not open_set.empty(): 35 | # Get the current node from the open set 36 | _, _, current = open_set.get() 37 | open_set_hash.remove(current) 38 | 39 | visited_nodes += 1 40 | 41 | # End the search if the current node is the end node 42 | if current.is_end(): 43 | path_size = count_path(came_from, grid.end) 44 | break 45 | 46 | # Check the neighbors of the current node 47 | for neighbor in current.neighbors: 48 | # Calculate the tentative g score for the neighbor 49 | temp_g_score = g_score[current] + 1 50 | 51 | # Update the g core for the neighbor if the tentative g score is lower 52 | if temp_g_score < g_score[neighbor]: 53 | came_from[neighbor] = current 54 | g_score[neighbor] = temp_g_score 55 | 56 | # Add the neighbor to the open set if it is not already there 57 | if neighbor not in open_set_hash: 58 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.end) 59 | count += 1 60 | open_set.put((f_score, count, neighbor)) 61 | open_set_hash.add(neighbor) 62 | return visited_nodes, path_size 63 | -------------------------------------------------------------------------------- /main/testing/BFS.py: -------------------------------------------------------------------------------- 1 | from .RP import check, count_path 2 | 3 | 4 | def bfs(grid: object): 5 | """ 6 | Perform a breadth-first search from start to end. 7 | 8 | Args: 9 | grid (Grid): An object representing the current grid. 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | count_path (int): Length of the path found. 14 | """ 15 | 16 | # Initialize a list to store the nodes to visit (queue) 17 | nodes = [] 18 | nodes.append(grid.start) 19 | 20 | # Initialize a dictionary to store the predecessor of each node 21 | previous = {} 22 | 23 | # Initialize flags to track the search status 24 | found = False 25 | 26 | visited_nodes: int = 0 27 | 28 | # Perform the search 29 | while nodes and not found: 30 | # Get the next node to visit 31 | current = nodes.pop(0) 32 | 33 | # Skip the node if it has already been checked 34 | if current.is_checked(): 35 | continue 36 | 37 | visited_nodes += 1 38 | 39 | check(current) 40 | 41 | # Check the neighbors of the current node 42 | for neighbor in current.neighbors: 43 | # Skip the neighbor if it has already been checked 44 | if not neighbor.is_checked(): 45 | # End the search if the neighbor is the end node 46 | if neighbor.is_end(): 47 | previous[neighbor] = current 48 | found = True 49 | break 50 | # Add the neighbor to the list of nodes to visit if it is not the end node 51 | else: 52 | previous[neighbor] = current 53 | nodes.append(neighbor) 54 | return visited_nodes, count_path(previous, grid.end) 55 | -------------------------------------------------------------------------------- /main/testing/BStar.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import count_path, check, heuristic 3 | 4 | 5 | def b_star(grid: object): 6 | """ 7 | Perform a B* search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | None 14 | """ 15 | 16 | # Initialize counters and sets 17 | count = 0 18 | open_set = PriorityQueue() 19 | open_set.put((0, count, grid.start)) 20 | came_from = {} 21 | 22 | # Initialize dictionaries to store the g and f scores for each node 23 | g_score = {node: float("inf") for row in grid.grid for node in row} 24 | g_score[grid.start] = 0 25 | 26 | # Initialize a set to store the nodes in the open set 27 | open_set_hash = {grid.start} 28 | 29 | visited_nodes: int = 0 30 | path_size: int = 0 31 | 32 | # Perform the search 33 | while not open_set.empty(): 34 | # Get the current node from the open set 35 | _, _, current = open_set.get() 36 | open_set_hash.remove(current) 37 | 38 | visited_nodes += 1 39 | 40 | # End the search if the current node is the end node 41 | if current.is_end(): 42 | path_size = count_path(came_from, grid.end) 43 | break 44 | 45 | # Check the neighbors of the current node 46 | for neighbor in current.neighbors: 47 | # Calculate the tentative g score for the neighbor 48 | temp_g_score = g_score[current] + 1 49 | 50 | # Update the g and f scores for the neighbor if the 51 | # tentative g score is lower 52 | if temp_g_score < g_score[neighbor]: 53 | came_from[neighbor] = current 54 | g_score[neighbor] = temp_g_score 55 | f_score = temp_g_score + heuristic("d_manhattan", neighbor, grid.end) 56 | 57 | # Add the neighbor to the open set if it is not already there 58 | if neighbor not in open_set_hash: 59 | count += 1 60 | open_set.put((f_score, count, neighbor)) 61 | open_set_hash.add(neighbor) 62 | 63 | return visited_nodes, path_size -------------------------------------------------------------------------------- /main/testing/BeamSearch.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from .RP import heuristic, check, count_path 3 | 4 | 5 | def beam_search(grid: object, beam_size: int): 6 | """ 7 | Perform a beam search from start to end with a given beam size. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | beam_size (int): The maximum number of nodes to consider at each step. 12 | 13 | Returns: 14 | visited_nodes (int): Count of the number of nodes visited. 15 | path_size (int): Length of the path found. 16 | """ 17 | 18 | # Initialize the beam with the root node 19 | beam = [(0, grid.start)] 20 | 21 | # Initialize a dictionary to store the previous nodes for each node 22 | previous = {} 23 | previous[grid.start] = grid.start 24 | 25 | visited_nodes: int = 0 26 | path_size: int = 0 27 | 28 | # Perform the search 29 | while beam: 30 | # Get the most promising node from the beam 31 | _, current = heapq.heappop(beam) 32 | 33 | visited_nodes += 1 34 | 35 | # End the search if the current node is the end node 36 | if current.is_end(): 37 | path_size = count_path(previous, grid.end) 38 | break 39 | 40 | check(current) 41 | 42 | # Expand the current node and add its children to the beam 43 | children = current.neighbors 44 | 45 | # Remove the previous node from the list of children 46 | if previous[current] in children: 47 | children.remove(previous[current]) 48 | 49 | # Add the children to the beam 50 | for child in children: 51 | # Skip the child if it has already been checked 52 | if not child.is_checked(): 53 | previous[child] = current 54 | heapq.heappush(beam, (heuristic("manhattan", child, grid.end), child)) 55 | 56 | # Trim the beam to the desired size 57 | beam = beam[:beam_size] 58 | return visited_nodes, path_size 59 | -------------------------------------------------------------------------------- /main/testing/BellmanFord.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | from .RP import get_unvisited_nodes, count_path 3 | 4 | 5 | def bell_ford(grid: object, accuracy: float): 6 | """ 7 | Perform a Bellman-Ford search from start to end with a given accuracy. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | accuracy (float): The fraction of nodes to visit at each step. (0, 1] 12 | 13 | Returns: 14 | visited_nodes (int): Count of the number of nodes visited. 15 | count_path (int): Length of the path found. 16 | """ 17 | 18 | # Get the list of unvisited nodes 19 | nodes = get_unvisited_nodes(grid.start) 20 | 21 | # Initialize dictionaries to store the distance and predecessor for each node 22 | distance = {node: float("inf") for node in nodes} 23 | predecessor = {} 24 | 25 | # Set the distance of the start node to 0 26 | distance[grid.start] = 0 27 | 28 | # Calculate the number of nodes to visit at each step 29 | counter = int((len(nodes) - 1) * accuracy) 30 | 31 | visited_nodes: int = 0 32 | 33 | # Perform the search 34 | while counter >= 0: 35 | # Visit each node 36 | for current in nodes: 37 | 38 | visited_nodes += 1 39 | 40 | # Check the neighbors of the current node 41 | for neighbor in current.neighbors: 42 | # Update the distance and predecessor for the neighbor if the 43 | # current distance is shorter 44 | if distance[current] + 1 < distance[neighbor]: 45 | distance[neighbor] = distance[current] + 1 46 | predecessor[neighbor] = current 47 | # Decrement the counter 48 | counter -= 1 49 | 50 | return visited_nodes, count_path(predecessor, grid.end) 51 | -------------------------------------------------------------------------------- /main/testing/BestFS.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import heuristic, check 3 | 4 | 5 | def get_checked_neighbors(node: object): 6 | """ 7 | Get the neighbors of a node that have been checked or are the start node. 8 | 9 | Args: 10 | node (Node): The node to check. 11 | 12 | Returns: 13 | List[Node]: The list of checked or start neighbors. 14 | """ 15 | 16 | checked = [] 17 | for neighbor in node.neighbors: 18 | if neighbor.is_checked() or neighbor.is_start(): 19 | checked.append(neighbor) 20 | return checked 21 | 22 | 23 | def count_path(current: object, costs: dict): 24 | """ 25 | Reconstruct the path from the end node to the start node. 26 | 27 | Args: 28 | current (Node): The current node in the search. 29 | costs (Dict[Node, int]): The cost of reaching each node from the start. 30 | 31 | Returns: 32 | path_size (int): Length of the path found. 33 | """ 34 | path_size: int = 0 35 | 36 | # Continue until the current node is the start node 37 | while not current.is_start(): 38 | # Find the neighbor with the lowest cost 39 | current = min(get_checked_neighbors(current), key=lambda x: costs[x]) 40 | 41 | # Break if the current node is the start node 42 | if current.is_start(): 43 | break 44 | path_size += 1 45 | return path_size 46 | 47 | 48 | def best_fs(grid: object): 49 | """ 50 | Perform a best-first search from start to end. 51 | 52 | Args: 53 | grid (Grid): An object representing the current grid. 54 | 55 | Returns: 56 | visited_nodes (int): Count of the number of nodes visited. 57 | path_size (int): Length of the path found. 58 | """ 59 | 60 | # Initialize the priority queue with the start node 61 | queue = PriorityQueue() 62 | queue.put((0, 0, grid.start)) 63 | count = 0 64 | 65 | # Initialize a dictionary to store the cost of reaching each node from the start 66 | costs = {grid.start: 0} 67 | 68 | visited_nodes: int = 0 69 | path_size: int = 0 70 | 71 | # Perform the search 72 | while not queue.empty(): 73 | # Get the node with the lowest cost from the queue 74 | cost, _, current = queue.get() 75 | 76 | visited_nodes += 1 77 | 78 | # End the search if the current node is the end node 79 | if current.is_end(): 80 | path_size = count_path(current, costs) 81 | break 82 | 83 | check(current) 84 | 85 | # Check the neighbors of the current node 86 | for neighbor in current.neighbors: 87 | # Calculate the cost of reaching the neighbor 88 | cost = costs[current] + 1 89 | 90 | # Update the cost in the dictionary if it is lower than the current value 91 | if neighbor not in costs or cost < costs[neighbor]: 92 | costs[neighbor] = cost 93 | count += 1 94 | # Add the neighbor to the queue with the calculated cost as the priority 95 | queue.put( 96 | (cost + heuristic("manhattan", neighbor, grid.end), count, neighbor) 97 | ) 98 | return visited_nodes, path_size 99 | -------------------------------------------------------------------------------- /main/testing/DFS.py: -------------------------------------------------------------------------------- 1 | from .RP import check, count_path 2 | 3 | 4 | def dfs(grid: object): 5 | """ 6 | Perform a depth-first search from start to end. 7 | 8 | Args: 9 | grid (Grid): An object representing the current grid. 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | count_path (int): Length of the path found. 14 | """ 15 | 16 | # Initialize a stack to store the nodes to visit 17 | stack = [grid.start] 18 | 19 | # Initialize a dictionary to store the predecessor of each node 20 | previous = {} 21 | 22 | # Initialize flags to track the search status 23 | found = False 24 | 25 | visited_nodes: int = 0 26 | 27 | # Perform the search 28 | while len(stack) and not found: 29 | # Get the next node to visit 30 | current = stack.pop() 31 | 32 | # Skip the node if it has already been checked 33 | if current.is_checked(): 34 | continue 35 | 36 | visited_nodes += 1 37 | 38 | check(current) 39 | 40 | # Check the neighbors of the current node 41 | for neighbor in current.neighbors: 42 | # Skip the neighbor if it has already been checked 43 | if not neighbor.is_checked(): 44 | # End the search if the neighbor is the end node 45 | if neighbor.is_end(): 46 | previous[neighbor] = current 47 | found = True 48 | break 49 | # Add the neighbor to the stack of nodes to visit if it is not the end node 50 | else: 51 | previous[neighbor] = current 52 | stack.append(neighbor) 53 | 54 | return visited_nodes, count_path(previous, grid.end) 55 | -------------------------------------------------------------------------------- /main/testing/Dijkstra.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import check, count_path 3 | 4 | 5 | def dijkstra(grid: object): 6 | """ 7 | Perform Dijkstra's algorithm from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | visited_nodes (int): Count of the number of nodes visited. 14 | path_size (int): Length of the path found. 15 | """ 16 | 17 | # Initialize a priority queue to store the nodes to visit 18 | queue = PriorityQueue() 19 | 20 | # Set up the node values 21 | distance = {node: float("inf") for row in grid.grid for node in row} 22 | distance[grid.start] = 0 23 | 24 | # Holds the path from start to end 25 | previous = {} 26 | 27 | # Add the start node to the priority queue 28 | queue.put((distance[grid.start], 0, grid.start)) 29 | count = 0 30 | 31 | visited_nodes: int = 0 32 | path_size: int = 0 33 | 34 | # Perform the search 35 | while not queue.empty(): 36 | # Get the next node to visit 37 | current_distance, _, current_min = queue.get() 38 | 39 | visited_nodes += 1 40 | 41 | # End the search if the current node is the end node 42 | if current_min.is_end(): 43 | path_size = count_path(previous, grid.end) 44 | break 45 | 46 | check(current_min) 47 | 48 | # Check the neighbors of the current node 49 | for neighbor in current_min.neighbors: 50 | # Don't recheck for performance 51 | if not neighbor.is_checked(): 52 | # edges between vertecies are not weighted 53 | # (using constant weight of 1) 54 | temp_value = distance[current_min] + 1 55 | if temp_value < distance[neighbor]: 56 | distance[neighbor] = temp_value 57 | previous[neighbor] = current_min 58 | count += 1 59 | 60 | # Add the neighbor to the priority queue 61 | queue.put((distance[neighbor], count, neighbor)) 62 | 63 | return visited_nodes, path_size 64 | -------------------------------------------------------------------------------- /main/testing/FastMarching.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | from .RP import count_path 4 | 5 | def fmm(grid: object): 6 | """ 7 | Performs the Fast Marching Method on a uniform cost grid 8 | to solve the shortest path problem. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | visited_nodes (int): Count of the number of nodes visited. 15 | path_size (int): Length of the path found. 16 | """ 17 | # Initialize dictionary to store the cost for each node 18 | costs = {node: float("inf") for row in grid.grid for node in row} 19 | costs[grid.start] = 0 20 | 21 | # Record all the unvisited nodes in the grid 22 | far = [node for row in grid.grid for node in row if not node.is_start()] 23 | 24 | # Initialize the visited priority queue 25 | considered = [] 26 | heapq.heappush(considered, (0, grid.start)) 27 | 28 | came_from = {} 29 | 30 | visited_nodes: int = 0 31 | path_size: int = 0 32 | 33 | # Perform the search 34 | while considered: 35 | # Get the current node from the open set 36 | ccost, current = heapq.heappop(considered) 37 | 38 | visited_nodes += 1 39 | 40 | # End the search if the current node is the end node 41 | if current.is_end(): 42 | path_size = count_path(came_from, grid.end) 43 | break 44 | 45 | # Check the neighbors of the current node 46 | for neighbor in current.neighbors: 47 | # If the neighbor hasn't been checked 48 | if neighbor not in considered and not neighbor.is_checked(): 49 | # Edges between nodes are not weighted 50 | # (using constant weight of 1) 51 | cost = ccost + 1 52 | if cost < costs[neighbor]: 53 | costs[neighbor] = cost 54 | came_from[neighbor] = current 55 | 56 | # If neighbor hasn't been checked or been visited before 57 | # add it to the queue 58 | if neighbor in far: 59 | heapq.heappush(considered, (cost, neighbor)) 60 | far.remove(neighbor) 61 | return visited_nodes, path_size -------------------------------------------------------------------------------- /main/testing/FloodFill.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import heapq 3 | 4 | def flood_fill(grid: object): 5 | """ 6 | Performs the Flood Fill algorithm to solve the 7 | shortest path problem. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid 11 | 12 | Returns: 13 | visited_nodes (int): Count of the number of nodes visited. 14 | path_size (int): Length of the path found. 15 | """ 16 | # Create a dictionary to store distance values 17 | distances = { node: float("inf") for row in grid for node in row } 18 | distances[grid.end] = 0 19 | 20 | # Start from end 21 | queue = [(0, grid.end)] 22 | 23 | visited_nodes: int = 0 24 | path_size: int = 0 25 | 26 | while queue: 27 | dist, current = heapq.heappop(queue) 28 | 29 | if current.is_checked(): 30 | continue 31 | 32 | 33 | if current.is_start(): 34 | break 35 | 36 | if not current.is_end(): 37 | current.check() 38 | 39 | visited_nodes += 1 40 | 41 | # Check every neighbor of the current cell and 42 | # add it to the queue if it hasn't been checked before 43 | for neighbor in current.neighbors: 44 | if not neighbor.is_checked(): 45 | distances[neighbor] = dist + 1 46 | heapq.heappush(queue, (dist + 1, neighbor)) 47 | 48 | # Count path 49 | if distances[grid.start] != float("inf"): 50 | current = grid.start 51 | found = False 52 | while not found: 53 | best = (float("inf"), None) 54 | for neighbor in current.neighbors: 55 | if neighbor.is_end(): 56 | found = True 57 | break 58 | if distances[neighbor] < best[0]: 59 | best = (distances[neighbor], neighbor) 60 | if not found: 61 | path_size += 1 62 | current = best[1] 63 | 64 | return visited_nodes, path_size 65 | -------------------------------------------------------------------------------- /main/testing/FloydWarshall.py: -------------------------------------------------------------------------------- 1 | from .RP import get_unvisited_nodes 2 | 3 | 4 | def count_path( 5 | dist: list, 6 | V: int, 7 | start: object, 8 | end: object, 9 | nodes: list, 10 | checked_nodes: list, 11 | ): 12 | """ 13 | Function to reconstruct a path from the start node to the end node based on 14 | the given distance matrix, list of nodes, and list of checked nodes. 15 | 16 | Args: 17 | dist (list): A 2D list representing the distance matrix between nodes. 18 | V (int): The number of nodes in the grid. 19 | start (Node): The starting node. 20 | end (Node): The ending node. 21 | nodes (list): A list of all nodes in the grid. 22 | checked_nodes (list): A list of nodes that have been visited during the search. 23 | 24 | Returns: 25 | error_value or path_size (int): 26 | -1 if the end node wasn't checked; 27 | -2 if the end node wasn't in the list of connected nodes; 28 | Length of the path found. 29 | """ 30 | 31 | path_size: int = 0 32 | 33 | # If the end node is not in the list of nodes, return False 34 | try: 35 | test = nodes.index(end) 36 | if end not in checked_nodes: 37 | return -1 38 | except ValueError: 39 | return -2 40 | 41 | # Get the indices of the start and end nodes in the list of nodes 42 | u, v = nodes.index(start), nodes.index(end) 43 | 44 | # Initialize empty lists for the path, left-side distances, 45 | # and right-side distances 46 | path = [] 47 | left = [] 48 | right = [] 49 | current = v 50 | 51 | # Iterate backwards through the distance matrix 52 | for k in range(V - 1, -1, -1): 53 | # If the distance between the start and end nodes is equal to the 54 | # distance between the start node and the k-th node plus the distance 55 | # between the k-th node and the end node, add the k-th node to the 56 | # path and add the coresponding distances to the left and right lists 57 | if dist[u][v] == dist[u][k] + dist[k][v]: 58 | # Only add the node to the path if it is not the start or end node 59 | # and its distance values are not already in the 60 | # left and right lists 61 | if ( 62 | not nodes[k].is_start() 63 | and not nodes[k].is_end() 64 | and dist[u][k] not in left 65 | and dist[k][v] not in right 66 | ): 67 | path.append(nodes[k]) 68 | left.append(dist[u][k]) 69 | right.append(dist[k][v]) 70 | current = k 71 | 72 | # Set the current node to the end node 73 | curr = end 74 | # Keep looping until the current node is the start node 75 | while curr != start: 76 | # Check each neighbor of the current node 77 | for node in curr.neighbors: 78 | # If the neighbor is the start node, return True 79 | if node.is_start(): 80 | return path_size 81 | 82 | # If the neighbor is in the path, mark it as part of the path and 83 | # set the current node to the neighbor 84 | if node in path: 85 | path_size += 1 86 | path.remove(node) 87 | curr = node 88 | 89 | return path_size 90 | 91 | 92 | def floyd_warshall(grid: object): 93 | """ 94 | Implements the Floyd-Warshall algorithm to find the shortest path between 95 | the start and end nodes in the given grid. 96 | 97 | Args: 98 | grid (Grid): An object representing the current grid. 99 | 100 | Returns: 101 | visited_nodes (int): Count of the number of nodes visited. 102 | count_path (int): Length of the path found. 103 | """ 104 | 105 | # Get a list of all unvisited nodes, excluding the start node 106 | nodes = get_unvisited_nodes(grid.start) 107 | 108 | # Get the number of nodes 109 | V = len(nodes) 110 | 111 | # Initialize the distance matrix with all values set to infinity 112 | distance = [[float("inf") for _ in range(V)] for _ in range(V)] 113 | 114 | # Used for path reconstruction 115 | checked_nodes = [grid.start] 116 | 117 | visited_nodes: int = 0 118 | 119 | # Initialize the distance values in the distance matrix 120 | for i in range(V): 121 | for j in range(V): 122 | visited_nodes += 1 123 | 124 | # If the nodes are neighbors, set the distance to 1 125 | if nodes[i] in nodes[j].neighbors: 126 | distance[i][j] = 1 127 | 128 | # If the two nodes are the same, set the distance to 0 129 | for i in range(V): 130 | visited_nodes += 1 131 | distance[i][i] = 0 132 | 133 | 134 | # Iterate through all nodes in the list 135 | for k in range(V): 136 | # Iterate through all pairs of nodes in the list 137 | for i in range(V): 138 | for j in range(V): 139 | visited_nodes += 1 140 | # If the distance between the two nodes is currently longer than 141 | # the path through the k-th node, set it to the new shorter distance 142 | if distance[i][j] > distance[i][k] + distance[k][j]: 143 | distance[i][j] = distance[i][k] + distance[k][j] 144 | 145 | # If none of the nodes are the start or end nodes, check them again 146 | if ( 147 | not nodes[i].is_start() 148 | and not nodes[i].is_end() 149 | and not nodes[j].is_start() 150 | and not nodes[j].is_end() 151 | and not nodes[k].is_start() 152 | and not nodes[k].is_end() 153 | ): 154 | checked_nodes.append(nodes[i]) 155 | checked_nodes.append(nodes[j]) 156 | checked_nodes.append(nodes[k]) 157 | 158 | # Adding end if it is connected to start 159 | try: 160 | test = nodes.index(grid.end) 161 | checked_nodes.append(grid.end) 162 | except ValueError: 163 | pass 164 | 165 | return visited_nodes, count_path( 166 | distance, V, grid.start, grid.end, nodes, checked_nodes 167 | ) 168 | -------------------------------------------------------------------------------- /main/testing/Fringe.py: -------------------------------------------------------------------------------- 1 | from .RP import heuristic 2 | 3 | def count_path(cache: dict, current: object): 4 | """ 5 | Counts the path size. 6 | 7 | Args: 8 | cache (dict): the cache of nodes in the grid that were visited 9 | current (Node): the ending node 10 | 11 | Returns: 12 | path (int): path size 13 | """ 14 | path = -1 15 | while not current.is_start(): 16 | path += 1 17 | _, current = cache[current] 18 | return path 19 | 20 | def fringe_search(grid: object): 21 | """ 22 | Performs a Fringe Search over the grid. 23 | 24 | Args: 25 | grid (Grid): An object representing the current grid 26 | 27 | Returns: 28 | visited_nodes (int): Count of the number of nodes visited. 29 | path_size (int): Length of the path found. 30 | """ 31 | fringe = [] 32 | cache = {} 33 | cache[grid.start] = (0, None) 34 | fringe.append(grid.start) 35 | 36 | f_limit = heuristic("manhattan", grid.start, grid.end) 37 | 38 | found = False 39 | 40 | visited_nodes: int = 0 41 | path_size: int = 0 42 | 43 | while not found and fringe: 44 | f_min = float("inf") 45 | # Check every node in the fringe 46 | for node in fringe: 47 | g, parent = cache[node] 48 | # Get f-score for current node 49 | f = g + heuristic("manhattan", node, grid.end) 50 | # Check if the f-score is greater than the allowed limit 51 | if f > f_limit: 52 | f_min = min(f, f_min) 53 | continue 54 | 55 | visited_nodes += 1 56 | 57 | if node.is_end(): 58 | path_size = count_path(cache, node) 59 | found = True 60 | break 61 | 62 | for child in node.neighbors: 63 | g_child = g + 1 64 | # If the child node has already been seen 65 | if child in cache: 66 | g_cached, c_parent = cache[child] 67 | if g_child >= g_cached: 68 | continue 69 | if child in fringe: 70 | fringe.remove(child) 71 | fringe.append(child) 72 | cache[child] = (g_child, node) 73 | fringe.remove(node) 74 | f_limit = f_min 75 | return visited_nodes, path_size -------------------------------------------------------------------------------- /main/testing/GBFS.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import heuristic, check, count_path 3 | 4 | 5 | def gbfs(grid: object): 6 | """ 7 | Perform a greedy best-first search from start to end. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | visited_nodes (int): Count of the number of nodes visited. 14 | count_path (int): Length of the path found. 15 | """ 16 | 17 | # Initialize priority queue with the start node 18 | Q = PriorityQueue() 19 | Q.put((heuristic("manhattan", grid.start, grid.end), 0, grid.start)) 20 | 21 | # Initialize counters and flags 22 | counter = 0 23 | found = False 24 | 25 | # Initialize a dictionary to store the previous nodes for each node 26 | previous = {} 27 | 28 | visited_nodes: int = 0 29 | 30 | # Perform the search 31 | while not Q.empty() and not found: 32 | # Get the current node from the queue 33 | _, _, current = Q.get() 34 | 35 | # Skip if the node has already been checked 36 | if current.is_checked(): 37 | continue 38 | 39 | visited_nodes += 1 40 | 41 | # Draw the current node 42 | check(current) 43 | 44 | # Check the neighbors of the current node 45 | for neighbor in current.neighbors: 46 | # Skip if the neighbor has already been checked 47 | if not neighbor.is_checked(): 48 | # End the search if the neighbor is the end node 49 | if neighbor.is_end(): 50 | previous[neighbor] = current 51 | visited_nodes += 1 52 | found = True 53 | break 54 | 55 | # Add the neighbor to the queue 56 | counter += 1 57 | distance = heuristic("manhattan", neighbor, grid.end) 58 | previous[neighbor] = current 59 | Q.put((distance, counter, neighbor)) 60 | return visited_nodes, count_path(previous, grid.end) 61 | -------------------------------------------------------------------------------- /main/testing/GBLS.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from .RP import heuristic, check, count_path 3 | 4 | 5 | def gbls(grid: object): 6 | """ 7 | Modified version of the greedy best-first search algorithm that explores neighbors 8 | in the direction of the last chosen node as long as the estimated distance to the 9 | goal is shorter than the current node. 10 | 11 | Args: 12 | grid (Grid): An object representing the current grid. 13 | 14 | Returns: 15 | visited_nodes (int): Count of the number of nodes visited. 16 | count_path (int): Length of the path found. 17 | """ 18 | 19 | # Initialize the priority queue with the starting node 20 | Q = PriorityQueue() 21 | Q.put((heuristic("manhattan", grid.start, grid.end), 0, grid.start)) 22 | 23 | # Initialize the counter and flags 24 | counter = 0 25 | found = False 26 | 27 | previous = {} 28 | last_direction = None 29 | 30 | visited_nodes: int = 0 31 | 32 | # Continue the search as long as there are nodes in the queue or the goal has 33 | # not been found 34 | while not Q.empty() and not found: 35 | _, _, current = Q.get() 36 | 37 | if current.is_checked(): 38 | continue 39 | 40 | visited_nodes += 1 41 | 42 | check(current) 43 | 44 | # Choose the neighbors in the last direction first, 45 | # if a direction has been chosen 46 | if last_direction: 47 | neighbors = [ 48 | n 49 | for n in current.neighbors 50 | if n.get_pos()[0] - current.get_pos()[0] == last_direction[0] 51 | and n.get_pos()[1] - current.get_pos()[1] == last_direction[1] 52 | ] 53 | 54 | # Add the other neighbors to the list 55 | neighbors += [n for n in current.neighbors if n not in neighbors] 56 | # If no direction has been chosen, explore all neighbors 57 | else: 58 | neighbors = current.neighbors 59 | 60 | for neighbor in neighbors: 61 | if not neighbor.is_checked(): 62 | if neighbor.is_end(): 63 | previous[neighbor] = current 64 | visited_nodes += 1 65 | found = True 66 | break 67 | 68 | counter += 1 69 | distance = heuristic("manhattan", neighbor, grid.end) 70 | 71 | # Add the neighbor to the queue with the estimated 72 | # distance as the priority 73 | previous[neighbor] = current 74 | Q.put((distance, counter, neighbor)) 75 | 76 | # Save the direction to the neighbor 77 | last_direction = ( 78 | neighbor.get_pos()[0] - current.get_pos()[0], 79 | neighbor.get_pos()[1] - current.get_pos()[1], 80 | ) 81 | return visited_nodes, count_path(previous, grid.end) 82 | -------------------------------------------------------------------------------- /main/testing/IDAStar.py: -------------------------------------------------------------------------------- 1 | from .RP import heuristic 2 | 3 | 4 | def ida_star(grid: object): 5 | """ 6 | Implements the IDA* search algorithm. 7 | 8 | Args: 9 | grid (Grid): An object representing the current grid. 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | path_size (int): Length of the path found. 14 | """ 15 | bound = heuristic("manhattan", grid.start, grid.end) 16 | 17 | # Stack for path being searched 18 | path = [grid.start] 19 | 20 | visited_nodes: int = 0 21 | 22 | while True: 23 | result, temp_v_n = search(visited_nodes, path, 0, bound, grid.end) 24 | 25 | visited_nodes = temp_v_n 26 | 27 | if result is True: 28 | break 29 | if result == float("inf"): 30 | break 31 | 32 | bound = result 33 | 34 | return visited_nodes, len(path) - 2 35 | 36 | 37 | def search(visited_nodes: int, path: list, g: int, bound: float, end: object): 38 | """ 39 | Recursive search function used by the IDA* algorithm. 40 | 41 | Args: 42 | visited_nodes (int): Count of the number of nodes visited. 43 | path (list): The list of nodes representing the path taken by the 44 | search algorithm. 45 | g (int): The cost of the path represented by the list of nodes. 46 | bound (float): The bound on the cost of the path. 47 | end (Node): The ending node. 48 | Returns: 49 | True or f (boolean or float): 50 | end was reached; 51 | g-score + heuristic is greater than current depth (bound) 52 | min_val (float): minimum value for the path 53 | """ 54 | # Get last node in path 55 | node = path[-1] 56 | 57 | visited_nodes += 1 58 | 59 | # Calculate the f-score 60 | f = g + heuristic("manhattan", node, end) 61 | 62 | if f > bound: 63 | return f, visited_nodes 64 | if node.is_end(): 65 | return True, visited_nodes 66 | 67 | min_val = float("inf") 68 | for neighbor in node.neighbors: 69 | if neighbor not in path: 70 | path.append(neighbor) 71 | result, temp_v_n = search(visited_nodes, path, g + 1, bound, end) 72 | visited_nodes = temp_v_n 73 | if result is True: 74 | return True, visited_nodes 75 | if result < min_val: 76 | min_val = result 77 | 78 | path.pop() 79 | return min_val, visited_nodes 80 | -------------------------------------------------------------------------------- /main/testing/IDDFS.py: -------------------------------------------------------------------------------- 1 | from .RP import count_path 2 | 3 | 4 | def depth_limit( 5 | visited_nodes: int, 6 | current: object, 7 | depth: int, 8 | visited: set, 9 | path: dict, 10 | ): 11 | """ 12 | Recursive DFS with a depth limit. 13 | 14 | Args: 15 | visited_nodes (int): Count of the number of nodes visited. 16 | current (Node): Current node being visited. 17 | depth (int): Current depth of the search. 18 | visited (set): Set of nodes already visited. 19 | path (dict): Dictionary of parent nodes, used to reconstruct the path. 20 | 21 | Returns: 22 | dict: Dictionary of parent nodes, used to reconstruct the path. 23 | Returns an empty dictionary if no path is found. 24 | visited_nodes (int): Count of the number of nodes visited. 25 | """ 26 | if depth < 0: 27 | return {}, visited_nodes 28 | if current.is_end(): 29 | visited_nodes += 1 30 | return path, visited_nodes 31 | 32 | visited.add(current) 33 | 34 | visited_nodes += 1 35 | 36 | for neighbor in current.neighbors: 37 | if neighbor not in visited: 38 | path[neighbor] = current 39 | 40 | result, temp_v_n = depth_limit( 41 | visited_nodes, neighbor, depth - 1, visited, path 42 | ) 43 | 44 | visited_nodes = temp_v_n 45 | 46 | if result: 47 | return result, visited_nodes 48 | 49 | return {}, visited_nodes 50 | 51 | 52 | def iddfs(grid: object, length: int): 53 | """ 54 | Iterative deepening DFS. 55 | 56 | Args: 57 | grid (Grid): An object representing the current grid. 58 | length (int): Length of the grid. 59 | 60 | Returns: 61 | visited_nodes (int): Count of the number of nodes visited. 62 | path_size (int): Length of the path found. 63 | """ 64 | 65 | visited_nodes: int = 0 66 | path_size: int = 0 67 | 68 | for depth in range(0, length): 69 | visited = set() 70 | path, temp_v_n = depth_limit( 71 | visited_nodes, grid.start, depth, visited, {grid.start: None} 72 | ) 73 | 74 | visited_nodes = temp_v_n 75 | 76 | if path: 77 | path_size = count_path(path, grid.end) 78 | break 79 | return visited_nodes, path_size 80 | -------------------------------------------------------------------------------- /main/testing/JPS.py: -------------------------------------------------------------------------------- 1 | from .RP import heuristic, count_path 2 | 3 | 4 | def jps(grid: object): 5 | """ 6 | Perform an Jump Point Search from start to end. 7 | 8 | Args: 9 | grid (Grid): An object representing the current grid 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | path_size (int): Length of the path found. 14 | """ 15 | 16 | open_set = set([grid.start]) 17 | closed_set = set() 18 | 19 | came_from = {} 20 | 21 | g_values = {grid.start: 0} 22 | f_values = {grid.start: heuristic("manhattan", grid.start, grid.end)} 23 | 24 | visited_nodes: int = 0 25 | path_size: int = 0 26 | 27 | while open_set: 28 | # Find the node with the lowest f-value in the open set. 29 | current = min(open_set, key=lambda node: f_values[node]) 30 | 31 | visited_nodes += 1 32 | 33 | if current.is_end(): 34 | path_size = count_path(came_from, grid.end) 35 | break 36 | 37 | # Move current from open set to closed set 38 | open_set.remove(current) 39 | closed_set.add(current) 40 | 41 | for neighbor in current.neighbors: 42 | # Check if neighbor is already closed 43 | if neighbor in closed_set: 44 | continue 45 | 46 | if neighbor not in open_set: 47 | g_value = g_values[current] + heuristic("manhattan", current, neighbor) 48 | 49 | g_values[neighbor] = g_value 50 | f_values[neighbor] = g_value + heuristic( 51 | "manhattan", neighbor, grid.end 52 | ) 53 | 54 | came_from[neighbor] = current 55 | open_set.add(neighbor) 56 | return visited_nodes, path_size 57 | -------------------------------------------------------------------------------- /main/testing/LPAStar.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from .RP import heuristic 3 | 4 | 5 | def get_checked_neighbors(node: object): 6 | """ 7 | Get the neighbors of a node that have been checked or are the start node. 8 | 9 | Args: 10 | node (Node): The node to check. 11 | 12 | Returns: 13 | List[Node]: The list of checked or start neighbors. 14 | """ 15 | 16 | checked = [] 17 | for neighbor in node.neighbors: 18 | if neighbor.is_checked() or neighbor.is_start(): 19 | checked.append(neighbor) 20 | return checked 21 | 22 | 23 | def count_path(current: object, costs: dict, path_size: int): 24 | """ 25 | Reconstruct the path from the end node to the start node. 26 | 27 | Args: 28 | current (Node): The current node in the search. 29 | costs (Dict[Node, int]): The cost of reaching each node from the start. 30 | path_size (int): Counter for path size found. 31 | 32 | Returns: 33 | None: The function updates the screen with the path. 34 | """ 35 | 36 | # Continue until the current node is the start node 37 | while not current.is_start(): 38 | # Find the neighbor with the lowest cost 39 | current = min(get_checked_neighbors(current), key=lambda x: costs[x]) 40 | 41 | # Break if the current node is the start node 42 | if current.is_start(): 43 | break 44 | 45 | path_size += 1 46 | return path_size 47 | 48 | 49 | def update_vertex(grid: object, node: object, rhs: dict, g: dict, open_list: list): 50 | """ 51 | Updates rhs values, and open list 52 | 53 | Args: 54 | grid (Grid): An object representing the current grid. 55 | node (Node): Current node being checked. 56 | rhs (dict): Dictionary of the rhs values. 57 | g (dict): Dictionary of the g values. 58 | open_list (list): List of open nodes. 59 | 60 | Returns: 61 | open_list: List of open nodes. 62 | """ 63 | if node != grid.start: 64 | rhs[node] = float("inf") 65 | for neighbor in node.neighbors: 66 | if ( 67 | neighbor.is_checked() 68 | or neighbor.is_start() 69 | or neighbor.is_unchecked() 70 | or (neighbor.is_end() and neighbor.been_checked) 71 | ): 72 | rhs[node] = min(rhs[node], g[neighbor] + 1) 73 | 74 | open_list = [(key, value) for key, value in open_list if value != node] 75 | heapq.heapify(open_list) 76 | 77 | if g[node] != rhs[node]: 78 | heapq.heappush(open_list, (calc_key(grid.end, node, g, rhs), node)) 79 | return open_list 80 | 81 | 82 | def calc_key(end: object, node: object, g: dict, rhs: dict): 83 | """ 84 | Calculates the key for the priority queue 85 | 86 | Args: 87 | end (Node): The end node of the current grid. 88 | node (Node): Current node. 89 | g (dict): Dictionary of the g values. 90 | rhs (dict): Dictionary of the rhs values. 91 | 92 | Returns: 93 | tuple: (min of g & rhs + heuristic, min of g & rhs) 94 | """ 95 | return ( 96 | min(g[node], rhs[node]) + heuristic("manhattan", node, end), 97 | min(g[node], rhs[node]), 98 | ) 99 | 100 | 101 | def lpa_star(grid: object): 102 | """ 103 | Performs the LPA* algorithm from start to end. 104 | 105 | Args: 106 | grid (Grid): An object representing the current grid 107 | 108 | Returns: 109 | visited_nodes (int): Count of the number of nodes visited. 110 | path_size (int): Length of the path found. 111 | """ 112 | rhs = {node: float("inf") for row in grid.grid for node in row} 113 | g = {node: float("inf") for row in grid.grid for node in row} 114 | open_list = [] 115 | 116 | rhs[grid.start] = 0 117 | topKey = calc_key(grid.end, grid.start, g, rhs) 118 | open_list.append((topKey, grid.start)) 119 | 120 | visited_nodes: int = 0 121 | path_size: int = 0 122 | 123 | while topKey < calc_key(grid.end, grid.end, g, rhs) or rhs[grid.end] != g[grid.end]: 124 | # Check for no path found 125 | if open_list: 126 | topKey, node = heapq.heappop(open_list) 127 | visited_nodes += 1 128 | else: 129 | break 130 | 131 | if g[node] > rhs[node]: 132 | g[node] = rhs[node] 133 | else: 134 | g[node] = float("inf") 135 | open_list = update_vertex(grid, node, rhs, g, open_list) 136 | 137 | # Update neighboring nodes 138 | for neighbor in node.neighbors: 139 | if ( 140 | not neighbor.is_start() 141 | and not neighbor.is_end() 142 | and not neighbor.is_checked() 143 | ): 144 | neighbor.uncheck() 145 | open_list = update_vertex(grid, neighbor, rhs, g, open_list) 146 | 147 | # Check the current node if it is not the start node or end node 148 | if not node.is_start() and not node.is_end(): 149 | node.check() 150 | 151 | # Reconstruct path if at end 152 | if node == grid.end: 153 | path_size = count_path(node, rhs, path_size) 154 | return visited_nodes, path_size -------------------------------------------------------------------------------- /main/testing/LexicographicBFS.py: -------------------------------------------------------------------------------- 1 | from .RP import count_path 2 | 3 | 4 | def lex_bfs(visited_nodes: int, start: object): 5 | """ 6 | Implements the Lexicographic Breadth-First Search (LexBFS) algorithm. 7 | 8 | Args: 9 | visited_nodes (int): Count of the number of nodes visited. 10 | start (start): The starting node. 11 | 12 | Returns: 13 | order (list): A list of nodes in the graph, in the order they were 14 | visited by LexBFS. 15 | levels (dict): A dictionary containing the level of each node in the 16 | breadth-first search tree. 17 | visited_nodes (int): Count of the number of nodes visited. 18 | parent (dict): A dictionary containing the parent of each node checked. 19 | """ 20 | 21 | # Initialize the list to store the order of the vertices 22 | order = [] 23 | 24 | # Initialize the dictionaries to store the levels and parents of 25 | # the vertices 26 | levels = {} 27 | parent = {} 28 | 29 | # Set the level of the starting vertex to 0 and its parent to None 30 | levels[start] = 0 31 | parent[start] = None 32 | 33 | # Initialize the counter and the queue 34 | level = 0 35 | Q = [start] 36 | 37 | # Run the algorithm until the queue is empty or the flag is set to False 38 | while Q: 39 | # Pop the first element from the queue 40 | current = Q.pop(0) 41 | 42 | visited_nodes += 1 43 | 44 | order.append(current) 45 | 46 | for neighbor in current.neighbors: 47 | # If neighbor has not been visited before 48 | if neighbor not in levels: 49 | # Set the level of neighbor to the current level + 1 50 | levels[neighbor] = level + 1 51 | parent[neighbor] = current 52 | Q.append(neighbor) 53 | level = level + 1 54 | 55 | return order, levels, visited_nodes, parent 56 | 57 | 58 | def lbfs(grid: object): 59 | """ 60 | Solves the shortest path problem using LexBFS. 61 | 62 | Args: 63 | grid (Grid): An object representing the current grid. 64 | 65 | Returns: 66 | visited_nodes (int): Count of the number of nodes visited. 67 | count_path (int): Length of the path found. 68 | """ 69 | 70 | visited_nodes: int = 0 71 | 72 | # Run LexBFS to get the order of the vertices 73 | order, levels, temp_v_n, parent = lex_bfs(visited_nodes, grid.start) 74 | 75 | visited_nodes = temp_v_n 76 | 77 | if order is not False: 78 | # Initialize the distances dictionary 79 | distances = {node: float("inf") for row in grid.grid for node in row} 80 | distances[grid.start] = 0 81 | 82 | # Iterate through the vertices in the order produced by LexBFS 83 | for current in order: 84 | visited_nodes += 1 85 | # Update the distances of the neighbors of current 86 | for neighbor in current.neighbors: 87 | if distances[current] + 1 < distances[neighbor]: 88 | distances[neighbor] = distances[current] + 1 89 | parent[neighbor] = current 90 | 91 | return visited_nodes, count_path(parent, grid.end) 92 | -------------------------------------------------------------------------------- /main/testing/RandomLIFO.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from queue import LifoQueue 4 | from .RP import count_path 5 | 6 | def rand_lifo(grid: object): 7 | """ 8 | Runs a Random search on the grid using a LIFO Queue 9 | to choose the next node to check. 10 | 11 | Args: 12 | grid (Grid): An object representing the current grid. 13 | 14 | Returns: 15 | visited_nodes (int): Count of the number of nodes visited. 16 | path_size (int): Length of the path found. 17 | """ 18 | # Initialize path and queue 19 | came_from = {} 20 | queue = LifoQueue() 21 | queue.put(grid.start) 22 | 23 | visited_nodes: int = 0 24 | path_size: int = 0 25 | 26 | # Run search till queue is empty 27 | while not queue.empty(): 28 | current = queue.get() 29 | 30 | 31 | # End search if target is found 32 | if current.is_end(): 33 | path_size = count_path(came_from, current) 34 | break 35 | 36 | visited_nodes += 1 37 | 38 | if not current.is_start(): 39 | current.check() 40 | 41 | # Choose a random neighbor of the current node 42 | next_node = random.randint(0, len(current.neighbors) - 1) 43 | 44 | # Add every possible neighbor to the queue in the correct order 45 | for index, neighbor in enumerate(current.neighbors): 46 | if neighbor.is_checked(): 47 | continue 48 | 49 | came_from[neighbor] = current 50 | if index != next_node: 51 | queue.put(neighbor) 52 | 53 | if not current.neighbors[next_node].is_checked(): 54 | queue.put(current.neighbors[next_node]) 55 | return visited_nodes, path_size -------------------------------------------------------------------------------- /main/testing/RandomWalk.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def rand_walk(grid: object): 5 | """ 6 | Generates a random walk from the starting node to the goal node. 7 | 8 | Parameters: 9 | grid (Grid): An object representing the current grid. 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | path_size (int): Length of the path found. 14 | """ 15 | 16 | # Initialize the list of previous nodes and the current node 17 | came_from = [] 18 | current = grid.start 19 | 20 | visited_nodes: int = 0 21 | 22 | # Continue the search until the goal is reached 23 | while True: 24 | # Choose a random neighbor of the current node 25 | neighbor = random.randint(0, len(current.neighbors) - 1) 26 | 27 | # Add the current node to the list of previous nodes if it 28 | # has not already been added 29 | if current not in came_from: 30 | came_from.append(current) 31 | 32 | # Move to the chosen neighbor 33 | current = current.neighbors[neighbor] 34 | 35 | visited_nodes += 1 36 | 37 | if current.is_end(): 38 | break 39 | return visited_nodes, len(came_from) - 1 40 | -------------------------------------------------------------------------------- /main/testing/SPFA.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | from .RP import count_path 3 | 4 | def spfa(grid: object): 5 | """ 6 | Performs the Shortest Path Faster Algorithm for the given grid. 7 | 8 | Args: 9 | grid (Grid): An object representing the current grid. 10 | 11 | Returns: 12 | visited_nodes (int): Count of the number of nodes visited. 13 | count_path (int): Length of the path found. 14 | """ 15 | d = {node: float("inf") for row in grid.grid for node in row} 16 | d[grid.start] = 0 17 | 18 | queue = Queue() 19 | queue.put(grid.start) 20 | 21 | path = {} 22 | 23 | visited_nodes: int = 0 24 | 25 | while not queue.empty(): 26 | u = queue.get() 27 | 28 | visited_nodes += 1 29 | 30 | for v in u.neighbors: 31 | if d[u] + 1 < d[v]: 32 | # Update the distance and predecessor for the neighbor if the 33 | # current distance is shorter 34 | d[v] = d[u] + 1 35 | path[v] = u 36 | 37 | if v not in queue.queue: 38 | queue.put(v) 39 | 40 | return visited_nodes, count_path(path, grid.end) -------------------------------------------------------------------------------- /main/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/main/testing/__init__.py -------------------------------------------------------------------------------- /main/testing/bidirectional/AStar.py: -------------------------------------------------------------------------------- 1 | from queue import PriorityQueue 2 | from ..RP import heuristic, thread_construct 3 | 4 | 5 | def bi_a_star(grid: object): 6 | """ 7 | Performs a bidirectional search to find the shortest path between the 8 | start and end nodes. The search is visualized using the given draw object. 9 | 10 | Args: 11 | grid (Grid): An object representing the current grid 12 | 13 | Returns: 14 | None: The function updates the screen with the search progress and path. 15 | """ 16 | 17 | # Initialize two queues, one for each direction of the search 18 | count_start = 0 19 | count_end = 0 20 | 21 | open_set_start = PriorityQueue() 22 | open_set_end = PriorityQueue() 23 | 24 | open_set_start.put((0, 0, grid.start)) 25 | open_set_end.put((0, 0, grid.end)) 26 | 27 | came_from_start = {} 28 | came_from_end = {} 29 | 30 | g_score_start = {node: float("inf") for row in grid.grid for node in row} 31 | g_score_start[grid.start] = 0 32 | 33 | g_score_end = {node: float("inf") for row in grid.grid for node in row} 34 | g_score_end[grid.end] = 0 35 | 36 | # Initialize two sets to keep track of visited nodes, one for 37 | # each direction of the search 38 | visited_start = set() 39 | visited_end = set() 40 | 41 | # Add the start and end nodes to the path dictionaries 42 | came_from_start[grid.start] = grid.start 43 | came_from_end[grid.end] = grid.end 44 | 45 | visited_nodes: int = 0 46 | path_size: int = 0 47 | 48 | # Loop until one of the queues is empty 49 | while not open_set_start.empty() and not open_set_end.empty(): 50 | # Dequeue a node from each queue and process it 51 | start_score, start_count, start_node = open_set_start.get() 52 | 53 | # Check for redundant checks 54 | if start_node in visited_start: 55 | continue 56 | 57 | _, _, end_node = open_set_end.get() 58 | # Check for redundant checks 59 | if end_node in visited_end: 60 | open_set_start.put((start_score, start_count, start_node)) 61 | continue 62 | 63 | # Check if the nodes have already been visited from the other direction 64 | if start_node in visited_end: 65 | # Construct two threads to reconstruct the path from the start and 66 | # end directions 67 | path_size = thread_construct( 68 | (came_from_start, start_node, grid.start), 69 | (came_from_end, start_node, grid.end), 70 | ) 71 | break 72 | if end_node in visited_start: 73 | # Construct two threads to reconstruct the path from the start and 74 | # end directions 75 | path_size = thread_construct( 76 | (came_from_start, end_node, grid.start), 77 | (came_from_end, end_node, grid.end), 78 | ) 79 | break 80 | 81 | visited_nodes += 2 82 | 83 | # Add the neighbors of the dequeued nodes to their respective queues 84 | for neighbor in start_node.neighbors: 85 | temp_g_score = g_score_start[start_node] + 1 86 | 87 | if temp_g_score < g_score_start[neighbor]: 88 | came_from_start[neighbor] = start_node 89 | g_score_start[neighbor] = temp_g_score 90 | if neighbor not in visited_start: 91 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.end) 92 | count_start += 1 93 | open_set_start.put((f_score, count_start, neighbor)) 94 | visited_start.add(start_node) 95 | 96 | for neighbor in end_node.neighbors: 97 | temp_g_score = g_score_end[end_node] + 1 98 | 99 | if temp_g_score < g_score_end[neighbor]: 100 | came_from_end[neighbor] = end_node 101 | g_score_end[neighbor] = temp_g_score 102 | if neighbor not in visited_end: 103 | f_score = temp_g_score + heuristic("manhattan", neighbor, grid.start) 104 | count_end += 1 105 | open_set_end.put((f_score, count_end, neighbor)) 106 | visited_end.add(end_node) 107 | 108 | return visited_nodes, path_size 109 | -------------------------------------------------------------------------------- /main/testing/bidirectional/BFS.py: -------------------------------------------------------------------------------- 1 | from ..RP import thread_construct 2 | 3 | 4 | def bi_search(grid: object): 5 | """ 6 | Performs a bidirectional search to find the shortest path between the 7 | start and end nodes. 8 | 9 | Args: 10 | grid (Grid): An object representing the current grid. 11 | 12 | Returns: 13 | visited_nodes (int): Count of the number of nodes visited. 14 | path_size (int): Length of the path found. 15 | """ 16 | 17 | # Initialize two queues, one for each direction of the search 18 | queue_start = [grid.start] 19 | queue_end = [grid.end] 20 | 21 | # Initialize two sets to keep track of visited nodes, one for 22 | # each direction of the search 23 | visited_start = set() 24 | visited_end = set() 25 | 26 | # Initialize dictionaries to store the paths taken by each search direction 27 | start_path = {} 28 | end_path = {} 29 | 30 | # Add the start and end nodes to the path dictionaries 31 | start_path[grid.start] = grid.start 32 | end_path[grid.end] = grid.end 33 | 34 | visited_nodes: int = 0 35 | path_size: int = 0 36 | 37 | # Loop until one of the queues is empty 38 | while queue_start and queue_end: 39 | # Dequeue a node from each queue and process it 40 | start_node = queue_start.pop(0) 41 | # Check for redundant checks 42 | if start_node in visited_start: 43 | continue 44 | 45 | end_node = queue_end.pop(0) 46 | # Check for redundant checks 47 | if end_node in visited_end: 48 | queue_start.insert(0, start_node) 49 | continue 50 | 51 | # Check if the nodes have already been visited from the other direction 52 | if start_node in visited_end: 53 | # Construct two threads to reconstruct the path from the start and 54 | # end directions 55 | path_size = thread_construct( 56 | (start_path, start_node, grid.start), (end_path, start_node, grid.end) 57 | ) 58 | break 59 | 60 | if end_node in visited_start: 61 | # Construct two threads to reconstruct the path from the start and 62 | # end directions 63 | path_size = thread_construct( 64 | (start_path, end_node, grid.start), (end_path, end_node, grid.end) 65 | ) 66 | break 67 | 68 | visited_nodes += 2 69 | 70 | # Mark the nodes as visited 71 | visited_start.add(start_node) 72 | visited_end.add(end_node) 73 | 74 | # Add the neighbors of the dequeued nodes to their respective queues 75 | loop_helper(start_node, visited_start, queue_start, start_path) 76 | loop_helper(end_node, visited_end, queue_end, end_path) 77 | 78 | return visited_nodes, path_size 79 | 80 | 81 | def loop_helper(node: object, visited: set, queue: list, path: dict): 82 | """ 83 | Does the neighbor search. 84 | 85 | Args: 86 | node (Node): The node being checked. 87 | visited (set): The set of visited nodes from the side the node is being 88 | checked from. 89 | queue (list): The list of node to visit. 90 | path (dict): The path taken from the side the node is being checked. 91 | 92 | Returns: 93 | None 94 | """ 95 | for neighbor in node.neighbors: 96 | if neighbor not in visited: 97 | queue.append(neighbor) 98 | path[neighbor] = node -------------------------------------------------------------------------------- /main/testing/bidirectional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/main/testing/bidirectional/__init__.py -------------------------------------------------------------------------------- /main/testing/results/Generated_Maze-Example.csv: -------------------------------------------------------------------------------- 1 | Algorithm,Local Run Time,Times Nodes Checked,Path Length,General Data 2 | ,,,,Total Open Nodes: 1165 3 | astar,0.0009820179993766942,157,65 4 | beam,0.0002225520001957193,72,65 5 | bellford,0.4267947219996131,1357225,65 6 | bestfs,0.0007002390002526226,157,65 7 | bfs,0.0004396410004119389,445,65 8 | biastar,0.0016367050002372707,198,65 9 | bisearch,0.00041756000064196996,312,65 10 | bstar,0.0009466930005146423,157,65 11 | dfs,0.001031709999551822,1146,65 12 | dijkstra,0.0018467629997758195,468,65 13 | flood,0.001915261000249302,969,65 14 | floyd,203.65243170800022,1582525515,65 15 | fmm,0.09329834500022116,480,65 16 | fringe,0.0021174599996811594,668,65 17 | gbfs,0.0004050409997944371,72,65 18 | gbls,0.0005712299998776871,75,65 19 | ida,0.002999525000632275,654,65 20 | iddfs,0.005324234999534383,9923,65 21 | jps,0.0005544420000660466,157,65 22 | lbfs,0.0013342349993763492,2330,65 23 | lpa,0.002990240999679372,157,65 24 | rand,1.682881677000296,42184,867 25 | rand_lifo,0.002002788000027067,485,65 26 | spfa,0.004598628999701759,1165,65 27 | theta,0.001139300000431831,157,65,Turn points: 28 28 | -------------------------------------------------------------------------------- /resources/Best-First-Search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/Best-First-Search.gif -------------------------------------------------------------------------------- /resources/OtherAlgorithms.md: -------------------------------------------------------------------------------- 1 | # Algorithms that could be used for path finding 2 | 3 | ``` 4 | Algorithms to look at: 5 | 6 | - Hill climbing 7 | - Genetic algorithms 8 | - Ant colony optimization 9 | - Particle swarm optimization 10 | - Simulated annealing 11 | 12 | - D* 13 | - D* Lite 14 | - Landmark Label Setting 15 | - Hierarchical A* 16 | - Contraction Hierarchies 17 | - Lazy Theta* 18 | - Anytime Repairing A* 19 | - Weighted A* 20 | - Bounded A* 21 | 22 | - Optimal Reciprocal Collision Avoidance (ORCA) 23 | - Potential Field Path Planning (PFPP) 24 | - Rapidly-Exploring Random Tree (RRT) 25 | - Kinodynamic RRT (Kino-RRT) 26 | - Visibility Graph 27 | - Probabilistic Roadmap (PRM) 28 | - Elastic Band (EB) 29 | - A* with Elastic Band (AEB) 30 | 31 | - Cooperative Path Planning (CPP) 32 | - Optimal Cooperative Path Planning (OCPP) 33 | - Path Planning in Complex Environments (PPCP) 34 | - Path Planning for Multiple Robots (PPMR) 35 | - Multi-Agent Path Finding (MAPF) 36 | - Conflict-Based Search (CBS) 37 | - Incremental Search (ICS) 38 | - Conflict-Based Multi-Agent Path Finding (CB-MAPF) 39 | 40 | - Multi-Objective Evolutionary Algorithms (MOEA) 41 | - Constraint-Based Path Planning (CBPP) 42 | - Anytime Constraint-Based Path Planning (ACBPP) 43 | - Constraint-Based Multi-Agent Path Finding (CB-MAPF) 44 | - Sampling-Based Motion Planning (SMP) 45 | - Probabilistic Roadmap with Sampling (PRM-S) 46 | - Kinodynamic Sampling-Based Motion Planning (Kino-SMP) 47 | - Kinodynamic Probabilistic Roadmap with Sampling (Kino-PRM-S) 48 | ``` 49 | -------------------------------------------------------------------------------- /resources/Thoughts.md: -------------------------------------------------------------------------------- 1 | ## Reason for writing 2 | 3 | While programming this project, I have learned a lot about graph search and path finding algorithms, and I wanted to share some of my opinions and thoughts about them, in relation to this project. 4 | 5 | ## Process 6 | 7 | A lot of what went in to this project was researching what algorithms there are and how to translate them into something that can be used in a grid-based graph. 8 | 9 | I used resources like Wikipedia, and ChatGPT for a lot of the algorithms, and explainations. Most algorithm pages on Wikipedia have a pseudocode section, which is helpful when the mathematical notation doesn't quite make sense. ChatGPT, while currently looked at with suspicion by some, is a great resource for learning and as a tool for assisting in development. The only gripe I have to say about ChatGPT is that you can't be 100% certain what it spits out is correct without validating it against another source, like Wikipedia, or primary sources like the papers where the algorithms were published. 10 | 11 | Another resource that I tried to use is YouTube. However, most of the algorithms on YouTube (pathfinding algorithms) are either A*, BFS, DFS, or Dijkstra's algorithm. I could scarely find the other algorithms I implemented here, and ones I'm going to implement, on YouTube. There were some videos that went over the concept of the algorithm, like Bellman-Ford, and Floyd-Warshall, but they didn't provide either usable psuedocode or actual code for the algorithm. This is a personal opinion of mine, but if you want to do a deep dive on pathfinding algorithms, I would suggest using other resources than YouTube. However, if you're just looking to get started or just a cursory overview of pathfinding algorithms, then YouTube is a good resource. 12 | 13 | ## Algorithm Discussion 14 | 15 | While implementing these algorithms, I relized there is an issue with the project environment. By this I mean, not every algorithm can display its own strenths. The biggest losers of this simulation are the algorithms that are ment to be run once and the results used multiple times. For example, the Floyd-Warshall algorithm. If you look at the example test result in the `main/testing/results` folder, you will see that there are > one billion node checks. In the environment that I have set up, all data from the previous run is deleted, so the distance matrix that is created isn't reused. So, every run, on a default generated maze, the Floyd-Warshall algorithm will have to run > one billion checks. However, if data were saved from a previous run, the Floyd-Warshall algorithm would only have to do those checks once and then the shortest path could be found in linear time (O(|V|)). On the other end of the spectrum, the algorithms that are most suited to this environment are ones that are meant to be run without any preprocessing, like A*, BFS, and Dijkstra. 16 | 17 | If I were to reimplement this, or modify this program, I would add something like a caching function to store precomputed information that would speed up algorithm runtime. 18 | 19 | --- 20 | 21 | This program is mainly designed as a learning tool, rather than anything practical. However, most of the algorithms have practical applications. 22 | 23 | I think the easiest demonstration would be a GPS application. Many of the concepts in the algorithms implemented here are used for GPS applications. If we look at Theta*, we can see it gives turn points. This is congruent to what many GPS applications do where it tells you to turn at X street. 24 | 25 | --TBC -------------------------------------------------------------------------------- /resources/astar_maze.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/astar_maze.gif -------------------------------------------------------------------------------- /resources/b_star.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/b_star.pdf -------------------------------------------------------------------------------- /resources/d-star.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/d-star.pdf -------------------------------------------------------------------------------- /resources/fast_marching.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/fast_marching.pdf -------------------------------------------------------------------------------- /resources/gabows.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/gabows.pdf -------------------------------------------------------------------------------- /resources/interesting_ideas.md: -------------------------------------------------------------------------------- 1 | One example of an interesting and unorthodox pathfinding algorithm is the "ants on a graph" algorithm, which is based on the behavior of real ants as they search for food. In this algorithm, virtual ants are placed on a graph and are allowed to move randomly, with the probability of moving to a particular node being based on the attractiveness of that node. Over time, the ants will tend to move towards nodes that are closer to the goal, resulting in the formation of a "pheromone" trail that other ants can follow. 2 | 3 | Another interesting and unorthodox pathfinding algorithm is the "firefly algorithm", which is based on the behavior of fireflies as they search for mates. In this algorithm, virtual fireflies are placed on a graph and are allowed to move randomly, with the probability of moving to a particular node being based on the brightness of that node. Over time, the fireflies will tend to move towards nodes that are brighter, resulting in the formation of a "light" trail that other fireflies can follow. 4 | 5 | In the case of the Japanese train routes, the researchers used a type of slime mold called Physarum polycephalum, which is known for its ability to find the shortest path between multiple food sources. By creating a model of the train network and providing the slime mold with "food" at the locations of different cities, the researchers were able to observe the growth of the slime mold and use it to determine the optimal routes between cities. 6 | 7 | This type of nature-inspired algorithm, known as "biological computing", has the potential to provide new solutions to complex problems that may be difficult to solve using traditional methods. While it is not always practical or feasible to use biological systems directly, studying and understanding the behavior of these systems can provide valuable insights and inspiration for developing new algorithms and approaches to problem-solving. 8 | 9 | ***** 10 | 11 | Particle swarm optimization (PSO) is a heuristic optimization algorithm that is based on the behavior of bird flocks or fish schools. It is often used for solving problems that involve finding the optimal solution in a complex system. 12 | 13 | In the context of pathfinding, PSO can be used to find the shortest path between two nodes in a graph or network. It does this by simulating the movement of a group of particles, or agents, in the graph. Each particle represents a potential solution to the problem, and the particles move through the graph according to a set of rules that are designed to guide them towards the optimal solution. 14 | 15 | Here's an overview of how PSO might be used for pathfinding: 16 | 17 | Initialize the position and velocity of each particle in the graph. 18 | For each particle, calculate its fitness, or the quality of the solution it represents. 19 | Update the position and velocity of each particle based on its fitness and the positions and velocities of the other particles. 20 | Repeat steps 2 and 3 until the particles converge on the optimal solution. 21 | 22 | Once the particles have converged on the optimal solution, the shortest path between the two nodes can be reconstructed from the positions of the particles. PSO is a heuristic algorithm, so it doesn't guarantee that the solution it finds will be the global optimum, but it is often effective at finding good solutions in complex systems. 23 | 24 | ***** 25 | 26 | The basic idea behind hill climbing is to start with an initial solution to the problem, and then iteratively improve the solution by making small, local changes. In the case of pathfinding, the initial solution might be a random path between the two nodes, and the local changes might involve modifying the path by adding or removing edges. 27 | 28 | Here's an overview of how hill climbing might be applied to pathfinding: 29 | 30 | Start with an initial path between the two nodes. 31 | Iteratively modify the path by adding or removing edges. 32 | Calculate the fitness, or the quality, of the modified path. 33 | If the modified path is better than the current path, update the current path with the modified path. 34 | Repeat steps 2-4 until the path cannot be improved any further. 35 | 36 | Once the path cannot be improved any further, the algorithm has converged on the local optimum, which is the shortest path between the two nodes according to the local search rules. Like other heuristic algorithms, hill climbing doesn't guarantee that the solution it finds will be the global optimum, but it is often effective at finding good solutions in complex systems. 37 | 38 | ***** -------------------------------------------------------------------------------- /resources/links.md: -------------------------------------------------------------------------------- 1 | Theta*: https://en.wikipedia.org/wiki/Theta*#Pseudocode 2 | Best First Search: https://en.wikipedia.org/wiki/Best-first_search 3 | Graph Algorithms: https://en.wikipedia.org/wiki/Category:Graph_algorithms 4 | SSS*: https://en.wikipedia.org/wiki/SSS* 5 | Shortest Path Problem: https://en.wikipedia.org/wiki/Shortest_path_problem 6 | AAPP: https://en.wikipedia.org/wiki/Any-angle_path_planning 7 | Johnson's: https://en.wikipedia.org/wiki/Johnson%27s_algorithm 8 | Fast Marching: https://en.wikipedia.org/wiki/Fast_marching_method 9 | https://math.berkeley.edu/~sethian/ 10 | Fast Sweeping: https://en.wikipedia.org/wiki/Fast_sweeping_method -------------------------------------------------------------------------------- /resources/theta_star.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/resources/theta_star.pdf -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check for missing commit message 4 | if [ -z "$1" ]; then 5 | echo "Error: Please provide a commit message." 6 | exit 1 7 | fi 8 | 9 | # Enable extended globbing. 10 | shopt -s extglob 11 | 12 | dir="main/testing/results" 13 | 14 | # Check if directory exists 15 | if [ -d "$dir" ]; then 16 | # Check if there are more than one file in the directory 17 | if [ "$(ls -1 "$dir" | wc -l)" -gt 1 ]; then 18 | # Navigate to the subfolder containing the CSV files. 19 | cd "$dir" 20 | rm !(Generated_Maze-Example.csv) 21 | 22 | echo "Deleting CSV files from ./main/testing/results" 23 | else 24 | echo "Directory contains only one file. Nothing deleted." 25 | fi 26 | else 27 | echo "Directory does not exist." 28 | fi 29 | 30 | # Upload to repository 31 | git add -A 32 | git commit -m "$1" 33 | git push 34 | -------------------------------------------------------------------------------- /website/TODO: -------------------------------------------------------------------------------- 1 | - Bidirectional A* 2 | - Bidirectional search 3 | - Fringe search 4 | - Iterative Deepening (IDA*) 5 | - Iterative Deepening DFS (IDDFS) 6 | - Jump Point Search (JPS) 7 | - Lexicographic BFS (LBFS) 8 | - Lifelong Planning A* (LPA*) 9 | - Random Walk with LIFO Queue 10 | - Shortest Path Faster Algorithm (SPFA) 11 | - Theta* -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pathfinding", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://gurknathe.github.io/Pathfinding-Algorithms", 6 | "dependencies": { 7 | "@emotion/react": "^11.11.0", 8 | "@emotion/styled": "^11.11.0", 9 | "@mui/icons-material": "^5.11.16", 10 | "@mui/material": "^5.13.3", 11 | "@testing-library/jest-dom": "^5.16.5", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "^16.18.34", 16 | "@types/react": "^18.2.8", 17 | "@types/react-dom": "^18.2.4", 18 | "gh-pages": "^5.0.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-router-dom": "^6.11.2", 22 | "three": "^0.151.3", 23 | "typescript": "^4.9.5", 24 | "web-vitals": "^2.1.4" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "predeploy": "npm run build", 30 | "deploy": "gh-pages -d build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "devDependencies": { 53 | "react-scripts": "5.0.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/favicon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/website/public/favicon.xcf -------------------------------------------------------------------------------- /website/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Pathfinding Algorithms 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /website/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/website/public/logo192.png -------------------------------------------------------------------------------- /website/public/logo192.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GurkNathe/Pathfinding-Algorithms/917dbbbc9fb075d1d9020403ecb6ca2426e0bb83/website/public/logo192.xcf -------------------------------------------------------------------------------- /website/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ethan Krug", 3 | "name": "Ethan Krug Personal Website", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /website/src/App.tsx: -------------------------------------------------------------------------------- 1 | import Panel from "./components/Panel" 2 | 3 | export default function App() { 4 | return( 5 |
6 | 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /website/src/algorithms/a*.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors, heuristic, reconstructPath } from "./helper"; 3 | 4 | export default function astar(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid = [...colorGrid]; 6 | let count: number = 0; 7 | let openSet: PriorityQueue = new PriorityQueue(); 8 | openSet.enqueue(endPoints.start, 0, count); 9 | let previous : any = {}; 10 | 11 | let gscore : any = {}; 12 | for (let i = 0; i < grid.length; i++) { 13 | for (let j = 0; j < grid[0].length; j++) { 14 | gscore[`${i}-${j}`] = Infinity; 15 | } 16 | } 17 | gscore[genKey(endPoints.start)] = 0; 18 | 19 | while (!openSet.isEmpty()) { 20 | let [y, x] = openSet.dequeue(); 21 | 22 | if (grid[y][x] === "blue") { 23 | reconstructPath(grid, previous, [y, x]); 24 | break; 25 | } 26 | 27 | let temp_gscore = gscore[`${y}-${x}`] + 1; 28 | for (const neighbor of getNeighbors(grid, [y, x])) { 29 | let [ny, nx] = neighbor; 30 | 31 | if (temp_gscore < gscore[`${ny}-${nx}`]) { 32 | previous[`${ny}-${nx}`] = [y, x]; 33 | gscore[`${ny}-${nx}`] = temp_gscore; 34 | 35 | if (!openSet.contains([ny, nx])) { 36 | let fscore = temp_gscore + heuristic("manhattan", grid, neighbor, endPoints.end); // + heuristic 37 | count++; 38 | openSet.enqueue(neighbor, fscore, count); 39 | if (!(grid[ny][nx] === "orange" || grid[ny][nx] === "blue")) { 40 | grid[ny][nx] = "green"; 41 | } 42 | } 43 | } 44 | 45 | } 46 | 47 | if (grid[y][x] !== "orange") { 48 | grid[y][x] = "red"; 49 | } 50 | 51 | setGrid(grid); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /website/src/algorithms/b*.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors, heuristic, reconstructPath } from "./helper"; 3 | 4 | export default function bstar(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid = [...colorGrid]; 6 | let count: number = 0; 7 | let openSet: PriorityQueue = new PriorityQueue(); 8 | openSet.enqueue(endPoints.start, 0, count); 9 | let previous : any = {}; 10 | 11 | let gscore : any = {}; 12 | for (let i = 0; i < grid.length; i++) { 13 | for (let j = 0; j < grid[0].length; j++) { 14 | gscore[`${i}-${j}`] = Infinity; 15 | } 16 | } 17 | gscore[genKey(endPoints.start)] = 0; 18 | 19 | while (!openSet.isEmpty()) { 20 | let [y, x] = openSet.dequeue(); 21 | 22 | if (grid[y][x] === "blue") { 23 | reconstructPath(grid, previous, [y, x]); 24 | break; 25 | } 26 | 27 | let temp_gscore = gscore[`${y}-${x}`] + 1; 28 | for (const neighbor of getNeighbors(grid, [y, x])) { 29 | let [ny, nx] = neighbor; 30 | 31 | if (temp_gscore < gscore[`${ny}-${nx}`]) { 32 | previous[`${ny}-${nx}`] = [y, x]; 33 | gscore[`${ny}-${nx}`] = temp_gscore; 34 | 35 | if (!openSet.contains([ny, nx])) { 36 | let fscore = temp_gscore + heuristic("d_manhattan", grid, neighbor, endPoints.end); // + heuristic 37 | count++; 38 | openSet.enqueue(neighbor, fscore, count); 39 | if (!(grid[ny][nx] === "orange" || grid[ny][nx] === "blue")) { 40 | grid[ny][nx] = "green"; 41 | } 42 | } 43 | } 44 | 45 | } 46 | 47 | if (grid[y][x] !== "orange") { 48 | grid[y][x] = "red"; 49 | } 50 | 51 | setGrid(grid); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /website/src/algorithms/beamsearch.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors, heuristic, reconstructPath } from "./helper"; 3 | 4 | export default function beamsearch(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let beam = new PriorityQueue(); 7 | let beamsize = 50; 8 | beam.enqueue(endPoints.start, 0, 0); 9 | 10 | let previous: any = {}; 11 | previous[genKey(endPoints.start)] = endPoints.start; 12 | 13 | while (beam.size() > 0) { 14 | let [y, x] = beam.dequeue(); 15 | 16 | if (grid[y][x] === "blue") { 17 | reconstructPath(grid, previous, [y, x]); 18 | break; 19 | } 20 | 21 | if (grid[y][x] !== "orange") { 22 | grid[y][x] = "red"; 23 | } 24 | 25 | let children = getNeighbors(grid, [y, x]); 26 | children = children.filter((e) => { 27 | return !(e[0] === previous[`${y}-${x}`][0] && e[1] === previous[`${y}-${x}`][1]); 28 | }) 29 | 30 | for (const child of children) { 31 | let [cy, cx] = child; 32 | 33 | if (grid[cy][cx] !== "red") { 34 | previous[`${cy}-${cx}`] = [y, x]; 35 | beam.enqueue(child, heuristic("manhattan", grid, child, endPoints.end), 0); 36 | 37 | if (grid[cy][cx] !== "orange" && grid[cy][cx] !== "blue") { 38 | grid[cy][cx] = "green"; 39 | } 40 | } 41 | } 42 | 43 | beam.resize(beamsize) 44 | } 45 | } -------------------------------------------------------------------------------- /website/src/algorithms/bellford.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { genKey, getNeighbors, getUnvisitedNodes, reconstructPath } from "./helper"; 3 | 4 | export default function bellmanford(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests){ 5 | let grid: States[][] = [...colorGrid]; 6 | let accuracy = 1; 7 | 8 | let nodes: [number, number][] = getUnvisitedNodes(grid, endPoints.start); 9 | let previous : any = {}; 10 | let distance : any = {}; 11 | 12 | for (const node of nodes) { 13 | distance[`${node[0]}-${node[1]}`] = Infinity; 14 | } 15 | 16 | distance[genKey(endPoints.start)] = 0; 17 | 18 | let counter: number = Math.floor((nodes.length - 1) * accuracy); 19 | 20 | while (counter >= 0) { 21 | for (const node of nodes) { 22 | let [y, x] = node; 23 | 24 | let color = grid[y][x]; 25 | 26 | if (color !== "orange" && color !== "blue") { 27 | grid[y][x] = "red"; 28 | } 29 | 30 | for (const neighbor of getNeighbors(grid, node)) { 31 | let [ny, nx] = neighbor; 32 | if (distance[`${y}-${x}`] + 1 < distance[`${ny}-${nx}`]) { 33 | distance[`${ny}-${nx}`] = distance[`${y}-${x}`] + 1; 34 | previous[`${ny}-${nx}`] = node; 35 | 36 | if (grid[ny][nx] !== "orange" && grid[ny][nx] !== "blue") { 37 | grid[ny][nx] = "green"; 38 | } 39 | } 40 | } 41 | } 42 | 43 | counter--; 44 | setGrid(grid); 45 | } 46 | 47 | reconstructPath(grid, previous, endPoints.end); 48 | } -------------------------------------------------------------------------------- /website/src/algorithms/bestfirstsearch.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors, heuristic } from "./helper"; 3 | 4 | export default function bestfirstsearch(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let queue = new PriorityQueue(); 7 | let count = 0; 8 | let costs: any = {}; 9 | 10 | const getChecked = (grid: States[][], node: [number, number]) => { 11 | let list: [number, number][] = []; 12 | for (const neighbor of getNeighbors(grid, node)) { 13 | let [ny, nx] = neighbor; 14 | if (grid[ny][nx] === "red" || grid[ny][nx] === "orange") { 15 | list.push(neighbor); 16 | } 17 | } 18 | return list; 19 | } 20 | 21 | const reconstructPath = (grid: States[][], costs: any, current: [number, number]) => { 22 | while (grid[current[0]][current[1]] !== "orange") { 23 | current = getChecked(grid, current).reduce((min, key) => { 24 | if (costs[genKey(key)] < costs[genKey(min)]) { 25 | return key; 26 | } 27 | return min; 28 | }); 29 | 30 | if (grid[current[0]][current[1]] === "orange") { 31 | break; 32 | } 33 | 34 | grid[current[0]][current[1]] = "purple"; 35 | } 36 | } 37 | 38 | costs[genKey(endPoints.start)] = 0; 39 | queue.enqueue(endPoints.start, 0, 0); 40 | 41 | while (!queue.isEmpty()) { 42 | let [y, x] = queue.dequeue(); 43 | 44 | let color = grid[y][x]; 45 | 46 | if (color === "blue") { 47 | reconstructPath(grid, costs, endPoints.end); 48 | break; 49 | } 50 | 51 | if (color !== "orange") { 52 | grid[y][x] = "red"; 53 | } 54 | 55 | for (const neighbor of getNeighbors(grid, [y,x])) { 56 | let [ny, nx] = neighbor; 57 | let cost = costs[`${y}-${x}`] + 1; 58 | 59 | if (!Object.keys(costs).includes(`${ny}-${nx}`) || cost < costs[`${ny}-${nx}`]) { 60 | costs[`${ny}-${nx}`] = cost; 61 | count++; 62 | queue.enqueue(neighbor, cost + heuristic("manhattan", grid, neighbor, endPoints.end), count); 63 | 64 | if (grid[ny][nx] !== "orange" && grid[ny][nx] !== "blue") { 65 | grid[ny][nx] = "green"; 66 | } 67 | } 68 | } 69 | 70 | setGrid(grid); 71 | } 72 | } -------------------------------------------------------------------------------- /website/src/algorithms/bfs.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { getNeighbors, reconstructPath } from "./helper"; 3 | 4 | export default function bfs(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid = [...colorGrid]; 6 | let nodes: [number, number][] = []; 7 | nodes.push(endPoints.start) 8 | 9 | let previous : any = {}; 10 | 11 | let found : boolean = false; 12 | while (nodes.length > 0 && !found) { 13 | let [i, j] = nodes.shift()!; 14 | 15 | if (grid[i][j] === "red") { 16 | continue; 17 | } 18 | 19 | if (grid[i][j] !== "orange") { 20 | grid[i][j] = "red"; 21 | } 22 | 23 | for (const neighbor of getNeighbors(grid, [i,j])) { 24 | let [ni, nj] = neighbor; 25 | let nColor : States = grid[ni][nj]; 26 | if (nColor !== "red") { 27 | if (nColor === "blue") { 28 | previous[`${ni}-${nj}`] = [i,j]; 29 | found = true; 30 | reconstructPath(grid, previous, endPoints.end); 31 | break; 32 | } else { 33 | previous[`${ni}-${nj}`] = [i,j]; 34 | nodes.push(neighbor); 35 | 36 | if (nColor !== "orange") { 37 | grid[ni][nj] = "green"; 38 | } 39 | } 40 | } 41 | } 42 | 43 | setGrid(grid); 44 | } 45 | } -------------------------------------------------------------------------------- /website/src/algorithms/dfs.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { getNeighbors, reconstructPath } from "./helper"; 3 | 4 | export default function dfs(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let stack: [number, number][] = []; 7 | let previous : any = {}; 8 | 9 | stack.push(endPoints.start); 10 | 11 | let found: boolean = false; 12 | 13 | while (stack.length > 0 && !found) { 14 | let [y, x] = stack.pop()!; 15 | 16 | if (grid[y][x] === "red") { 17 | continue; 18 | } 19 | 20 | if (grid[y][x] !== "orange") { 21 | grid[y][x] = "red"; 22 | } 23 | 24 | for (const neighbor of getNeighbors(grid, [y, x])) { 25 | let [ny, nx] = neighbor; 26 | if (grid[ny][nx] !== "red") { 27 | if (grid[ny][nx] === "blue") { 28 | previous[`${ny}-${nx}`] = [y,x]; 29 | found = true; 30 | reconstructPath(grid, previous, endPoints.end); 31 | break; 32 | } else { 33 | previous[`${ny}-${nx}`] = [y,x]; 34 | stack.push(neighbor); 35 | 36 | if (grid[ny][nx] !== "orange") { 37 | grid[ny][nx] = "green" 38 | } 39 | } 40 | } 41 | } 42 | setGrid(grid) 43 | } 44 | } -------------------------------------------------------------------------------- /website/src/algorithms/dijkstra.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors, reconstructPath } from "./helper"; 3 | 4 | export default function dijkstra(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let queue: PriorityQueue = new PriorityQueue(); 7 | 8 | let gscore: any = {}; 9 | for (let i = 0; i < grid.length; i++) { 10 | for (let j = 0; j < grid[0].length; j++) { 11 | gscore[`${i}-${j}`] = Infinity; 12 | } 13 | } 14 | gscore[genKey(endPoints.start)] = 0; 15 | 16 | let previous: any = {}; 17 | 18 | queue.enqueue(endPoints.start, 0, 0); 19 | 20 | let count: number = 0 21 | 22 | while(!queue.isEmpty()) { 23 | let [y, x] = queue.dequeue(); 24 | 25 | if (grid[y][x] === "blue") { 26 | reconstructPath(grid, previous, [y, x]); 27 | break; 28 | } 29 | 30 | if (grid[y][x] !== "orange") { 31 | grid[y][x] = "red"; 32 | } 33 | 34 | for (const neighbor of getNeighbors(grid, [y, x])) { 35 | let [ny, nx] = neighbor; 36 | if (grid[ny][nx] !== "red") { 37 | let temp_gscore = gscore[`${y}-${x}`] + 1; 38 | 39 | if (temp_gscore < gscore[`${ny}-${nx}`]) { 40 | gscore[`${ny}-${nx}`] = temp_gscore; 41 | previous[`${ny}-${nx}`] = [y, x]; 42 | 43 | count++; 44 | queue.enqueue(neighbor, temp_gscore, count); 45 | 46 | if (grid[ny][nx] !== "orange" && grid[ny][nx] !== "blue") { 47 | grid[ny][nx] = "green"; 48 | } 49 | } 50 | } 51 | } 52 | 53 | setGrid(grid); 54 | } 55 | } -------------------------------------------------------------------------------- /website/src/algorithms/floodfill.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, genKey, getNeighbors } from "./helper"; 3 | 4 | export default function flood_fill(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid = [...colorGrid]; 6 | 7 | let distances : any = {}; 8 | for (let i = 0; i < grid.length; i++) { 9 | for (let j = 0; j < grid[0].length; j++) { 10 | distances[`${i}-${j}`] = Infinity; 11 | } 12 | } 13 | distances[genKey(endPoints.end)] = 0; 14 | 15 | let openSet: PriorityQueue = new PriorityQueue(); 16 | openSet.enqueue(endPoints.end, 0, 0); 17 | 18 | while (!openSet.isEmpty()) { 19 | let item = openSet.p_dequeue(); 20 | 21 | let distance = item![0]; 22 | let [y, x] = item![1]; 23 | 24 | if (grid[y][x] === "red") { 25 | continue; 26 | } 27 | 28 | if (grid[y][x] === "orange") { 29 | break; 30 | } 31 | 32 | if (grid[y][x] !== "blue") { 33 | grid[y][x] = "red"; 34 | } 35 | 36 | for (const neighbor of getNeighbors(grid, [y, x])) { 37 | let [ny, nx] = neighbor; 38 | 39 | if (grid[ny][nx] !== "red") { 40 | distances[`${ny}-${nx}`] = distance + 1; 41 | openSet.enqueue(neighbor, distance + 1, 0); 42 | if (!(grid[ny][nx] === "orange" || grid[ny][nx] === "blue")) { 43 | grid[ny][nx] = "green"; 44 | } 45 | } 46 | } 47 | } 48 | 49 | if (distances[genKey(endPoints.start)] !== Infinity) { 50 | let current = endPoints.start; 51 | let found = false; 52 | let best : [number, [number, number] | undefined] = [Infinity, undefined]; 53 | while (!found) { 54 | for (const neighbor of getNeighbors(grid, current)) { 55 | let [ny, nx] = neighbor; 56 | if (grid[ny][nx] === "blue") { 57 | found = true; 58 | break; 59 | } 60 | if (distances[`${ny}-${nx}`] < best[0]) { 61 | best = [distances[`${ny}-${nx}`], neighbor]; 62 | } 63 | } 64 | if (!found) { 65 | let [ny, nx] = best[1]!; 66 | grid[ny][nx] = "purple"; 67 | current = best[1]!; 68 | } 69 | } 70 | } 71 | 72 | setGrid(grid); 73 | } -------------------------------------------------------------------------------- /website/src/algorithms/floydwarshall.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { containsPos, getNeighbors, getUnvisitedNodes, findPos } from "./helper"; 3 | 4 | export default function floydwarshall(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let nodes: [number, number][] = getUnvisitedNodes(grid, endPoints.start); 7 | let V: number = nodes.length; 8 | 9 | const reconstructPath = (grid: States[][], nodes: [number, number][], distances: number[][]) => { 10 | let [u, v] = [findPos(nodes, endPoints.start), findPos(nodes, endPoints.end)]; 11 | 12 | let path: [number, number][] = []; 13 | let left: number[] = []; 14 | let right: number[] = []; 15 | 16 | for (let k = V - 1; k >= 0; k--) { 17 | if (distances[u][v] === distances[u][k] + distances[k][v]) { 18 | if (grid[nodes[k][0]][nodes[k][1]] !== "orange" && 19 | grid[nodes[k][0]][nodes[k][1]] !== "blue" && 20 | !left.includes(distances[u][k]) && 21 | !right.includes(distances[k][v])) { 22 | path.push(nodes[k]); 23 | left.push(distances[u][k]); 24 | right.push(distances[k][v]); 25 | } 26 | } 27 | } 28 | 29 | let curr: [number, number] = endPoints.end; 30 | 31 | while (grid[curr[0]][curr[1]] !== "orange") { 32 | for (const node of getNeighbors(grid, curr)) { 33 | let [ny, nx] = node; 34 | 35 | if (grid[ny][nx] === "orange") { 36 | curr = endPoints.start; 37 | break; 38 | } 39 | 40 | if (containsPos(path, node)) { 41 | grid[ny][nx] = "purple"; 42 | path = path.filter((val) => { 43 | return !(val[0] === ny && val[1] === nx); 44 | }); 45 | curr = node; 46 | } 47 | setGrid(grid); 48 | } 49 | } 50 | } 51 | 52 | let distance: number[][] = []; 53 | 54 | for (let v = 0; v < V; v++) { 55 | distance[v] = []; 56 | for (let w = 0; w < V; w++) { 57 | distance[v][w] = Infinity; 58 | } 59 | } 60 | 61 | for (let i = 0; i < V; i++) { 62 | for (let j = 0; j < V; j++) { 63 | if (containsPos(getNeighbors(grid, nodes[j]), nodes[i])) { 64 | distance[i][j] = 1; 65 | } 66 | } 67 | } 68 | 69 | for (let i = 0; i < V; i++) { 70 | distance[i][i] = 0; 71 | } 72 | 73 | let checked: [number, number][] = [endPoints.start]; 74 | 75 | for (let k = 0; k < V; k++) { 76 | for (let i = 0; i < V; i++) { 77 | for (let j = 0; j < V; j++) { 78 | if (distance[i][j] > distance[i][k] + distance[k][j]) { 79 | distance[i][j] = distance[i][k] + distance[k][j]; 80 | 81 | let iCol = grid[nodes[i][0]][nodes[i][1]]; 82 | let jCol = grid[nodes[j][0]][nodes[j][1]]; 83 | let kCol = grid[nodes[k][0]][nodes[k][1]]; 84 | 85 | if (iCol !== "orange" && iCol !== "blue" && 86 | jCol !== "orange" && jCol !== "blue" && 87 | kCol !== "orange" && kCol !== "blue") { 88 | checked.push(nodes[i]); 89 | checked.push(nodes[j]); 90 | checked.push(nodes[k]); 91 | grid[nodes[i][0]][nodes[i][1]] = "red"; 92 | grid[nodes[j][0]][nodes[j][1]] = "red"; 93 | grid[nodes[k][0]][nodes[k][1]] = "red"; 94 | } 95 | } 96 | 97 | setGrid(grid); 98 | } 99 | setGrid(grid); 100 | } 101 | setGrid(grid); 102 | } 103 | 104 | if (containsPos(nodes, endPoints.end)) { 105 | reconstructPath(grid, nodes, distance); 106 | } 107 | } -------------------------------------------------------------------------------- /website/src/algorithms/fmm.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel"; 2 | import { PriorityQueue, genKey, getNeighbors, reconstructPath } from "./helper"; 3 | 4 | export default function fmm(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid = [...colorGrid]; 6 | 7 | let costs : any = {}; 8 | for (let i = 0; i < grid.length; i++) { 9 | for (let j = 0; j < grid[0].length; j++) { 10 | costs[`${i}-${j}`] = Infinity; 11 | } 12 | } 13 | costs[genKey(endPoints.start)] = 0; 14 | 15 | let far : any = []; 16 | for (let i = 0; i < grid.length; i++) { 17 | for (let j = 0; j < grid[0].length; j++) { 18 | if (grid[i][j] !== "black" && grid[i][j] !== "orange") { 19 | far.push([i, j]); 20 | } 21 | } 22 | } 23 | 24 | let considered: PriorityQueue = new PriorityQueue(); 25 | considered.enqueue(endPoints.start, 0, 0); 26 | 27 | let previous : any = {}; 28 | 29 | while (!considered.isEmpty()) { 30 | let item = considered.p_dequeue(); 31 | 32 | let ccost : number = item![0]; 33 | let [y, x] : [number, number] = item![1]; 34 | 35 | if (grid[y][x] === "blue") { 36 | reconstructPath(grid, previous, [y, x]); 37 | break; 38 | } 39 | 40 | if (grid[y][x] !== "orange") { 41 | grid[y][x] = "red"; 42 | } 43 | 44 | for (const neighbor of getNeighbors(grid, [y, x])) { 45 | let [ny, nx] = neighbor; 46 | 47 | if (!considered.contains([y, x]) && grid[ny][nx] !== "red") { 48 | let cost : number = ccost + 1; 49 | 50 | if (cost < costs[`${ny}-${nx}`]) { 51 | costs[`${ny}-${nx}`] = cost; 52 | previous[`${ny}-${nx}`] = [y, x]; 53 | 54 | if (!!far.find((i : [number, number]) => i[0] === ny && i[1] === nx)) { 55 | considered.enqueue(neighbor, cost, 0); 56 | const index = far.findIndex((i : [number, number]) => i[0] === ny && i[1] === nx); 57 | 58 | if (index > -1) { 59 | far.splice(index, 1); 60 | } 61 | 62 | if (grid[ny][nx] !== "orange" && grid[ny][nx] !== "blue") { 63 | grid[ny][nx] = "green"; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | setGrid(grid); 70 | } 71 | } -------------------------------------------------------------------------------- /website/src/algorithms/fringe.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { genKey, getNeighbors, heuristic } from "./helper"; 3 | 4 | function reconstructPath(grid: States[][], path: any, current: [number, number]) { 5 | while (path.hasOwnProperty(`${current[0]}-${current[1]}`)) { 6 | if (grid[current[0]][current[1]] !== "orange") { 7 | current = path[`${current[0]}-${current[1]}`][1] 8 | if (grid[current[0]][current[1]] !== "orange") { 9 | grid[current[0]][current[1]] = "purple"; 10 | } 11 | } else { 12 | break; 13 | } 14 | } 15 | } 16 | 17 | export default function fringe(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 18 | let grid = [...colorGrid]; 19 | 20 | let fringe = [endPoints.start]; 21 | let cache : any = {}; 22 | cache[genKey(endPoints.start)] = [0, null]; 23 | 24 | let f_limit = heuristic("manhattan", grid, endPoints.start, endPoints.end); 25 | 26 | let found = false; 27 | 28 | while (!found && fringe.length > 0) { 29 | let f_min = Infinity; 30 | 31 | for (let node of fringe) { 32 | let g = cache[genKey(node)][0]; 33 | 34 | let f = g + heuristic("manhattan", grid, node, endPoints.end); 35 | 36 | if (f > f_limit) { 37 | f_min = Math.min(f, f_min); 38 | continue; 39 | } 40 | if (grid[node[0]][node[1]] === "blue") { 41 | reconstructPath(grid, cache, node); 42 | found = true; 43 | break; 44 | } 45 | 46 | if (!(grid[node[0]][node[1]] === "orange")) { 47 | grid[node[0]][node[1]] = "red"; 48 | } 49 | 50 | for (let neighbor of getNeighbors(grid, node)) { 51 | let g_neighbor = g + 1; 52 | 53 | if (Object.keys(cache).includes(genKey(neighbor))) { 54 | let g_cache = cache[genKey(neighbor)][0]; 55 | 56 | if (g_neighbor >= g_cache) { 57 | continue; 58 | } 59 | } 60 | 61 | if (fringe.some(a => a.every((val, i) => val === neighbor[i]))) { 62 | fringe = fringe.filter(a => !(a.every((val, i) => val === neighbor[i]))); 63 | } 64 | 65 | fringe.push(neighbor); 66 | 67 | cache[genKey(neighbor)] = [g_neighbor, node]; 68 | 69 | if (!(grid[neighbor[0]][neighbor[1]] === "orange" || grid[neighbor[0]][neighbor[1]] === "blue")) { 70 | grid[neighbor[0]][neighbor[1]] = "green"; 71 | } 72 | } 73 | fringe = fringe.filter(a => !(a.every((val, i) => val === node[i]))); 74 | } 75 | f_limit = f_min; 76 | setGrid(grid); 77 | } 78 | } -------------------------------------------------------------------------------- /website/src/algorithms/gbfs.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel"; 2 | import { PriorityQueue, getNeighbors, heuristic, reconstructPath } from "./helper"; 3 | 4 | export default function gbfs(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let queue: PriorityQueue = new PriorityQueue(); 7 | queue.enqueue(endPoints.start, heuristic("manhattan", grid, endPoints.start, endPoints.end), 0); 8 | 9 | let count: number = 0; 10 | let found: boolean = false; 11 | 12 | let previous: any = {}; 13 | 14 | while (!queue.isEmpty() && !found) { 15 | let [y, x] = queue.dequeue(); 16 | 17 | if (grid[y][x] === "red") { 18 | continue; 19 | } 20 | 21 | if (grid[y][x] !== "orange" && grid[y][x] !== "blue") { 22 | grid[y][x] = "red"; 23 | } 24 | 25 | for (const neighbor of getNeighbors(grid, [y,x])) { 26 | let [ny, nx] = neighbor; 27 | if (grid[ny][nx] !== "red") { 28 | if (grid[ny][nx] === "blue") { 29 | previous[`${ny}-${nx}`] = [y, x]; 30 | found = true; 31 | reconstructPath(grid, previous, endPoints.end); 32 | break; 33 | } 34 | 35 | if (grid[ny][nx] !== "orange") { 36 | grid[ny][nx] = "green"; 37 | } 38 | 39 | count++; 40 | let distance: number = heuristic("manhattan", grid, neighbor, endPoints.end); 41 | 42 | previous[`${ny}-${nx}`] = [y, x]; 43 | queue.enqueue(neighbor, distance, count); 44 | } 45 | } 46 | 47 | setGrid(grid); 48 | } 49 | } -------------------------------------------------------------------------------- /website/src/algorithms/gbls.ts: -------------------------------------------------------------------------------- 1 | import { Interests, States } from "../components/Panel" 2 | import { PriorityQueue, getNeighbors, heuristic, reconstructPath } from "./helper"; 3 | 4 | export default function gbls(colorGrid: States[][], setGrid: React.Dispatch>, endPoints: Interests) { 5 | let grid: States[][] = [...colorGrid]; 6 | let queue: PriorityQueue = new PriorityQueue(); 7 | queue.enqueue(endPoints.start, heuristic("manhattan", grid, endPoints.start, endPoints.end), 0); 8 | 9 | let count: number = 0; 10 | let found: boolean = false; 11 | 12 | let previous: any = {}; 13 | let lastdir: [number, number] = [-1, -1]; 14 | 15 | while (!queue.isEmpty() && !found) { 16 | let [y, x] = queue.dequeue(); 17 | 18 | if (grid[y][x] === "red") { 19 | continue; 20 | } 21 | 22 | if (grid[y][x] !== "orange" && grid[y][x] !== "blue") { 23 | grid[y][x] = "red"; 24 | } 25 | 26 | let neighbors: [number, number][] = getNeighbors(grid, [y,x]); 27 | 28 | let templastdir = lastdir; 29 | 30 | if (templastdir[0] !== -1) { 31 | let tempNeighbors = neighbors.filter((n) => (n[0] - y === templastdir[0]) && (n[1] - x === templastdir[1])); 32 | for (const n of neighbors) { 33 | let i = tempNeighbors.findIndex(item => item[0] === n[0] && item[1] === n[1]); 34 | if (i === -1) { 35 | tempNeighbors.push(n); 36 | } 37 | } 38 | neighbors = tempNeighbors; 39 | } 40 | 41 | for (const neighbor of neighbors) { 42 | let [ny, nx] = neighbor; 43 | if (grid[ny][nx] !== "red") { 44 | if (grid[ny][nx] === "blue") { 45 | previous[`${ny}-${nx}`] = [y, x]; 46 | found = true; 47 | reconstructPath(grid, previous, endPoints.end); 48 | break; 49 | } 50 | 51 | if (grid[ny][nx] !== "orange") { 52 | grid[ny][nx] = "green"; 53 | } 54 | 55 | count++; 56 | let distance: number = heuristic("manhattan", grid, neighbor, endPoints.end); 57 | 58 | previous[`${ny}-${nx}`] = [y, x]; 59 | queue.enqueue(neighbor, distance, count); 60 | 61 | lastdir = [ny - y, nx - x]; 62 | } 63 | } 64 | 65 | setGrid(grid); 66 | } 67 | } -------------------------------------------------------------------------------- /website/src/algorithms/helper.ts: -------------------------------------------------------------------------------- 1 | import { States } from "../components/Panel"; 2 | 3 | export function getNeighbors(grid: States[][], pos: [number, number]) { 4 | let [i, j] = pos; 5 | 6 | let neighbors : [number, number][] = []; 7 | 8 | if (i - 1 >= 0 && grid[i - 1][j] !== "black") { 9 | neighbors.push([i - 1, j]); 10 | } 11 | if (j - 1 >= 0 && grid[i][j - 1] !== "black") { 12 | neighbors.push([i, j - 1]); 13 | } 14 | if (i + 1 < grid.length && grid[i + 1][j] !== "black") { 15 | neighbors.push([i + 1, j]); 16 | } 17 | if (j + 1 < grid[0].length && grid[i][j + 1] !== "black") { 18 | neighbors.push([i, j + 1]); 19 | } 20 | 21 | return neighbors; 22 | } 23 | 24 | export function reconstructPath(grid: States[][], path: any, current: [number, number]) { 25 | while (path.hasOwnProperty(`${current[0]}-${current[1]}`)) { 26 | if (grid[current[0]][current[1]] !== "orange") { 27 | current = path[`${current[0]}-${current[1]}`] 28 | if (grid[current[0]][current[1]] !== "orange") { 29 | grid[current[0]][current[1]] = "purple"; 30 | } 31 | } else { 32 | break; 33 | } 34 | } 35 | } 36 | 37 | function d_manhattan(grid: States[][], node1: [number, number], node2: [number, number]) { 38 | let m = manhattan(node1, node2); 39 | 40 | let neighbors: [number, number][] = getNeighbors(grid, node1); 41 | let penalty = neighbors.length; 42 | for (const node of neighbors) { 43 | if (grid[node[0]][node[1]] !== "red" && grid[node[0]][node[1]] !== "green") { 44 | penalty--; 45 | } 46 | } 47 | 48 | return m - penalty; 49 | } 50 | 51 | function manhattan(node1: [number, number], node2: [number, number]) { 52 | let [y1, x1] = node1; 53 | let [y2, x2] = node2; 54 | 55 | return Math.abs(x1 - x2) + Math.abs(y1 - y2); 56 | } 57 | 58 | export function heuristic(type: string, grid: States[][], start: [number, number], end: [number, number]) { 59 | switch(type) { 60 | case "d_manhattan": 61 | return d_manhattan(grid, start, end); 62 | case "manhattan": 63 | return manhattan(start, end); 64 | default: 65 | return manhattan(start, end); 66 | } 67 | } 68 | 69 | export function getUnvisitedNodes(grid: States[][], start: [number, number]) { 70 | let queue = new Queue(); 71 | let queuehash: [number, number][] = [start]; 72 | queue.enqueue(start); 73 | 74 | while (!queue.isEmpty) { 75 | let current: [number, number] = queue.dequeue() as [number, number]; 76 | 77 | for (const neighbor of getNeighbors(grid, current)) { 78 | if (!queuehash.some(node => { 79 | return node[0] === neighbor[0] && node[1] === neighbor[1]; 80 | })) { 81 | queue.enqueue(neighbor); 82 | queuehash.push(neighbor); 83 | } 84 | } 85 | } 86 | 87 | return queuehash; 88 | } 89 | 90 | export function containsPos(list: [number, number][], target: [number, number]) { 91 | return list.some((pos) => { 92 | return pos[0] === target[0] && pos[1] === target[1]; 93 | }) 94 | } 95 | 96 | export function findPos(list: [number, number][], target: [number, number]) { 97 | let index = -1; 98 | for (const [i, node] of list.entries()) { 99 | if (node[0] === target[0] && node[1] === target[1]) { 100 | index = i; 101 | break; 102 | } 103 | } 104 | return index; 105 | } 106 | 107 | export function genKey(node: [number, number]) { 108 | return `${node[0]}-${node[1]}`; 109 | } 110 | 111 | class QElement { 112 | element: any; 113 | priority: number; 114 | count: number; 115 | 116 | constructor(element : any, priority: number, count: number) { 117 | this.element = element; 118 | this.priority = priority; 119 | this.count = count; 120 | } 121 | } 122 | 123 | export class PriorityQueue { 124 | private items: QElement[]; 125 | 126 | constructor() { 127 | this.items = []; 128 | } 129 | 130 | size() { 131 | return this.items.length; 132 | } 133 | 134 | resize(size: number) { 135 | this.items = this.items.slice(0, size); 136 | } 137 | 138 | contains(element: [number, number]) { 139 | return this.items.some((item) => { 140 | return JSON.stringify(item.element) === JSON.stringify(element); 141 | }); 142 | } 143 | 144 | enqueue(element: [number, number], priority: number, count: number) { 145 | const qElement = new QElement(element, priority, count); 146 | let contain = false; 147 | 148 | for (let i = 0; i < this.items.length; i++) { 149 | if (this.items[i].priority > qElement.priority) { 150 | this.items.splice(i, 0, qElement); 151 | contain = true; 152 | break; 153 | } else if (this.items[i].priority === qElement.priority) { 154 | if (this.items[i].count < qElement.count) { 155 | this.items.splice(i + 1, 0, qElement); 156 | } else { 157 | this.items.splice(i, 0, qElement); 158 | } 159 | contain = true; 160 | break; 161 | } 162 | } 163 | 164 | if (!contain) { 165 | this.items.push(qElement); 166 | } 167 | } 168 | 169 | p_dequeue() { 170 | if (this.isEmpty()) { 171 | return; 172 | } 173 | 174 | const element = this.items.shift(); 175 | 176 | return [element?.priority, element?.element]; 177 | } 178 | 179 | dequeue() { 180 | if (this.isEmpty()) { 181 | return; 182 | } 183 | 184 | return this.items.shift()!.element; 185 | } 186 | 187 | isEmpty() { 188 | return this.items.length === 0; 189 | } 190 | } 191 | 192 | class Queue { 193 | private elements: { [key: number]: T }; 194 | private head: number; 195 | private tail: number; 196 | 197 | constructor() { 198 | this.elements = {}; 199 | this.head = 0; 200 | this.tail = 0; 201 | } 202 | 203 | enqueue(element: T): void { 204 | this.elements[this.tail] = element; 205 | this.tail++; 206 | } 207 | 208 | dequeue(): T | undefined { 209 | const item = this.elements[this.head]; 210 | delete this.elements[this.head]; 211 | this.head++; 212 | return item; 213 | } 214 | 215 | peek(): T | undefined { 216 | return this.elements[this.head]; 217 | } 218 | 219 | get length(): number { 220 | return this.tail - this.head; 221 | } 222 | 223 | get isEmpty(): boolean { 224 | return this.length === 0; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /website/src/algorithms/ida*.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/iddfs.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/jps.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/lbfs.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/lpa*.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/r-lifo.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/spfa.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/algorithms/theta*.ts: -------------------------------------------------------------------------------- 1 | export {} -------------------------------------------------------------------------------- /website/src/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } -------------------------------------------------------------------------------- /website/src/css/panel.css: -------------------------------------------------------------------------------- 1 | .panel { 2 | display: grid; 3 | overflow: auto; 4 | width: 100vw; 5 | height: 100vh; 6 | grid-template-columns: auto auto; 7 | background-color: cadetblue; 8 | } 9 | 10 | @media only screen and (max-width: 600px) { 11 | .panel { 12 | justify-content: center; 13 | } 14 | 15 | .options { 16 | justify-self: center; 17 | grid-row-start: 1; 18 | grid-row-end: 1; 19 | } 20 | 21 | .grid { 22 | align-self: baseline; 23 | margin: 10px 10px 10px 10px !important; 24 | grid-row-start: 2; 25 | grid-row-end: 2; 26 | } 27 | } 28 | 29 | .grid { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | margin: 10px 20px 35px 0px; 34 | } 35 | 36 | .options { 37 | display: flex; 38 | flex-direction: column; 39 | margin: 10px; 40 | max-width: 295px; 41 | } 42 | 43 | .option { 44 | margin: 1px; 45 | } -------------------------------------------------------------------------------- /website/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './css/index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | 10 | root.render( 11 | 12 | 13 | 14 | ); -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext", 8 | "es6" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "noImplicitAny": true, 24 | "noImplicitThis": true, 25 | "strictNullChecks": true 26 | }, 27 | "include": [ 28 | "src" 29 | ] 30 | } --------------------------------------------------------------------------------