{"id":149,"date":"2021-11-30T00:49:12","date_gmt":"2021-11-30T00:49:12","guid":{"rendered":"https:\/\/tults.com\/?page_id=149"},"modified":"2024-09-30T20:54:54","modified_gmt":"2024-09-30T20:54:54","slug":"maze-solving-and-generation","status":"publish","type":"page","link":"https:\/\/tults.com\/index.php\/software-engineering\/maze-solving-and-generation\/","title":{"rendered":"Maze Solving and Generation"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Synopsis: This project originally started out as a quick and snappy comparison of Dijkstra and A*. This however proved difficult, as I had nothing to test it on. As such, I then had to figure out many many other things, starting with, building a maze. I used mazes because I think that they are fascinating, the classic myth of the Minotaur in the labyrinth has always enticed me. So, to test Dijkstra and A*, I learned how to build a maze. Then, after building mazes, I learned to solve them, and then how to apply other methods of solving, like A* and Dijkstra.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Introduction<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">While at university, the discussion of various path finding and solving algorithms was often discussed, and like most things in university, we used  a cookie cutter version of these algorithms to solve problems. I like to understand problems and solutions, and so I decided to see if I could use these algorithms to solve a problem of my own design and creation. Cursory google searching revealed that mazes are a great path finding problem. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Making Mazes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The first challenge to solve when solving a maze is having a maze to solve. So instead of just googling mazes and solving something someone else built, I wanted to solve any arbitrary maze. The best way to solve something arbitrary, is to build an arbitrary something generator. And that is exactly what I did. Research through google showed many different algorithms to generate mazes. I wanted mazes that were difficult for humans to solve, with long paths that spiral out of control. I wanted a maze that mirrored real life &#8211; one where you could spend quite some time on the wrong path before realizing that there was a mistake. As such, I settled on the recursive back tracking algorithm.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Maze_generation_algorithm#Recursive_implementation\">Recursive Backtracking<\/a> is a very intuitive way of making a maze, it does however use recursion, which some languages (<a href=\"https:\/\/www.reddit.com\/r\/Python\/comments\/4hkds8\/do_you_recommend_using_recursion_in_python_why_or\/\">python<\/a>) are not very good at. My first ever course at uni was all about recursive programming, so I am very familiar with the principal, but I have very distinct memories of pulling my hair at maximum recursion depth errors in python. So, I had to re-adapt the algorithm to no longer be recursive, and instead have it nested in a loop. This was clunkier and less elegant than the recursive solution, but it still worked. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The recursive back tracking algorithm starts at a certain point (the entrance) and then looks for every possible move around it. It then makes that move, and pushes the move made onto a stack. It then repeats (recursively), until it can&#8217;t make any more moves. Then it pops moves off the stack until it finds one that can make moves, and starts again, making moves until it is stuck. Then, pops until it can make moves again, and so on. When it has no moves left in the stack, the game is done. This however, does not add an exit, so we then look for every white square in the last column, and then randomly pick one, then turn it white, to give us an exit. This can all be seen in the following code block, which generates mazes on the home page. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/Setup canvas\n&lt;canvas id=\"Maze\" width=\"300\" height=\"300\" style=\"border:1px solid #000000;\"&gt;&lt;\/canvas&gt;\n&lt;script&gt;\n\/\/ Function includes all moves made and current tile, is to check to see if there are any white squares around a tile, and is a helper function to get valid moves\nfunction getValidMovesHelper(moves, tile){\n    \/\/Assumes that it is a valid move, then proves otherwise\n    let out = true;\n    \/\/surrounding here is the surrounding moves\n    let surrounding = &#91;];\n    \/\/If there is any surrounding tiles, we push back 1 to surrounding\n    if(moves.includes(tile+20)){\n        surrounding.push(1);\n    }\n    if(moves.includes(tile-20)){\n        surrounding.push(1);\n    }\n    if(moves.includes(tile-1)){\n        surrounding.push(1);\n    }\n    if(moves.includes(tile+1)){\n        surrounding.push(1);\n    }\n    \/\/If there is any more than one item in the surrounding tiles, it is an invalid move, and we return false\n    if(surrounding.length &gt; 1){\n        out = false;\n    }\n    return out;\n}\n\n\/\/ getValid moves, takes a list of all moves, a tile, and a list of all walls and returns all surrounding tiles that it can move to\nfunction getValidMoves(moves, tile, walls){\n    \/\/ Out hereis the output moves\n    let out = &#91;];\n   \n    \/\/Check up\n    if(!moves.includes(tile-1)){\n        if(!walls.includes(tile-1)){\n            if(getValidMovesHelper(moves, tile-1)){\n                out.push(tile-1);\n            }\n        }\n    }\n    \/\/Check Down\n    if(!moves.includes(tile+1)){\n        if(!walls.includes(tile+1)){\n            if(getValidMovesHelper(moves, tile+1)){\n                out.push(tile+1);\n            }\n        }\n    }\n    \/\/Check Left\n    if(!moves.includes(tile-20)){\n        if(!walls.includes(tile-20)){\n            if(getValidMovesHelper(moves, tile-20)){\n                out.push(tile-20);\n            }\n        }\n    }\n    \/\/Check Right\n    if(!moves.includes(tile+20)){\n        if(!walls.includes(tile+20)){\n            if(getValidMovesHelper(moves, tile+20)){\n                out.push(tile+20);\n            }\n        }\n    }\n    return out;\n}\n\n\/\/gen Maze helper loops through the valid moves, and actually implements recursive back tracking\nfunction genMazeHelper(moves, walls){\n    \/\/stacks is the stack of all moves\n    var stacks = &#91;];\n    \/\/ copy all moves into the stack\n    for(i = 0; i &lt; moves.length; i++){\n        stacks.push(moves&#91;i]);\n    }\n    \/\/ declare variables so I don't do it a million times in a while loop\n    var curTile = 0;\n    var allMoves = &#91;];\n    var randNum = 0;\n    var sizeStack = stacks.length;\n    \/\/ While there are moves in the stack, see if there are any valid moves from the top, if there is, make moves until you can't and push to stack and allMoves, then pop until 0. \n    while(stacks.length &gt; 0){\n        sizeStack = stacks.length;\n        curTile = stacks&#91;sizeStack-1];\n        allMoves = getValidMoves(moves, curTile, walls);\n        if(allMoves.length == 0){\n            stacks.pop();\n        }\n        else{\n            randNum = Math.floor(Math.random() * allMoves.length);\n            moves.push(allMoves&#91;randNum]);\n            stacks.push(allMoves&#91;randNum]);\n        }\n    }\n    return moves;\n}\n\nfunction genMaze(){\n    \/\/ Arrays represents maze, size 20*20\n    \/\/ first, fill out maze with Black. \n    let out = &#91;];\n    for(let i = 0; i &lt; 400; i++){\n        out.push(1);\n    }\n    let walls = &#91;]\n    \/\/Then fill out the maze. The edges are 0-&gt;19; 379-&gt;399; 20, 40, 60, 80 so ADD TO LIST\n    for(let i = 0; i &lt; 20; i++){\n        walls.push(i);\n        walls.push(i+379);\n        walls.push(i*20);\n        walls.push(i*20-1);\n    }\n    walls.push(399);\n    \/\/Now draw out start moves\n    let moves = &#91;];\n    moves.push(30);\n    moves.push(31);\n    moves.push(32);\n    moves.push(33, 34, 29, 29);\n    moves.push(51);\n    \n    \/\/Now, we need to generate the rest of the maze!\n    moves = genMazeHelper(moves, walls);\n    \/\/Add entrance\n    moves.push(11);\n    \/\/ Add the exit\n    let validExits = &#91;];\n    for(let m = 360; m &lt; 380; m++)\n    {\n        if(moves.includes(m)){\n            validExits.push(m);\n        }\n    }\n    let randNum = Math.floor(Math.random() * validExits.length);\n    moves.push(validExits&#91;randNum]+20);\n    for(let n = 0; n &lt; moves.length; n++){\n        let cur2 = moves&#91;n];\n        out&#91;cur2] = 0;\n    }\n    return out;\n}\n\n\/\/Init canvas and actually draw\nvar c = document.getElementById(\"Maze\");\nc.parentElement.style.textAlign = \"center\";\nvar ctx = c.getContext(\"2d\");\nlet curMaze = genMaze()\nvar total = 0;\nfor(let i = 0; i &lt; 20; i++){\n   for(let j = 0; j &lt; 20; j++){\n       var val = curMaze&#91;total];\n       if(val != 0){\n           ctx.beginPath();\n           ctx.fillRect(i*15, j*15, 15, 15);\n           ctx.stroke();\n       }\n       total = total + 1;\n   }\n}\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That is a teeny tiny 20*20 maze. So, for a bigger one, see below, this is a dynamically generated maze, new every time, guaranteed to have a solution:<\/p>\n\n\n\n<canvas id=\"Maze1\" width=\"600\" height=\"600\" style=\"border:1px solid #000000;\"><\/canvas>\n<script>\nlet curMaze = [];\n\nfunction getValidMovesHelper(moves, tile){\n    let out = true;\n    let test = [];\n    if(moves.includes(tile+40)){\n        test.push(1);\n    }\n    if(moves.includes(tile-40)){\n        test.push(1);\n    }\n    if(moves.includes(tile-1)){\n        test.push(1);\n    }\n    if(moves.includes(tile+1)){\n        test.push(1);\n    }\n    if(test.length > 1){\n        out = false;\n    }\n    return out;\n}\n\nfunction getValidMoves(moves, tile, walls){\n    let out = [];\n    \/\/Check up\n    if(!moves.includes(tile-1)){\n        if(!walls.includes(tile-1)){\n            if(getValidMovesHelper(moves, tile-1)){\n                out.push(tile-1);\n            }\n        }\n    }\n    \/\/Check Down\n    if(!moves.includes(tile+1)){\n        if(!walls.includes(tile+1)){\n            if(getValidMovesHelper(moves, tile+1)){\n                out.push(tile+1);\n            }\n        }\n    }\n    \/\/Check Left\n    if(!moves.includes(tile-40)){\n        if(!walls.includes(tile-40)){\n            if(getValidMovesHelper(moves, tile-40)){\n                out.push(tile-40);\n            }\n        }\n    }\n    \/\/Check Right\n    if(!moves.includes(tile+40)){\n        if(!walls.includes(tile+40)){\n            if(getValidMovesHelper(moves, tile+40)){\n                out.push(tile+40);\n            }\n        }\n    }\n    return out;\n}\n\nfunction genMazeHelper(moves, walls){\n    var stacks = [];\n    for(i = 0; i < moves.length; i++){\n        stacks.push(moves[i]);\n    }\n    var curTile = 0;\n    var allMoves = [];\n    var randNum = 0;\n    var sizeStack = stacks.length;\n    while(stacks.length > 0){\n        sizeStack = stacks.length;\n        curTile = stacks[sizeStack-1];\n        allMoves = getValidMoves(moves, curTile, walls);\n        if(allMoves.length == 0){\n            stacks.pop();\n        }\n        else{\n            randNum = Math.floor(Math.random() * allMoves.length);\n            moves.push(allMoves[randNum]);\n            stacks.push(allMoves[randNum]);\n        }\n        for(let n = 0; n < moves.length; n++){\n            let cur2 = moves[n];\n            curMaze[cur2] = 0;\n        }\n        window.requestAnimationFrame(draw);\n    }\n    return moves;\n}\n\nfunction genMaze(){\n    \/\/ Arrays represents maze, size 20*20\n    \/\/ first, fill out maze with Black. \n    let out = [];\n    for(let i = 0; i < 1600; i++){\n        out.push(1);\n    }\n    let walls = []\n    \/\/Then fill out the maze. The edges are 0->19; 379->399; 20, 40, 60, 80 so ADD TO LIST\n    for(let i = 0; i < 40; i++){\n        walls.push(i);\n        walls.push(i+1559);\n        walls.push(i*40);\n        walls.push(i*40-1);\n    }\n    walls.push(1600);\n    \/\/Now draw out start moves\n    let moves = [];\n    moves.push(57, 58, 59, 60, 61, 62, 63, 100, 140, 180);\n    \n    \/\/Now, we need to generate the rest of the maze!\n    moves = genMazeHelper(moves, walls);\n    \/\/Add entrance\n    moves.push(20);\n    \/\/ Add the exit\n    let validExits = [];\n    for(let m = 1520; m < 1560; m++)\n    {\n        if(moves.includes(m)){\n            validExits.push(m);\n        }\n    }\n    let randNum = Math.floor(Math.random() * validExits.length);\n    moves.push(validExits[randNum]+40);\n    for(let n = 0; n < moves.length; n++){\n        let cur2 = moves[n];\n        out[cur2] = 0;\n    }\n    return out;\n}\n\ncurMaze = genMaze();\n\n\nfunction draw(){\n    var c = document.getElementById(\"Maze1\");\n    c.parentElement.style.textAlign = \"center\";\n    var ctx = c.getContext(\"2d\");\n    ctx.clearRect(0, 0, 600, 600);\n    var total = 0;\n    for(var i = 0; i < 40; i++){\n       for(var j = 0; j < 40; j++){\n           var val = curMaze[total];\n           if(val != 0){\n                   ctx.beginPath();\n                   ctx.fillRect(i*15, j*15, 15, 15);\n                   ctx.stroke();\n           }\n           total = total + 1;\n       }\n    }\n}\n\n\n<\/script>\n\n\n\n<p class=\"wp-block-paragraph\">So now, we have a maze generated that looks pretty, we need to solve it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">My Solution<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">As any good Scientist or Engineer knows, you should always try and come up with your own conclusion\/ solutions before realizing your idea is slow and garbage and some crazy person like Dijkstra has come up with a near perfect mathematical solution to the problem. So, in that spirit, here is the brief explanation of my solution. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check through every tile of the maze, and check if we are on a dead end square (one surrounding tile). Then check the surrounding tile and see if it is a dead end as well, then mark it as a dead end as well. Then keep going until we can get through the whole maze without finding any dead ends, and we have our path through the maze. It is reminiscent of when I solved mazes as a kid and would colour in all the dead ends red until the maze was solved. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is not a very good solution. It requires many iterations through the maze, and, in the worst case it could take quite some time. However, it was my own solution before I stole someone elses idea and it is fun because of that. So, without further ado, see my solution below, stepping through and looking pretty, using the maze from above.<\/p>\n\n\n\n<canvas id=\"Maze2\" width=\"600\" height=\"600\" style=\"border:1px solid #000000;\"><\/canvas>\n <button type=\"button\" id=\"SolveMaze1\" onclick=\"window.requestAnimationFrame(draw2);\">Solve<\/button> \n <button type=\"button\" id=\"ResetMazes\" onclick=\"resetMazes();\">Reset<\/button> \n<script>\nvar solvedMaze = [];\nfor(var i = 0; i < curMaze.length; i++){\nsolvedMaze.push(curMaze[i]);\n}\n\n\n\nvar solved = false;\nvar timer = 0;\n\nfunction isDeadEnd(maze, tile){\nvar out = true;\nvar up = maze[tile-1];\nvar down = maze[tile+1];\nvar left = maze[tile];\nvar right = maze[tile];\nif(tile > 40 && tile < 1560){\nvar left = maze[tile-40];\nvar right = maze[tile+40];\n}\nelse{\nout = false;\n}\nvar TotalAround = [];\nif(up != 0){\nTotalAround.push(1);\n}\nif(down != 0){\nTotalAround.push(1);\n}\nif(left != 0){\nTotalAround.push(1);\n}\nif(right != 0){\nTotalAround.push(1);\n}\nif(TotalAround.length <3){\nout = false;\n}\n\nreturn out;\n}\n\nfunction solveMaze(){\nchanged = false;\nfor(var i = 0; i < solvedMaze.length; i++){\nvar curTile = solvedMaze[i];\nif(curTile == 0){\nvar isDead = isDeadEnd(solvedMaze, i);\nif(isDead){\nchanged = true;\nsolvedMaze[i] = 2;\n}\n}\n}\nif(!changed){\nsolved = true;\n}\n}\n\nfunction draw3(){\nvar c = document.getElementById(\"Maze2\");\nc.parentElement.style.textAlign = \"center\";\nvar ctx = c.getContext(\"2d\");\nctx.clearRect(0, 0, 600, 600);\n\nvar total = 0;\nfor(var i = 0; i < 40; i++){\nfor(var j = 0; j < 40; j++){\nvar val2 = solvedMaze[total];\nif(val2 == 1){\nctx.beginPath();\nctx.fillRect(i*15, j*15, 15, 15);\nctx.fillStyle = '#000000';\nctx.stroke();\n}\ntotal = total+1;\n}\n}\n}\n\nfunction drawSolvedInit(){\nfor(var i = 0; i<solvedMaze.length; i++){\nif(solvedMaze[i]== 0){\nsolvedMaze[i] = 3;\n}\n}\nwindow.requestAnimationFrame(drawSolved);\n}\n\nvar timer2 = 0;\n\nfunction drawSolvedFinal(){\nvar c = document.getElementById(\"Maze2\");\nc.parentElement.style.textAlign = \"center\";\nvar ctx = c.getContext(\"2d\");\nctx.clearRect(0, 0, 600, 600);\nvar total = 0;\nfor(var i = 0; i < 40; i++){\nfor(var j = 0; j < 40; j++){\nctx.globalAlpha = 1;\nvar val2 = solvedMaze[total];\nif(val2 == 1){\nctx.beginPath();\nctx.fillStyle = \"#000000\";\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\nif(val2 == 3){\nctx.beginPath();\nctx.fillStyle = '#98FB98';\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\ntotal = total + 1;\n}\n}\n\n}\n\nfunction drawSolved(){\nvar c = document.getElementById(\"Maze2\");\nc.parentElement.style.textAlign = \"center\";\nvar ctx = c.getContext(\"2d\");\nctx.clearRect(0, 0, 600, 600);\nvar total = 0;\nfor(var i = 0; i < 40; i++){\nfor(var j = 0; j < 40; j++){\nctx.globalAlpha = 1;\nvar val2 = solvedMaze[total];\nif(val2 == 1){\nctx.beginPath();\nctx.fillStyle = \"#000000\";\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\nif(val2 == 2){\nctx.globalAlpha = (1600-timer2)\/1600;\nctx.beginPath();\nctx.fillStyle = '#F47174';\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\nif(val2 == 3){\nctx.globalAlpha = timer2\/1600;\nctx.beginPath();\nctx.fillStyle = '#98FB98';\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\ntotal = total + 1;\n}\n}\nctx.globalAlpha = 1;\ntimer2 = timer2 + 20;\nif(timer2 > 1600){\ndrawSolvedFinal();\n}\nelse{\nwindow.requestAnimationFrame(drawSolved);\n}\n}\n\nfunction draw2(){\nvar c = document.getElementById(\"Maze2\");\nc.parentElement.style.textAlign = \"center\";\nvar ctx = c.getContext(\"2d\");\nctx.clearRect(0, 0, 600, 600);\n\nvar total = 0;\nfor(var i = 0; i < 40; i++){\nfor(var j = 0; j < 40; j++){\nvar val2 = solvedMaze[total];\nif(val2 == 1){\nctx.beginPath();\nctx.fillStyle = \"#000000\";\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\nif(val2 == 2){\nctx.beginPath();\nctx.fillStyle = '#F47174';\nctx.fillRect(i*15, j*15, 15, 15);\nctx.stroke();\n}\ntotal = total + 1;\n}\n}\ntimer = timer+1;\n\nconsole.log(timer);\n\nif(timer%2==0){\nif(!solved){\nsolveMaze();\n}\n}\nif(!solved){\nwindow.requestAnimationFrame(draw2);\n}\nelse{\nwindow.requestAnimationFrame(drawSolvedInit);\n}\n}\ndraw3();\n\/\/window.requestAnimationFrame(draw2);\n\nfunction resetMazes(){\nsolvedMaze = [];\ncurMaze = [];\ncurMaze = genMaze();\nfor(var i = 0; i < curMaze.length; i++){\nsolvedMaze.push(curMaze[i]);\n}\nsolved = false;\ntimer = 0;\ntimer2 = 0;\ndraw();\ndraw3();\n}\n\n<\/script>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, the question becomes, how to make this faster? The above solver is deliberately slowed because I like how it looks, however, it is not very efficient. It needs to look through the whole maze, and finds any dead end it can, then it starts again. To speed this up, there are two options. We can store the earliest square found, and the oldest square found with each iteration, and then only search in that zone. This means that we will reduce the search space with each successive loop, and we can cut out big chunks of the maze that do not need to be checked. This can further be optimized, by keeping a track of the white squares, and only checking them. This way, we are skipping checks for any of the black squares that sit in our maze, and we can improve performance accordingly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In addition to this, when we find a dead end, we can then search all the surrounding tiles and see if they are now dead ends as well. This means, that some of the longer paths will be almost the same computation time as the shorter paths, and we can stop stepping through the whole maze so quickly. It is difficult to quantify how much time this will save our algorithm, but there is a comparison of all the times in the end section.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Dijkstra<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Dijkstra's algorithm finds the shortest path from a root node to every other node, until a target is reached. It is often considered to be one of the most useful graph theory algorithms and it can be modified easily to solve many different problems. It is useful because it does not require searching every possible path, unlike some other algorithms. It is used in network routing, believed to be used in mapping, such as google maps, etc. Much like recursive back tracking mentioned before, it is a multi-tool of an algorithm and can be used to solve a variety of different problems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How does it work? Well, it is quite simply, we have a start node and a list of surrounding candidate nodes. They are all assigned a value of infinity, assuming they are infinitely far away, except for our origin point. Then, find the closest node, and repeat until we find destination. If we can't find the destination, we back track through our list, and try again with previous nodes. <\/p>\n\n\n\n<canvas id=\"Dijkstra\" width=\"600\" height=\"600\" style=\"border:1px solid #000000;\"><\/canvas>\n    <button type=\"button\" id=\"SolveDijkstra\" onclick=\"window.requestAnimationFrame(solveDijkstraInit);\">Solve<\/button>\n    <button type=\"button\" id=\"ResetDijkstra\" onclick=\"resetDijkstra();\">Reset<\/button>\n<script>\nvar solvedDMaze = [];\nvar discoveredNodes = []; \/\/ Array to hold discovered nodes\nvar path = []; \/\/ Array to hold the final path\nvar distances = []; \/\/ Distances array to store the shortest distance to each node\nvar previous = []; \/\/ To store the previous node in the path\nvar priorityQueue = []; \/\/ Priority queue (min-heap-like behavior)\nvar dijkstraExit = null;\nvar dijkstraAnimating = false; \/\/ Flag to control the animation\nvar fadeProgress = 1; \/\/ Global fade progress\n\n\/\/ Initialize the maze data\nfor (var i = 0; i < curMaze.length; i++) {\n    solvedDMaze.push(curMaze[i]);\n}\n\nfunction getSurroundingNum(pos) {\n    var out = 0;\n    var up = solvedDMaze[pos - 1]; \/\/ Up\n    var down = solvedDMaze[pos + 1]; \/\/ Down\n    var left = solvedDMaze[pos - 40]; \/\/ Left\n    var right = solvedDMaze[pos + 40]; \/\/ Right\n\n    if (up != 1) out += 1;\n    if (down != 1) out += 1;\n    if (left != 1) out += 1;\n    if (right != 1) out += 1;\n\n    return out;\n}\n\nfunction findNodes() {\n    for (var q = 0; q < solvedDMaze.length; q++) {\n        var cTile = solvedDMaze[q];\n        if (cTile != 1) {\n            var numSur = getSurroundingNum(q);\n            if (numSur > 2) {\n                solvedDMaze[q] = 2; \/\/ Mark it as a node\n                discoveredNodes.push(q); \/\/ Store discovered node for animation\n            }\n        }\n    }\n}\n\nfunction findExit() {\n    for (var i = 1560; i < 1600; i++) {\n        if (solvedDMaze[i] != 1) {\n            return i; \/\/ Return the exit position\n        }\n    }\n}\n\n\/\/ Dijkstra's helper functions\nfunction getSurroundingNodes(curNode) {\n    let neighbors = [];\n\n    \/\/ Up\n    if (curNode - 1 >= 0 && solvedDMaze[curNode - 1] != 1) {\n        neighbors.push(curNode - 1);\n    }\n\n    \/\/ Down\n    if (curNode + 1 < solvedDMaze.length &#038;&#038; solvedDMaze[curNode + 1] != 1) {\n        neighbors.push(curNode + 1);\n    }\n\n    \/\/ Left\n    if (curNode % 40 > 0 && solvedDMaze[curNode - 40] != 1) {\n        neighbors.push(curNode - 40);\n    }\n\n    \/\/ Right\n    if (curNode % 40 < 39 &#038;&#038; solvedDMaze[curNode + 40] != 1) {\n        neighbors.push(curNode + 40);\n    }\n\n    return neighbors;\n}\n\nfunction solveDijkstraInit() {\n    findNodes(); \/\/ Prepare nodes\n    var startNode = 60; \/\/ Starting point\n    dijkstraExit = findExit(); \/\/ Find the exit point\n    distances = new Array(solvedDMaze.length).fill(Infinity); \/\/ Initialize distances\n    previous = new Array(solvedDMaze.length).fill(null); \/\/ Initialize previous array\n\n    distances[startNode] = 0; \/\/ Distance to start node is 0\n    priorityQueue = [[startNode, distances[startNode]]]; \/\/ Initialize priority queue with start node\n\n    dijkstraAnimating = true;\n    pathFound = false; \/\/ Reset path found flag\n    fadeProgress = 1; \/\/ Reset fade progress\n    animateDijkstraStep(); \/\/ Start the animation\n}\n\nfunction animateDijkstraStep() {\n    if (priorityQueue.length > 0) {\n        \/\/ Sort the priority queue to get the node with the smallest total distance\n        priorityQueue.sort((a, b) => a[1] - b[1]); \n        let [curNode, curDist] = priorityQueue.shift(); \/\/ Get the closest node based on total distance\n\n        if (curNode === dijkstraExit) {\n            path = constructPath(previous, 60, dijkstraExit); \/\/ Construct the path\n            pathFound = true;\n            fadeProgress = 1; \/\/ Reset fading progress for discovered nodes\n            animateFinalPath(); \/\/ Highlight the final path\n            return;\n        }\n\n        let neighbors = getSurroundingNodes(curNode); \/\/ Get neighbors\n\n        neighbors.forEach(neighbor => {\n            \/\/ Calculate the distance to the neighbor (all edges have cost 1)\n            let newDist = curDist + 1; \/\/ Cost to move to the neighbor\n            if (newDist < distances[neighbor]) {\n                distances[neighbor] = newDist; \/\/ Update distance\n                previous[neighbor] = curNode; \/\/ Update the path\n                priorityQueue.push([neighbor, newDist]); \/\/ Add to the queue with updated distance\n                solvedDMaze[neighbor] = 2; \/\/ Mark as discovered\n                discoveredNodes.push(neighbor); \/\/ Store discovered node\n            }\n        });\n\n        \/\/ Draw the current state of the maze\n        drawMaze();\n\n        \/\/ Request the next animation frame\n        setTimeout(animateDijkstraStep, 100); \/\/ Animation speed\n    }\n}\n\nfunction constructPath(prev, start, exit) {\n    let path = [];\n    for (let at = exit; at !== null; at = prev[at]) {\n        path.push(at); \/\/ Construct the path\n    }\n    return path.reverse(); \/\/ Return reversed path\n}\n\nfunction animateFinalPath() {\n    timer = 0;\n    fadeProgress = 1; \/\/ Reset fading progress for the non-path nodes\n\n    function drawFinalPathFade() {\n        var c = document.getElementById(\"Dijkstra\");\n        var ctx = c.getContext(\"2d\");\n        ctx.clearRect(0, 0, 600, 600);\n\n        \/\/ Calculate the percentage of the maze that has been solved\n        let percentageSolved = timer \/ path.length;\n\n        \/\/ Adjust fading speed based on the percentage solved\n        fadeProgress = 1 - percentageSolved;\n\n        \/\/ Draw the entire maze\n        for (var i = 0; i < 40; i++) {\n            for (var j = 0; j < 40; j++) {\n                var val2 = solvedDMaze[i * 40 + j];\n                ctx.beginPath();\n                if (val2 === 1) {\n                    ctx.fillStyle = \"#000000\"; \/\/ Wall\n                } else if (val2 === 2) {\n                    ctx.fillStyle = `rgba(173, 216, 230, ${fadeProgress})`; \/\/ Discovered nodes fading (light blue)\n                } else if (val2 === 3) {\n                    ctx.fillStyle = '#32CD32'; \/\/ Final path highlighted in green\n                } else {\n                    ctx.fillStyle = '#FFFFFF'; \/\/ Free space\n                }\n                ctx.fillRect(j * 15, i * 15, 15, 15);\n                ctx.stroke();\n            }\n        }\n\n        \/\/ Gradually fade non-path nodes while drawing the path\n        if (timer < path.length) {\n            \/\/ Draw path nodes one by one in green\n            let pathNode = path[timer];\n            solvedDMaze[pathNode] = 3; \/\/ Change to final path (green)\n\n            drawMaze();\n            timer++;\n            setTimeout(drawFinalPathFade, 30); \/\/ Control fade speed\n        } else {\n            \/\/ Ensure all nodes are faded to white and path is fully drawn\n            fadeProgress = 0;\n            drawMaze();\n        }\n    }\n\n    drawFinalPathFade(); \/\/ Start the fade animation\n}\n\nfunction drawMaze() {\n    var c = document.getElementById(\"Dijkstra\");\n    var ctx = c.getContext(\"2d\");\n    ctx.clearRect(0, 0, 600, 600);\n\n    var total = 0;\n    for (var i = 0; i < 40; i++) {\n        for (var j = 0; j < 40; j++) {\n            var val2 = solvedDMaze[total];\n            ctx.beginPath();\n            if (val2 == 1) {\n                ctx.fillStyle = \"#000000\"; \/\/ Wall\n            } else if (val2 == 2) {\n                ctx.fillStyle = `rgba(173, 216, 230, ${fadeProgress})`; \/\/ Fading discovered nodes (light blue)\n            } else if (val2 == 3) {\n                ctx.fillStyle = '#32CD32'; \/\/ Final path (green)\n            } else {\n                ctx.fillStyle = \"#FFFFFF\"; \/\/ Free space\n            }\n            ctx.fillRect(j * 15, i * 15, 15, 15);\n            ctx.stroke();\n            total++;\n        }\n    }\n}\n\n\/\/ Reset function\nfunction resetDijkstra() {\n    solvedDMaze = []; \/\/ Reset the maze\n    discoveredNodes = []; \/\/ Reset discovered nodes\n    path = []; \/\/ Reset path\n    timer = 0; \/\/ Reset timer\n    fadeProgress = 1; \/\/ Reset fade\n\n    \/\/ Reinitialize the maze data\n    for (var i = 0; i < curMaze.length; i++) {\n        solvedDMaze.push(curMaze[i]);\n    }\n\n    \/\/ Redraw the maze\n    drawMaze();\n}\n\ndrawMaze(); \/\/ Initial draw of the maze\n\n<\/script>\n\n\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Synopsis: This project originally started out as a quick and snappy comparison of Dijkstra and A*. This however proved difficult, as I had nothing to test it on. As such, I then had to figure out many many other things, starting with, building a maze. I used mazes because I think that they are fascinating,Continue reading &rarr;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":43,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-149","page","type-page","status-publish","hentry","no-thumb"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/pages\/149","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/comments?post=149"}],"version-history":[{"count":236,"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/pages\/149\/revisions"}],"predecessor-version":[{"id":862,"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/pages\/149\/revisions\/862"}],"up":[{"embeddable":true,"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/pages\/43"}],"wp:attachment":[{"href":"https:\/\/tults.com\/index.php\/wp-json\/wp\/v2\/media?parent=149"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}