Pathfinder

Repo

The pathfinder project was made so I could learn how a pathfinding algorithm works, since it is not an algorithm I have ever programmed, and also how python programming in the web work, using

Functionality

The pathfinder consists of a grid, initially 10x10, of squares (which I will call nodes from now on), with the two edges (row 1, column 1 and row 10, column 10) being labeled START and END respectively. By clicking "Run", the algorithm finds the shortest path betweem those two nodes. The grid is color coded based on the type of node:

Components

The Algorithm

The pathfinder algorithm itself is composed of a breadth-first search starting from the START node, adding nodes to a queue and dequeing as it goes. It continues until a path is found or the queue is completely empty, meaning no path is possible between the two endpoints

Nodes are added to a "parent dictionary" once stepped in. Whenever a path is found, the algorithm walks backwards from that path dicitonary, now starting at the END node. This will show the shortest path

The process for finding a path is as follows:

  1. Disable other interactions: It starts by making sure no other actions or buttons can be clicked while it's running
  2. Mark the start and goal: It identifies where the search will begin and where it should end by locating two points on the grid (the start and end points)
  3. Prepare to explore: It creates a list where the first point (start) is added to kick off the search. Another list is kept to remember which places have already been visited to avoid checking the same spot more than once
  4. Search process: The algorithm begins looking for the path:
  5. Skip obstacles and repeat:
  6. Finish the search: The process repeats until either the destination is found, or there are no more possible moves. If no path is found, the algorithm notifies that it's impossible to reach the goal

Time Complexity

Passing the code through Big O Calc, the following explanation is gathered:

The time complexity of this pathfinding algorithm is O(V + E), where V is the number of vertices (cells) and E is the number of edges (connections between cells). This is because the algorithm uses a breadth-first search (BFS) approach to explore the graph of cells, visiting each cell and its neighbors once.

The space complexity of this algorithm is O(V), where V is the number of vertices (cells) in the graph. This is because the algorithm uses a queue to keep track of cells to visit next, and a list to keep track of cells that have already been visited. The maximum number of cells that can be in the queue at any given time is equal to the number of cells in the graph.

Overall, this algorithm is efficient in terms of time complexity as it explores the graph in a systematic manner, visiting each cell and its neighbors in a predictable order. The space complexity is also reasonable as it only requires storing a queue and a list of seen cells.

While BFS is effective for unweighted grids, other algorithms like A* or Dijkstra's could provide more optimal paths in different scenarios. For example, A* incorporates heuristics to estimate the distance to the end node, allowing it to find the shortest path more efficiently in larger or more complex grids. However, BFS's simplicity and guaranteed pathfinding make it a solid choice for beginners and educational purposes.

The following table will showcase the time complexity as it relates to grid size. Three calculations are done:

The process of calculating the cases goes as follows:

  1. The grid size is set and the endpoints are placed in the corners (top-left and bottom-right nodes)
  2. The blocking is randomized until a possible path is available
  3. The algorithm is run and the process time is calculated
  4. Steps 2 and 3 are repeated 5 times and the average is taken

A special button was created to automate this process, the Test button

Here is the gathered info:

Grid Size Best Case Average Case Worst Case
3x3 1.39 1.39 3.99
5x5 3.00 2.19 2.99
10x10 10.39 1.79 9.00
20x20 48.99 35.59 29.40
50x50 480.60 326.79 170.39
75x75 1846.79 1183.99 507.59
90x90 4858.78 3241.39 1458.59
100x100 6880.60 4434.99 1792.39

The following is a graph showing these results in a more visual manner:

It is possible to see that the "best case" actually performed the worst time-wise. This can be explained by the fact that a less-blocked grid results in more nodes having to be stepped-on, and, therefore, more nodes added to the queue, which means more calculations are necessary. A more blocked grid, as in the worst case, may have fewer possible paths, or even no possible paths, but the possible ones can be calculated quickly, as fewer nodes have to be checked and more nodes can be outright ignored.

Challenges and Solutions

I decided to write the entire implementation using py-script, utilizing JavaScript to interact with the page elements. The entire program was made thrugh a py-script tag instead of linking with a Python file

Although this made the code simpler and easier, as I am pretty familiar with Python, it also meant I had no debugger, autocompletion or warnings, which slowed down debugging quite a lot

As a solution, I utilized the py-terminal tag extensively, outputting and printing any errors or assertion I made along the way

One big hurdle was modyfying the grid. Initally, the grid had fixed size of 10x10, meaning the top-left node had id "00" and bottom-right had id "99". however, by introducing variable grid sizes, id values were not reliable anymore. if the id value "119" was given to the algorithm, how could that be understood? Is it row 11 column 9 or row 1 column 19?

My solution to this was fixing the ids to 4 digits. Since grid sizes bigger than 99x99 are not very desirable as computational speed slows down too much, a 4-digit id allows the algorithm to always understand the given values. The value "0119" could be translated easily to row 1, column 19

Of course, this also brought problems in regards to the calculations being performed by the algorithm, as now it is necessary to account for string manipulation. For example: if given the id "1", the algorithm has to add a leading "0" to consider it a valid id, otherwise the number will fall thorugh and no cell will be fetched

Learning Outcomes

This project was mainly done so I could learn pathfinding algorithms, but it also ended up becoming my first responsive and user-interactable website (of course ignoring the Digital Airport as that was mostly just a JavaScript learning project and not super interactable apart from two select tags)

I learned how to operate with py-script as was as how to modify the Document Object Model (DOM) to create responsive web applications, definetely a skill I will be utilizing in my next projects

The Code and Examples

You can check out the whole code for the pathfinder in the Git repo linked at the top. The entire code is condensed into one py-script tag

I will take this section to showcase some screenshots that may be of interest and that present the components of the project