UNPKG

@amarillion/helixgraph

Version:

A collection of graph algorithms for game development

95 lines (94 loc) 3.28 kB
import { PriorityQueue } from "../PriorityQueue.js"; /** * Prioritize nodes that were opened last, but shuffle edges within that node. * Produces windy mazes with high river, like the recursive backtracker. */ export const PRIM_LAST_ADDED_RANDOM_EDGES = ((prng = Math.random) => { let counter; return { start: () => counter = 0, nextNode: () => --counter, next: () => counter + prng() }; })(); /** * Prioritize edges that were opened last. * Produces a very regular effect. No randomness at all. * Relies more on the weight function to do something interesting. */ export const PRIM_LAST_ADDED = (() => { let counter; return { start: () => counter = 0, nextNode: () => { }, next: () => --counter }; })(); /** * Prioritize edges completely randomly. * Produces low-river mazes with lots of branches and lots of short dead-ends. */ export const PRIM_RANDOM = ((prng = Math.random) => { return { start: () => { }, nextNode: () => { }, next: () => prng() }; })(); export class PrimIter { constructor(startNode, getAdjacent, linkNodes, { getWeight = () => 1, tiebreaker = PRIM_LAST_ADDED_RANDOM_EDGES } = {}) { this.getAdjacent = getAdjacent; this.getWeight = getWeight; this.tiebreaker = tiebreaker; this.collectedNodes = new Set(); this.edgeQueue = new PriorityQueue((a, b) => b.weight - a.weight || b.tiebreaker - a.tiebreaker); tiebreaker.start(); this.collectNode(startNode); this.linkNodes = linkNodes; } collectNode(node) { for (const [edge, dest] of this.getAdjacent(node)) { // choice of tiebreaker determines the texture of the maze. // a random tiebreaker creates a texture more like kruskal or random prim // a decreasing tiebreaker creates a texture more like the recursive backtracker this.edgeQueue.push({ src: node, dir: edge, dest, weight: this.getWeight(edge, node), tiebreaker: this.tiebreaker.next() }); } this.tiebreaker.nextNode(); this.collectedNodes.add(node); } canLinkTo(destNode) { return !this.collectedNodes.has(destNode); } next() { while (true) { if (this.edgeQueue.isEmpty()) { return { value: undefined, done: true }; } const { src, dir, dest } = this.edgeQueue.pop(); if (this.canLinkTo(dest)) { this.collectNode(dest); this.linkNodes(src, dir, dest); return { value: undefined, done: false }; } } } [Symbol.iterator]() { return this; } } export function prim(startNode, getAdjacent, linkNodes, { maxIterations = 0, getWeight = () => 1, tiebreaker = PRIM_LAST_ADDED_RANDOM_EDGES } = {}) { const iter = new PrimIter(startNode, getAdjacent, linkNodes, { getWeight, tiebreaker }); let maxIt = maxIterations; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of iter) { if (--maxIt === 0) { throw new Error("Infinite loop detected"); } } }