
Show HN: Solve and generate mazes with JavaScript on HTML Canvas - davidim
https://github.com/dmaydan/Maze_Solver_Generator
======
amenghra
[https://bost.ocks.org/mike/algorithms/#maze-
generation](https://bost.ocks.org/mike/algorithms/#maze-generation) has some
visualizations for various different maze generation algorithms.

~~~
forrestthewoods
Great link. Here's another good one.

[https://www.jamisbuck.org/mazes/](https://www.jamisbuck.org/mazes/)

~~~
delinom
And here's another one. It introduces maze building ideas in a simple way.

[http://www.jamisbuck.org/presentations/rubyconf2011/index.ht...](http://www.jamisbuck.org/presentations/rubyconf2011/index.html)

------
jere
It would be cool to see the cells checked as well. To someone unfamiliar with
BFS, the demo makes it seem like the process of finding the solution doesn't
involve exploring any other tiles than the solution itself.

~~~
tolien
A pretty neat example of this is mbostock's A* search:
[https://beta.observablehq.com/@mbostock/best-first-
search](https://beta.observablehq.com/@mbostock/best-first-search)

------
xtiansimon
I explored the test site and tried different maze sizes with values in the
50s, 70s, 110s, 150 (including odd numbers). Curiously, I found the maze could
be visually divided into four Cartesian-like quadrants. Consistently the
middle divider had only one gap, and other major quadrants also consistently
have only one gap. So, when I wondered if BFS solution might be different if I
ran it multiple times, I couldn't tell. Because of the curious constraint I
just mentioned, all paths had to traverse these major divisions through the
same gap. In other words, if there was variance, it would be constrained to a
very small movement in a corner.

I imagine if I dived into the code I would realize if my hypothesis that
multiple runs of the BFS would always result in the same solution, was
plausible or not. And it might also be the case if I understood BFS more, I
would also know if it would always result in the same solution.

But how much fun if I could see it visually with a few clicks in the GUI.

Thanks and Good luck.

~~~
davidim
Thanks so much for the compliment. This is probably a bi-product of the maze
generation algorithm I used. Essentially, at every step, the algorithm bisects
the maze horizontally or vertically - then, it chooses a random cell along
this bisection to leave open (that way the 2 resulting regions are still
connected). Then, the same algorithm is performed on the 2 new regions.
Recursion continues until it no longer makes sense to continue bisecting.

Wikipedia has an article on maze generation that explains the algorithm -
[https://en.m.wikipedia.org/wiki/Maze_generation_algorithm](https://en.m.wikipedia.org/wiki/Maze_generation_algorithm)

~~~
crdrost
I can confirm that that's exactly what happened, different generation
algorithms have a different "texture" to them in precisely this way and those
textures do sometimes have a byproduct of making the maze easier or harder to
solve.

The algorithm with the least helpful "texture" is probably Wilson's algorithm,
which basically says: choose a point at random out in the unsolved territory
and begin a random walk: when you reach the solved territory (the maze as it
is already known), add those points back to the maze; when you self-intersect
you should delete the random walk back to where you self-intersected. (For the
very first walk, furthermore, there is only one "solved" point in the maze so
you may wish to speed up maze generation by doing a different random walk that
just "busts down walls" into the as-yet-unknown area; this is called Aldous-
Broder and it has the opposite problem where it tries to randomly walk into
unsolved space so it takes forever when the unsolved space becomes rare.) So
one would start from,

    
    
        function* randomWalkPoints({ x, y }, height, width) {
          const incr = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, {dx: 0, dy: -1}, {dx: 0, dy: 1}];
          while (true) {
            const { dx, dy } = incr[Math.floor(4 * Math.random())];
            const new_x = x + dx, new_y = y + dy;
            if (new_x >= 0 && new_x < width && new_y >= 0 && new_y < width) {
              x = new_x;
              y = new_y;
              yield { x, y };
            }
          }
        }
    

Now, the algorithm might get a bit weird because it looks like you are using
block mazes rather than conventional thin-walled mazes; every n-by-m thin-
walled maze has an embedding as a block maze of size (2n+1)-by-(2m+1) simply
by making (0, y) and (x, 0) all walls, every (odd, odd) point is empty space
representing one of the squares of the original maze, every (even, even) point
is a block, and (odd, even) and (even, odd) points are blocks if and only if
there is a vertical/horizontal wall between the corresponding squares in the
original maze. So all of the resources you will find are talking about mazes
that look like,

    
    
        ╔═══════════╦═══════╗
        ║           ║       ║
        ║   ╔═══         ═══╣
        ║   ║               ║
        ║   ║    ═══╗    ═══╣
        ║   ║       ║       ║
        ║   ╚═══════╣   ║   ║
        ║           ║   ║   ║
        ╠═══════    ╚═══╣   ║
        ║               ║   ║
        ╚═══════════════╩═══╝
    

and then in your context this becomes:

    
    
        X X X X X X X X X X X
        X . . . . . X . . . X
        X . X X X . X . X X X
        X . X . . . . . . . X
        X . X . X X X . X X X
        X . X . . . X . . . X
        X . X X X X X . X . X
        X . . . . . X . X . X
        X X X X X . X X X . X
        X . . . . . . . X . X
        X X X X X X X X X X X
    

So I'm not 100% sure whether you would want to just restrict yourself to block
mazes of this type, or whether you would want to just try something like "I
modify Wilson's to keep track of walls I have already placed, too, and then
when I intersect with myself on a path that already hit a block wall, I
discard back to the open-space which placed that block" to get a slightly more
general vision of the problem... I am not sure how you'd want to handle that.

~~~
airstrike
+1 for the ascii art alone. HN never ceases to amaze me

------
primaryobjects
Nice. I created a maze solver in js and canvas as well, solving using the A*
and Tremaux algorithms.

You can even create your own mazes by passing json in the url.

Simple Maze Solver in HTML5 and Js
[http://www.primaryobjects.com/maze/](http://www.primaryobjects.com/maze/)

------
jschulenklopper
This will be an interesting book to read... also showcasing multiple maze
generation algorithms: [https://pragprog.com/book/jbmaze/mazes-for-
programmers](https://pragprog.com/book/jbmaze/mazes-for-programmers)

~~~
asciimo
I love this book. It's on my retirement pile! (Hopefully mazes will still be
relevant by then.)

------
bhargav
Bug Report: [http://prntscr.com/mcyc1b](http://prntscr.com/mcyc1b)

When the website loads, if I put in a large number such as 500 and hit the
generate button, it does not clear the previous puzzle and overlays on top.
Second click still shows it bit now its a but transparent. Third click removes
it altogether. I tried to skim through the code but not sure what the cause is
given that you create a new Maze every single time. It seems to be some sort
of canvas rendering bug as if I clear the canvas [1] it seems to work.

1: [https://stackoverflow.com/a/2142549](https://stackoverflow.com/a/2142549)

~~~
davidim
Thanks a lot for the bug report! You're right - I was forgetting to clear the
canvas on render. So I added ctx.clearRect(0, 0, WIDTH, HEIGHT); to the start
of the render method.

------
robbrown451
I'm surprised it doesn't allow you to "draw" on it to solve the maze. That
seems like an essential (and easy to add) feature.

~~~
davidim
What sort of drawing are you thinking? Dragging the mouse around on the screen
or navigating with the arrows?

~~~
robbrown451
Well I would pick drawing with a mouse, but arrow keys would work in a pinch.

------
wcarss
Very cool! I started making a very unpolished game based on a similar sort of
map design a few months ago for ludum dare -- caution that there are sounds on
this link and no volume control:
[https://wcarss.ca/maze_of_sacrifice/](https://wcarss.ca/maze_of_sacrifice/)

------
kowdermeister
It's cool, but I expected some explanation about the steps, challenges.
Luckily the HN crowd filled that in :)

------
akovaski
The solutions for mazes of size 50-200 look quite like a river on a map; which
is pleasant to me.

------
benbristow
Nice work!

Seems to freeze if you put a number too high though. Perhaps you should add a
limit?

~~~
davidim
Thanks for checking it out! I am wondering how big is the number you are
putting in? If the number is really high, it's probably causing a stack
overflow. I would limit the size, but each browser has its own recursion
limits so that would be quite a pain. In retrospect, I probably should not
have used a recursive algorithm for this task :).

~~~
oneeyedpigeon
I put in a value of 500, and while it didn't freeze completely, it really
struggled to respond.

~~~
davidim
Recursive division may be elegant, but it seems that may also have its
disadvantages.

~~~
wakamoleguy
One trick you could use is to add a setTimeout or asnyc/await somewhere during
the recursion. By forcing a step to be asynchronous, the stack count will be
reset.

------
dan-robertson
Solving mazes of the kind of size one can see tends to be an easy problem in
that once you know about eg breadth first search it isn’t too hard to write a
program that will solve this sort of maze. Solving them is still necessary to
show there is a solution but it isn’t super interesting because it is sort of
obvious that breadth-first search gives the best solution and if you know
about it then it is quite easy to think of using breadth-first search.

A much harder problem is generating mazes.

The algorithm used here seems to be:

1\. Know how to generate a tiny maze 2\. To generate a big maze, divide it
into smaller mazes, generate those, then cut some things out (the tiny maze
algorithm) to connect the sub mazes.

You can see the bias in the algorithm from the grid pattern inside the
generated mazes.

One might wonder how to generate mazes such that each possible valid maze has
the same probability of being generated.

First one should define what a valid maze is. Here it seems the definition
should be something like:

A maze is an array of cells which are either white or black such that:

1\. There is no 2x2 square of white squares

2\. There is no 2x2 square of black squares (maybe this could be changed?)

3\. Any two white squares are connected by a unique* path of
(horizontally/vertically) adjacent white squares with no loops

* one may want to drop the uniqueness requirement.

I do not know how one would uniformly generate all such mazes.

Another definition of maze that I do know how to generate uniformly is one
made of thick maze cells and thin separating lines:

A maze is a spanning tree of the graph where the nodes are cells and the edges
are possible passages (so for a squarish maze the graph would basically be a
grid: nodes at integer coordinates and edges between any
horizontally/vertically adjacent nodes).

The problem of generating such a maze uniformly is therefore the same as that
of picking a spanning tree such that each spanning tree is chosen with the
same probability. An algorithm for this is _Wilson’s algorithm_ and works as
follows:

1\. Pick (not necessarily randomly) some node and add it to your spanning tree
(so your tree has one node and no edges)

2\. Pick (not necessarily randomly) some node not in your tree. If no such
node exists then you are done. Say that the current path is this node.

3\. Randomly (uniformly) choose an edge touching the last node of the current
path (it is ok to not choose the edge which is the last edge of the current
path). Add that edge and the node on the end to the current path.

4 (a). If the current path contains a loop then delete all nodes (and edges)
in the loop. Go to step 3.

4 (b). If the last node of the current path is in the spanning tree, add the
current path to the spanning tree. Go to step 2.

4 (c). Otherwise go to step (3).

This algorithm isn’t _too_ hard to implement (although drawing this sort of
maze is a bit annoying). A compatible definition of maze with this one that
fits into a “grid of black and white cells” pattern is the following:

A maze is a rectangular array of cells which are either black or white such
that:

1\. All cells with odd x and y coordinates are white

2\. All cells with even x and y coordinates are black

3\. For any two given white cells, there is exactly one path (ie with no
loops) of adjacent white cells between them.

If one redraws the spanning tree and current path occasionally as this
algorithm runs, the animation of maze generating is quite interesting to
watch.

I think implementing these sorts of maze drawing generating and solving
algorithms is good fun and quite a varied exercise. It’s definitely one I like
doing.

Here is a visualisation of Wilson’s algorithm:
[https://bl.ocks.org/mbostock/11363008](https://bl.ocks.org/mbostock/11363008)
Observe how different the shape of the tree is between runs.

If you want to skip implementing it yourself first and just see the result
then here it is for a maze:
[https://bl.ocks.org/mbostock/11357811](https://bl.ocks.org/mbostock/11357811)

