Skip to content

Commit 1bcd731

Browse files
committed
feat: add aldous-broder maze generator
1 parent 8152d13 commit 1bcd731

9 files changed

Lines changed: 103 additions & 4 deletions

File tree

PROJECT_DESCRIPTION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ npm run build
3636
| Need | Algorithm(s) | Module | Example |
3737
| ---- | ------------ | ------ | ------- |
3838
| Grid pathfinding | `astar`, `dijkstra`, `jumpPointSearch`, `computeFlowField`, `buildNavMesh`, `findNavMeshPath`, `manhattanDistance`, `gridFromString` | `pathfinding/astar.ts`, `pathfinding/dijkstra.ts`, `pathfinding/jumpPointSearch.ts`, `pathfinding/flowField.ts`, `pathfinding/navMesh.ts` | `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts` |
39-
| Procedural textures & terrain | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts` |
39+
| Procedural textures & terrain | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze`, `generateAldousBroderMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts` |
4040
| Spatial queries & collision | `Quadtree`, `aabbCollision`, `aabbIntersection`, `satCollision`, `circleRayIntersection`, `sweptAABB` | `spatial/*.ts` | `examples/sat.ts` |
4141
| Web performance & UI throttling | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts` |
4242
| Text & search | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance` | `search/*.ts` | `examples/search.ts` |

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ CDN usage:
2525
| Goal | Algorithms | Import From | Example |
2626
| ---- | ---------- | ----------- | ------- |
2727
| Pathfinding & navigation | `astar`, `dijkstra`, `jumpPointSearch`, `computeFlowField`, `buildNavMesh`, `findNavMeshPath`, `manhattanDistance`, `gridFromString` | `pathfinding/astar.ts`, `pathfinding/dijkstra.ts`, `pathfinding/jumpPointSearch.ts`, `pathfinding/flowField.ts`, `pathfinding/navMesh.ts` | `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts` |
28-
| Procedural generation | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts` |
28+
| Procedural generation | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze`, `generateAldousBroderMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts` |
2929
| Spatial queries & collision | `Quadtree`, `aabbCollision`, `aabbIntersection`, `satCollision`, `circleRayIntersection`, `sweptAABB` | `spatial/*.ts` | `examples/sat.ts` |
3030
| AI behaviours & crowds | `seek`, `flee`, `arrive`, `pursue`, `wander`, `updateBoids`, `BehaviorTree`, `rvoStep` | `ai/steering.ts`, `ai/boids.ts`, `ai/behaviorTree.ts`, `ai/rvo.ts` | `examples/steering.ts`, `examples/boids.ts`, `examples/rvo.ts` |
3131
| Web performance & UI | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts` |
@@ -52,7 +52,7 @@ npm run size # Enforce bundle size budget
5252
- Milestone 0.2 next targets crowd-flow integrations (RVO + flow fields) and behaviour-tree decorators for richer AI control.
5353
- Milestone 0.4 plans a procedural + gameplay systems toolkit (Wave Function Collapse, dungeon suite, L-systems, game loop, camera, particles, inventory, combat, save/load, and more).
5454

55-
Examples live under `examples/` and can be executed with `tsx`/`ts-node` or compiled for the browser. See `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/steering.ts`, `examples/boids.ts`, `examples/requestDedup.ts`, `examples/search.ts`, `examples/graph.ts`, `examples/geometry.ts`, `examples/visual.ts`, `examples/sat.ts`, `examples/simplex.ts`, and `examples/worley.ts` for quick starts. The `examples` registry exported from `src/index.ts` provides a typed index you can traverse programmatically.
55+
Examples live under `examples/` and can be executed with `tsx`/`ts-node` or compiled for the browser. See `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts`, `examples/steering.ts`, `examples/boids.ts`, `examples/requestDedup.ts`, `examples/search.ts`, `examples/graph.ts`, `examples/geometry.ts`, `examples/visual.ts`, `examples/sat.ts`, `examples/simplex.ts`, and `examples/worley.ts` for quick starts. The `examples` registry exported from `src/index.ts` provides a typed index you can traverse programmatically.
5656

5757
## Contributing
5858
1. Fork the repository.

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
- [x] Diamond-square terrain height map generator
4343
- [x] L-system generator for foliage and organic structures
4444
- [x] Dungeon generation suite (BSP subdivision, rooms & corridors variants)
45-
- [ ] Maze algorithms pack (Recursive backtracking ✅, Prim's ✅, Kruskal's ✅, Wilson's ✅, Aldous–Broder, Recursive Division)
45+
- [ ] Maze algorithms pack (Recursive backtracking ✅, Prim's ✅, Kruskal's ✅, Wilson's ✅, Aldous–Broder, Recursive Division)
4646
- Gameplay systems & utilities:
4747
- [ ] Fixed-timestep game loop utility with interpolation helpers
4848
- [ ] Delta-time manager for frame-independent timing

docs/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,14 @@ export function generateKruskalMaze(options: MazeOptions): MazeResult;
637637
*/
638638
export function generateWilsonMaze(options: MazeOptions): MazeResult;
639639

640+
/**
641+
* Generates a maze using the Aldous–Broder algorithm.
642+
* Use for: unbiased random mazes through random walks.
643+
* Performance: O(width × height × visits).
644+
* Import: procedural/maze.ts
645+
*/
646+
export function generateAldousBroderMaze(options: MazeOptions): MazeResult;
647+
640648
/**
641649
* Simplex noise generator for smooth gradients without directional artifacts.
642650
* Use for: large terrain synthesis, animated textures, volumetric noise.

examples/mazeAldous.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { generateAldousBroderMaze } from '../src/index.js';
2+
3+
const { grid, start, end } = generateAldousBroderMaze({
4+
width: 21,
5+
height: 21,
6+
seed: 2048,
7+
});
8+
9+
console.log('Start:', start);
10+
console.log('End:', end);
11+
console.log('Middle row:', grid[10]?.join(''));

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const examples = {
5252
generatePrimMaze: 'examples/mazePrim.ts',
5353
generateKruskalMaze: 'examples/mazeKruskal.ts',
5454
generateWilsonMaze: 'examples/mazeWilson.ts',
55+
generateAldousBroderMaze: 'examples/mazeAldous.ts',
5556
},
5657
spatial: {
5758
Quadtree: 'examples/sat.ts',
@@ -306,6 +307,13 @@ export { generateKruskalMaze } from './procedural/maze.js';
306307
*/
307308
export { generateWilsonMaze } from './procedural/maze.js';
308309

310+
/**
311+
* Aldous–Broder maze generator using random walks.
312+
*
313+
* Example file: examples/mazeAldous.ts
314+
*/
315+
export { generateAldousBroderMaze } from './procedural/maze.js';
316+
309317
// ============================================================================
310318
// 🎯 SPATIAL & COLLISION
311319
// ============================================================================

src/procedural/maze.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,51 @@ export function generateWilsonMaze({
251251
return { grid, start, end };
252252
}
253253

254+
/**
255+
* Generates a maze using the Aldous–Broder random walk algorithm.
256+
* Useful for: unbiased mazes with uniform spanning tree distribution.
257+
*/
258+
export function generateAldousBroderMaze({
259+
width,
260+
height,
261+
seed = Date.now(),
262+
}: MazeOptions): MazeResult {
263+
validateDimensions(width, height);
264+
265+
const grid = Array.from({ length: height }, () => Array<number>(width).fill(WALL));
266+
const random = createLinearCongruentialGenerator(seed);
267+
268+
const cells: Cell[] = [];
269+
const visited = new Set<string>();
270+
for (let y = 1; y < height; y += 2) {
271+
for (let x = 1; x < width; x += 2) {
272+
cells.push({ x, y });
273+
}
274+
}
275+
276+
let current = cells[Math.floor(random() * cells.length)] ?? { x: 1, y: 1 };
277+
carveCell(grid, current);
278+
visited.add(cellKey(current.x, current.y));
279+
280+
while (visited.size < cells.length) {
281+
const next = randomOddNeighbour(current, grid, random);
282+
const key = cellKey(next.x, next.y);
283+
if (!visited.has(key)) {
284+
carveCorridorBetween(grid, current, next);
285+
carveCell(grid, next);
286+
visited.add(key);
287+
}
288+
current = next;
289+
}
290+
291+
const start: Cell = { x: 1, y: 1 };
292+
carveCell(grid, start);
293+
const end = findFarthestCell(start, grid);
294+
carveCell(grid, end);
295+
296+
return { grid, start, end };
297+
}
298+
254299
function validateDimensions(width: number, height: number): void {
255300
if (!Number.isInteger(width) || !Number.isInteger(height)) {
256301
throw new Error('width and height must be integers.');

tests/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ describe('package entry point', () => {
1818
expect(examples.procedural.generatePrimMaze).toBe('examples/mazePrim.ts');
1919
expect(examples.procedural.generateKruskalMaze).toBe('examples/mazeKruskal.ts');
2020
expect(examples.procedural.generateWilsonMaze).toBe('examples/mazeWilson.ts');
21+
expect(examples.procedural.generateAldousBroderMaze).toBe('examples/mazeAldous.ts');
2122
expect(examples.search.Trie).toBe('examples/search.ts');
2223
expect(examples.pathfinding.buildNavMesh).toBe('examples/navMesh.ts');
2324
});
@@ -66,6 +67,7 @@ describe('package entry point', () => {
6667
| 'generatePrimMaze'
6768
| 'generateKruskalMaze'
6869
| 'generateWilsonMaze'
70+
| 'generateAldousBroderMaze'
6971
>();
7072
});
7173
});

tests/mazeAldous.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { generateAldousBroderMaze } from '../src/index.js';
4+
5+
describe('generateAldousBroderMaze', () => {
6+
it('returns deterministic mazes for identical seeds', () => {
7+
const options = { width: 21, height: 21, seed: 888 } as const;
8+
const a = generateAldousBroderMaze(options);
9+
const b = generateAldousBroderMaze(options);
10+
11+
expect(a.grid).toEqual(b.grid);
12+
expect(a.start).toEqual(b.start);
13+
expect(a.end).toEqual(b.end);
14+
});
15+
16+
it('produces walkable paths in a walled grid', () => {
17+
const { grid } = generateAldousBroderMaze({ width: 17, height: 17, seed: 42 });
18+
expect(grid.length).toBe(17);
19+
expect(grid.every((row) => row.length === 17)).toBe(true);
20+
expect(grid[0].every((cell) => cell === 1)).toBe(true);
21+
expect(grid[grid.length - 1].every((cell) => cell === 1)).toBe(true);
22+
expect(grid.every((row) => row[0] === 1 && row[row.length - 1] === 1)).toBe(true);
23+
expect(grid.flat().some((cell) => cell === 0)).toBe(true);
24+
});
25+
});

0 commit comments

Comments
 (0)