npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

algernon-js

v0.3.1

Published

Algernon is a JS library for efficiently generating, solving, and rendering mazes.

Downloads

77

Readme

GitHub package.json version (branch) npm bundle size contributions welcome tests ViewCount License: MIT NPM Downloads

Algernon-js

Algernon is a JS library for efficiently generating, solving, and rendering 2D mazes.

Where does the name come from?

Found an issue or a performance improvement? Feel free to leave an issue or make a pull request!

Algernon Generated Maze

Maze generated, solved, and rendered by Algernon-js

Installation

Via NPM:

npm i algernon-js

Via CDN:

<script
	type="module"
	src="https://cdn.jsdelivr.net/npm/algernon-js/dist/algernon.js"
></script>

Usage

Maze Generation

Example:

import { generateBacktrackingRaw } from "algernon-js"

const [rows, cols] = [20, 20]

const rawMaze = generateBacktrackingRaw(rows, cols)

Raw Format:

In algernon-js, mazes are stored as a 2D array of integers for a mix of space efficiency and convenience.

Each cell may have North, South, East, or West walls (the outer edges of the maze will be bounded).

Internally, bits are used to represent the presence of walls or other significant information.

That means a cell is internally used like this (0b1001 - a cell with a North and West wall), but looks like this (9 - the decimal representation).

If you are familiar with bit manipulation, feel free to use mazes in their "raw" format. If not, check out the conversions available for more familiar formats.

| Name | Description | Method | Occupancy Grid Support | | ------------ | ------------------------------------------------------------------------------------------ | ------------------------- | ---------------------- | | Backtracking | A fast algorithm for mazes with some long corridors. Reasonable general purpose algorithm. | generateBacktrackingRaw | ✅ | | Kruskal's | On the slower side, good for simple and relatively easy mazes. | generateKruskalRaw | ✅ | | Growing Tree | Fast, performs like Prim's by default but configurable. | generateGrowingTreeRaw | ✅ |

Some of these algorithms are additionally configurable. Check out the JSDoc comments for more info.

Occupancy Grid Format:

Algernon-js also supports the occupancy grid format. In this format, each cell is either open, or a wall/obstacle.

Open is represented as false and a wall/obstacle is true.

There are similar methods available for generating these mazes, like generateBacktrackingGrid.

Note that with this maze format, only odd-dimensioned mazes can be generated, and otherwise the bottom row and right column will both be completely walls. So 19 by 19 would work, but 20 by 20 is not recommended.

Maze Solving

Example:

// maze already generated, stored as `rawMaze`

import { solveAStarRaw } from "algernon-js"

// Solve the maze using A*, starting at the top left
// and ending at the bottom right
const solution = solveAStarRaw(rawMaze, [0, 0], [rows - 1, cols - 1])

Format:

Solutions are simply arrays, with each element as an index in the original rawMaze.

For example: [[0,0],[1,0],[1,1],[1,2],...]

| Name | Description | Method | Occupancy Grid Support | | -------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------- | ---------------------- | | A* | Fast Dijkstra's-based solver that uses heuristics. Configurable general purpose solver. | solveAStarRaw | ✅ | | ACO | Ant Colony Optimization is not recommended for real-world purposes. It is interesting for experimentation. | solveACO | ⏳ | | DFS | Depth First Search is simple and fast, exploring deep paths and backtracking. | solveDFSRaw | ✅ | | BFS | Breadth First Search is fast, simply exploring the whole maze. | solveBFSRaw | ✅ | | D* Lite | Essentially A* backwards. Pretty fast, and ideal for mazes that change and require re-planning. See main.js for examples. | solveDStarLite | ⏳ |

Many of these algorithms use heuristics or additional configuration. Check of the JSDoc comments for more info.

Rendering

This is intended as a simple solution for basic web apps or testing. Feel free to use the code as a starting point for a custom solution.

For the occupancy grid format, a similar method is available: renderGridMazeToCanvas.

Example:

<!-- index.html -->

<canvas id="demo-canvas" width="400" height="400"></canvas>
// script.js

import {
	generateBacktrackingRaw,
	solveAStarRaw,
	renderRawMazeToCanvas,
} from "algernon-js"

const [rows, cols] = [20, 20]

// Generate and solve the maze
const rawMaze = generateBacktrackingRaw(rows, cols)
const solution = solveAStarRaw(rawMaze, [0, 0], [rows - 1, cols - 1])

// Render the maze
const canvas = document.getElementById("demo-canvas")
const ctx = canvas.getContext("2d")

// Render each cell at 20 pixels x 20 pixels
// `solution` is an optional argument
renderRawMazeToCanvas(ctx, 20, rawMaze, solution)

Conversion

For the occupancy grid format, ✅ represents support for this format by typically adding Grid to the end of the method (though this may vary if the name implies it).

Raw <-> Node Matrix

// `rawMaze` already generated

const nodeMatrix = convertRawToNodeMatrix(rawMaze)

// A node matrix is a 2D array of `MatrixNode`,
// where each node contains the following booleans:
// hasNorthWall, hasSouthWall, hasEastWall, hasWestWall

// Convert node matrix back to raw
const updatedRaw = convertNodeMatrixToRaw(nodeMatrix)

Raw <-> Node Graph

// `rawMaze` already generated

// Generate a node graph with the given start and end points
const nodeGraph = convertRawToNodeGraph(rawMaze, [0, 0], [row - 1, col - 1])

// A node graph is a graph of `GraphNode`,
// where each node contains the following booleans:
// isStart, isEnd, hasNorthWall, hasSouthWall, hasEastWall, hasWestWall
// and the following references (or null if not present):
// northNeighbor, southNeighbor, eastNeighbor, westNeighbor

const updatedRaw = convertNodeGraphToRaw(nodeGraph, [0, 0])

Raw <-> ArrayBuffer (Binary)

// `rawMaze` already generated

// Convert the maze to a Uint8Array,
// with the first byte storing the rows and cols
const buffer = serializeRawToBinary(rawMaze)

const deserialized = deserializeBinaryToRaw(buffer)

Raw <-> Base64 String

// `rawMaze` already generated

// Convert the maze to a Base64 string
const base64 = serializeRawToString(rawMaze)

const deserialized = deserializeStringToRaw(base64)

Raw -> Supersampled

// `rawMaze` already generated

// Supersample the maze at a factor of 2
const supersampled = supersampleMaze(rawMaze, 2)

// Output: sample maze structure but twice the rows and columns

Raw <-> Grid Maze Format

// `rawMaze` already generated

// Replace wall properties with open/wall cells (and upscale to a factor of 2)
const gridMaze = convertRawToGridFormat(rawMaze, 2)

// Output: same maze structure but walls are represented by true
// and cells by false, and a different number of cells

// Convert back to Raw (and tell it the upscale factor)
const rawMaze2 = convertGridToRawFormat(gridMaze, 2)

// Output: the original `rawMaze`

// Convert points from raw to grid and back
const rawPoint = [2, 2]
const gridPoint = convertRawToGridPoint(rawPoint)
const originalPoint = convertGridToRawPoint(gridPoint)

Raw -> Raw (Braided)

// `rawMaze` already generated

// Remove dead-ends with the given probability
braidMaze(rawMaze, 0.5)

// Output: an in-place modification of the original maze,
// with walls removed to prevent dead-ends.

Grid -> Degrade/Fill

// `gridMaze` already generated

// Remove walls with the given probability
degradeGrid(gridMaze, 0.1)

// Add walls with the given probability
fillGrid(gridMaze, 0.05)

// Output: an in-place modification of the original maze,
// with walls removed or added. This is ONLY for occupancy
// grid mazes.

Helpers

Internally used helper methods, like removeWall, getAvailableNeighbors, getDirection, and more, are made available through the helpers namespace.

Additionally, many have support for occupancy grids as well by adding Grid to the end of the method name.

Example:

import { helpers } from "algernon-js"

const c1 = [0, 0]
const c2 = [1, 0]

const directionBetweenCells = helpers.getDirection(c1, c2)
// directionBetweenCells: 0b0100 or South

Data Structures

Internally used data structures are now available as well.

The two currently available are: createDisjointSet and createMinHeap.

Benchmarks

Benchmarks were run on a M2 MacBook Air with a minimum of 50 samples each.

Generation

| Maze Size | Name | Mean (ms) | RME | | --------- | ------------ | ---------- | ---- | | 20 x 20 | Backtracking | 0.1037 | 0.89 | | 50 x 50 | Backtracking | 0.5939 | 0.80 | | 80 x 80 | Backtracking | 1.4800 | 0.70 | | 20 x 20 | Kruskal | 2.0065 | 1.49 | | 50 x 50 | Kruskal | 44.493 | 3.59 | | 80 x 80 | Kruskal | 325.52 | 2.84 | | 20 x 20 | Growing Tree | 0.1283 | 0.39 | | 50 x 50 | Growing Tree | 0.8033 | 0.70 | | 80 x 80 | Growing Tree | 2.1503 | 2.45 |

Solving

Backtracking mazes were selected for this benchmark. Performance can vary with different maze types.

| Maze Size | Name | Mean (ms) | RME | | --------- | -------- | ---------- | ---- | | 20 x 20 | A-Star | 0.0173 | 1.13 | | 50 x 50 | A-Star | 0.1551 | 0.81 | | 80 x 80 | A-Star | 0.1676 | 0.78 | | 20 x 20 | ACO | 1.3462 | 0.64 | | 50 x 50 | ACO | 22.810 | 0.98 | | 80 x 80 | ACO | 69.850 | 1.67 | | 20 x 20 | DFS | 0.0617 | 0.41 | | 50 x 50 | DFS | 0.2197 | 0.48 | | 80 x 80 | DFS | 0.3464 | 0.60 | | 20 x 20 | BFS | 0.0532 | 0.38 | | 50 x 50 | BFS | 0.1643 | 0.62 | | 80 x 80 | BFS | 0.1504 | 0.37 | | 20 x 20 | D* Lite | 0.1215 | 1.06 | | 50 x 50 | D* Lite | 0.5553 | 0.64 | | 80 x 80 | D* Lite | 0.8571 | 1.71 |