Skip to content
Personal website
JavaScript TypeScript HTML CSS Dockerfile
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode
assets
build
docs
src
.dockerignore
.gitignore
.posthtmlrc
Dockerfile
README.md
build.js
package-lock.json
package.json
server.js

README.md

 Personal Website

This is my simple minimal digital business card. It includes a generated maze in the background and follows the mouse or touch movements of the user with a path through the maze. For more details feel free to read the accompanying blog post.

Maze generation

The maze is generated using Eller's algorithm. This is a very memory efficient algorithm as it operates row-by-row and does not need to keep track of more than one row at a time. It is also fast while still being somewhat flexible in how the resulting maze should look like, it is for example very simple to have mazes being more horizontal or vertical.

Maze generation is happening in a web worker to not affect the UI responsiveness, both the web worker and the main thread must have a representation of the maze since the main thread is doing the rendering, while the worker is generating and tracing paths between cells in the maze. The generated maze is passed back to the main thread using standard message passing which copies the data transferred, and thus the maze representation must be compact in order to avoid too much overhead when sending it to the main thread.

The maze is represented by an array of 8 bit unsigned integers using an Uint8Array. Each cell is represented by two bits that indicates whether there is a path to the east or south of the cell respectively. North and west can be inferred by the cell above and to the left. This means that a maze with 20000 cells, which in this case corresponds to about a full HD display, requires roughly 5 KB of memory which generally should be sub-millisecond to transfer from the worker.

Path finding

The path finding is triggered by mouse movements so finding a path between two cells must be very efficient. There are several options to path finding such as like Dijkstra or A*. However, the maze algorithm generates perfect mazes, which means there are no loops or inaccessible areas. This means that there is exactly one path from each cell to any other cell in the maze. This is utilized by selecting a fixed point as one end of the path and from there compute the path to every other cell in the maze. Given that this is stored efficiently, this leads to fast path lookups as the user is moving the mouse around. If the selected fixed point is changed the paths can be recomputed.

Paths are generated by visiting each cell once using BFS and storing the index of its parent cell at its own index in a Uint32Array, effectively creating a linked list that represents all paths in the maze. One array element is required per cell so for 20000 cells roughly 80 KB of memory is required. However, the path array only lives in the worker so it is never copied to the main thread. This allows for more than 4 billion cells which is more than enough for this use case. This operation is efficient for the type of mazes constructed with short corridors as the buffer of cells considered generally does not grow to a very large size.

To find a path the linked list is traversed until reaching the fixed source cell which is defined as having the parent 0xFFFFFFFF, which is very efficient as the number of steps required to form the path is exactly the length of the path itself.

Rendering

The maze and path are rendered to separate overlayed canvases since the path in general needs to be rendered much more frequently than the maze. There is however 4 canvases to allow for transitions when switching to a new maze. Rendering the maze is relatively efficient as each cell must render at most 2 lines for its east and south borders.

You can’t perform that action at this time.