Skip to content

Commit 968bbe2

Browse files
committed
Lot's of week gone by changes
Introduce new maze generation algorithms: * Introduce Kruskals Algorithm * Introduce Recursive Division Algorithm (with biased versions)
1 parent cba375b commit 968bbe2

File tree

12 files changed

+289
-26
lines changed

12 files changed

+289
-26
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Cell, initialiseAllWalls } from '../utils/PathfindingUtils';
2+
3+
type Maze = Cell[][];
4+
5+
class DSU {
6+
parent: number[];
7+
rank: number[];
8+
9+
constructor(size: number) {
10+
this.parent = new Array(size).fill(0).map((_, index) => index);
11+
this.rank = new Array(size).fill(0);
12+
}
13+
14+
find(x: number): number {
15+
if (this.parent[x] !== x) {
16+
this.parent[x] = this.find(this.parent[x]);
17+
}
18+
return this.parent[x];
19+
}
20+
21+
union(x: number, y: number): void {
22+
const rootX = this.find(x);
23+
const rootY = this.find(y);
24+
25+
if (rootX !== rootY) {
26+
if (this.rank[rootX] > this.rank[rootY]) {
27+
this.parent[rootY] = rootX;
28+
} else if (this.rank[rootX] < this.rank[rootY]) {
29+
this.parent[rootX] = rootY;
30+
} else {
31+
this.parent[rootY] = rootX;
32+
this.rank[rootX]++;
33+
}
34+
}
35+
}
36+
}
37+
38+
function isInsideMaze(maze: Maze, row: number, col: number): boolean {
39+
return row >= 0 && row < maze.length && col >= 0 && col < maze[0].length;
40+
}
41+
42+
function getEdges(maze: Maze): [number, number, number, number][] {
43+
const edges: [number, number, number, number][] = [];
44+
for (let row = 0; row < maze.length; row++) {
45+
for (let col = 0; col < maze[row].length; col++) {
46+
if (row % 2 === 1 && col % 2 === 1) {
47+
if (isInsideMaze(maze, row + 2, col)) {
48+
edges.push([row, col, row + 2, col]);
49+
}
50+
if (isInsideMaze(maze, row, col + 2)) {
51+
edges.push([row, col, row, col + 2]);
52+
}
53+
}
54+
}
55+
}
56+
return edges;
57+
}
58+
59+
function connectCells(maze: Maze, cell1: { row: number, col: number }, cell2: { row: number, col: number }): void {
60+
const betweenRow = Math.floor((cell1.row + cell2.row) / 2);
61+
const betweenCol = Math.floor((cell1.col + cell2.col) / 2);
62+
maze[betweenRow][betweenCol].wall = false;
63+
maze[cell1.row][cell1.col].wall = false;
64+
maze[cell2.row][cell2.col].wall = false;
65+
}
66+
67+
export function kruskalsMazeGeneration(maze: Maze, startCell: Cell, endCell: Cell, setMaze: (arr: Maze) => void): void {
68+
if (maze.length < 3 || maze[0].length < 3) {
69+
return;
70+
}
71+
initialiseAllWalls(maze);
72+
73+
const edges = getEdges(maze);
74+
const dsu = new DSU(maze.length * maze[0].length);
75+
76+
while (edges.length > 0) {
77+
const randomIndex = Math.floor(Math.random() * edges.length);
78+
const [row1, col1, row2, col2] = edges[randomIndex];
79+
edges.splice(randomIndex, 1);
80+
81+
const cellIndex1 = row1 * maze[0].length + col1;
82+
const cellIndex2 = row2 * maze[0].length + col2;
83+
84+
if (dsu.find(cellIndex1) !== dsu.find(cellIndex2)) {
85+
dsu.union(cellIndex1, cellIndex2);
86+
connectCells(maze, { row: row1, col: col1 }, { row: row2, col: col2 });
87+
}
88+
}
89+
90+
maze[startCell.row][startCell.col].wall = false;
91+
maze[endCell.row][endCell.col].wall = false;
92+
setMaze([...maze]);
93+
}

src/algorithms/mazes/PrimsMazeGeneration.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Cell } from '../utils/PathfindingUtils';
1+
import { Cell, initialiseAllWalls } from '../utils/PathfindingUtils';
22

33
type Maze = Cell[][];
44

@@ -40,14 +40,6 @@ function getNeighbors(maze: Maze, row: number, col: number): [number, number][]
4040
return neighbors;
4141
}
4242

43-
function initialiseAllWalls(maze: Maze): void {
44-
for (let row = 0; row < maze.length; row++) {
45-
for (let col = 0; col < maze[row].length; col++) {
46-
maze[row][col].wall = true;
47-
}
48-
}
49-
}
50-
5143
function connectCells(maze: Maze, cell1: { row: number, col: number }, cell2: { row: number, col: number }): void {
5244
const betweenRow = Math.floor((cell1.row + cell2.row) / 2);
5345
const betweenCol = Math.floor((cell1.col + cell2.col) / 2);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Cell, initialiseAllWalls } from '../utils/PathfindingUtils';
2+
3+
type Maze = Cell[][];
4+
5+
// Initialize edges as walls
6+
function initialiseEdges(maze: Maze): void {
7+
for (let row = 0; row < maze.length; row++) {
8+
maze[row][maze[row].length - 1].wall = true;
9+
maze[row][0].wall = true;
10+
}
11+
12+
for (let col = 0; col < maze[0].length; col++) {
13+
maze[0][col].wall = true;
14+
maze[maze.length - 1][col].wall = true;
15+
}
16+
}
17+
18+
// Add a horizontal wall with a single passage
19+
function addHorizontalWall(maze: Maze, y: number, xStart: number, xEnd: number, passageX: number): void {
20+
for (let x = xStart; x <= xEnd; x++) {
21+
if (x !== passageX) {
22+
maze[y][x].wall = true;
23+
} else {
24+
maze[y][x].wall = false;
25+
}
26+
}
27+
}
28+
29+
// Add a vertical wall with a single passage
30+
function addVerticalWall(maze: Maze, x: number, yStart: number, yEnd: number, passageY: number): void {
31+
for (let y = yStart; y <= yEnd; y++) {
32+
if (y !== passageY) {
33+
maze[y][x].wall = true;
34+
} else {
35+
maze[y][x].wall = false;
36+
}
37+
}
38+
}
39+
40+
// Recursive function to divide the maze
41+
function recursiveDivision(maze: Maze, xStart: number, yStart: number, width: number, height: number, chooseOrientation: (width: number, height: number) => boolean): void {
42+
if (width <= 2 || height <= 2) {
43+
return;
44+
}
45+
46+
const horizontal = chooseOrientation(width, height);
47+
if (horizontal) {
48+
const y = Math.floor(randomNumber(yStart, yStart + height - 1) / 2) * 2;
49+
const passageX = Math.floor(randomNumber(xStart, xStart + width - 1) / 2) * 2 + 1;
50+
addHorizontalWall(maze, y, xStart, xStart + width - 1, passageX);
51+
52+
recursiveDivision(maze, xStart, yStart, width, y - yStart, chooseOrientation);
53+
recursiveDivision(maze, xStart, y + 1, width, yStart + height - y - 1, chooseOrientation);
54+
} else {
55+
const x = Math.floor(randomNumber(xStart, xStart + width - 1) / 2) * 2;
56+
const passageY = Math.floor(randomNumber(yStart, yStart + height - 1) / 2) * 2 + 1;
57+
addVerticalWall(maze, x, yStart, yStart + height - 1, passageY);
58+
59+
recursiveDivision(maze, xStart, yStart, x - xStart, height, chooseOrientation);
60+
recursiveDivision(maze, x + 1, yStart, xStart + width - x - 1, height, chooseOrientation);
61+
}
62+
}
63+
64+
// Function to create a biased chooseOrientation function
65+
function createUseBiasOrientationFunction(bias: number) {
66+
return function (): boolean {
67+
return Math.random() > bias;
68+
};
69+
}
70+
71+
function nonBiasedOrientationFunction(width: number, height: number): boolean {
72+
if (width < height) return true;
73+
if (height < width) return false;
74+
return Math.random() < 0.5;
75+
}
76+
77+
// Generate a random number within a range
78+
function randomNumber(min: number, max: number): number {
79+
return Math.floor(Math.random() * (max - min + 1) + min);
80+
}
81+
82+
// Main function to generate the maze using recursive division
83+
export function recursiveDivisionMazeGeneration(maze: Maze, startCell: Cell, endCell: Cell, setMaze: (arr: Maze) => void, bias: number = 0.5): void {
84+
if (maze.length < 3 || maze[0].length < 3) {
85+
return;
86+
}
87+
initialiseAllWalls(maze, false);
88+
initialiseEdges(maze);
89+
const chooseOrientation = bias === 0.5 ? nonBiasedOrientationFunction : createUseBiasOrientationFunction(bias);
90+
recursiveDivision(maze, 1, 1, maze[0].length - 2, maze.length - 2, chooseOrientation);
91+
maze[startCell.row][startCell.col].wall = false;
92+
maze[endCell.row][endCell.col].wall = false;
93+
setMaze([...maze]);
94+
}

src/algorithms/utils/PathfindingUtils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,12 @@ export function getOptimalPath(maze: Cell[][], startCell: Cell, endCell: Cell):
5555
optimalPath.unshift(startCell);
5656

5757
return optimalPath;
58-
}
58+
}
59+
60+
export function initialiseAllWalls(maze: Cell[][], setWall: boolean = true): void {
61+
for (let row = 0; row < maze.length; row++) {
62+
for (let col = 0; col < maze[row].length; col++) {
63+
maze[row][col].wall = setWall;
64+
}
65+
}
66+
}

src/components/ControlPanel.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ControlPanel.tsx
2-
import React from 'react';
2+
import React, { useState } from 'react';
33
import { Algorithm } from '../utils/AlgorithmEnum';
44

55
interface ButtonLayoutProps {
@@ -9,15 +9,36 @@ interface ButtonLayoutProps {
99
}
1010

1111
const ControlPanel: React.FC<ButtonLayoutProps> = ({ triggerAlgorithm, generateTable, clearLatestRun}) => {
12+
const [pathfindingAlgorithm, setPathfindingAlgorithm] = useState<Algorithm>(Algorithm.BFS);
13+
const [mazeAlgorithm, setMazeAlgorithm] = useState<Algorithm>(Algorithm.PRIMS_MAZE);
14+
15+
function runPathfindingAlgorithm(){
16+
triggerAlgorithm(pathfindingAlgorithm);
17+
}
18+
19+
function generateMaze(){
20+
triggerAlgorithm(mazeAlgorithm);
21+
}
22+
1223
return (
13-
<div>
14-
<button onClick={() => triggerAlgorithm(Algorithm.BFS)} style={{backgroundColor: 'white', color: 'black'}}>Run BFS Visualisation</button>
15-
<button onClick={() => triggerAlgorithm(Algorithm.BFS)} style={{backgroundColor: 'white', color: 'black'}}>Run DFS Visualisation</button>
16-
<button onClick={() => triggerAlgorithm(Algorithm.DIJKSTRA)} style={{backgroundColor: 'white', color: 'black'}}>Run Djikstra Visualisation</button>
17-
<button onClick={() => triggerAlgorithm(Algorithm.PRIMS_MAZE)} style={{backgroundColor: 'white', color: 'black'}}>Generate Prims Maze</button>
18-
<button onClick={() => triggerAlgorithm(Algorithm.DFS_MAZE)} style={{backgroundColor: 'white', color: 'black'}}>Generate DFS Maze</button>
19-
<button onClick={() => generateTable()} style={{backgroundColor: 'white', color: 'black'}}>Reset Board</button>
20-
<button onClick={() => clearLatestRun()} style={{backgroundColor: 'white', color: 'black'}}>Clear Latest Run</button>
24+
<div className="floating-control-panel">
25+
<select id="pathfinding-selector" className="control-panel-select" onChange={(event) => {setPathfindingAlgorithm(event.target.value as Algorithm)}}>
26+
<option value={Algorithm.BFS}>Breadth First Search</option>
27+
<option value={Algorithm.DFS}>Depth First Search</option>
28+
<option value={Algorithm.DIJKSTRA}>Djikstra</option>
29+
</select>
30+
<button className="control-panel-btn" onClick={() => runPathfindingAlgorithm()}>Visualise Algorithm</button>
31+
<select id="maze-selector" className="control-panel-select" onChange={(event) => {setMazeAlgorithm(event.target.value as Algorithm)}}>
32+
<option value={Algorithm.PRIMS_MAZE}>Prims Maze</option>
33+
<option value={Algorithm.DFS_MAZE}>DFS Maze</option>
34+
<option value={Algorithm.KRUSKAL_MAZE}>Kruskals Maze</option>
35+
<option value={Algorithm.RECURSIVE_DIVISION_MAZE}>Recursive Division Maze</option>
36+
<option value={Algorithm.RECURSIVE_DIVISION_MAZE_HORIZONTAL_BIAS}>Recursive Division Maze (Horizontal)</option>
37+
<option value={Algorithm.RECURSIVE_DIVISION_MAZE_VERTICAL_BIAS}>Recursive Division Maze (Vertical)</option>
38+
</select>
39+
<button className="control-panel-btn" onClick={() => generateMaze()}>Generate Maze</button>
40+
<button className="control-panel-btn" onClick={() => generateTable()}>Reset Board</button>
41+
<button className="control-panel-btn" onClick={() => clearLatestRun()}>Clear Latest Run</button>
2142
</div>
2243
);
2344
};

src/pages/Navbar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React from 'react';
22
import '../styles/navbar.css';
33
import { Outlet, Link } from "react-router-dom";
4-
54
const Navbar: React.FC = () => {
65
return (
76
<>
87
<nav className='navbar'>
9-
<h2>Algorithm Visualiser</h2>
8+
<div className='navbar-header'>
9+
<h2>Algorithm Visualiser</h2>
10+
</div>
1011
<ul>
1112
<li><Link to="/sorting">Sorting Algorithms</Link></li>
1213
<li><Link to="/search">Search Algorithms</Link></li>

src/pages/PathfindingVisualiser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ const PathfindingVisualiser: React.FC = () => {
9191

9292
return (
9393
<div>
94+
<ControlPanel triggerAlgorithm={triggerAlgorithm} generateTable={generateTable} clearLatestRun={clearLatestRun}/>
9495
<MazeGrid
9596
maze={maze}
9697
handleMouseDown={handleMouseDown}
9798
handleMouseEnter={handleMouseEnter}
9899
handleMouseUp={handleMouseUp}
99100
/>
100-
<ControlPanel triggerAlgorithm={triggerAlgorithm} generateTable={generateTable} clearLatestRun={clearLatestRun}/>
101101
</div>
102102
);
103103
};

src/styles/navbar.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
text-align: left;
99
}
1010

11-
.navbar > h2 {
11+
.navbar > div > h2 {
1212
padding: 10px;
1313
margin: 0;
1414
color: #fff;
15+
}
16+
17+
.navbar-header{
18+
height: 64px;
1519
background-color: #6A0DAD;
1620
}
1721

src/styles/pathfinding.css

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,36 @@
99
.pathfinding-col {
1010
display: flex;
1111
flex-direction: column;
12-
}
12+
}
13+
14+
.control-panel-btn{
15+
color: black;
16+
background: white;
17+
margin: 12px 6px;
18+
font-size: .8em;
19+
height: 32px;
20+
padding: 0 1.2em;
21+
}
22+
23+
.control-panel-select {
24+
border-radius: 8px;
25+
border: 1px solid transparent;
26+
padding: 0 1.2em;
27+
font-size: .8em;
28+
background-color: white;
29+
color: black;
30+
cursor: pointer;
31+
transition: border-color 0.25s;
32+
margin: 16px 6px;
33+
height: 32px;
34+
}
35+
36+
.floating-control-panel{
37+
width: 100%;
38+
height: 64px;
39+
background-color: #6A0DAD;
40+
opacity: 80%;
41+
transition: .5s;
42+
}
43+
44+

src/utils/AlgorithmEnum.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ export enum Algorithm {
33
DIJKSTRA = 'dijkstra',
44
DFS = 'dfs',
55
PRIMS_MAZE = 'primsMaze',
6-
DFS_MAZE = 'dfsMaze'
6+
DFS_MAZE = 'dfsMaze',
7+
KRUSKAL_MAZE = 'kruskalMaze',
8+
RECURSIVE_DIVISION_MAZE = 'recursiveDivision',
9+
RECURSIVE_DIVISION_MAZE_HORIZONTAL_BIAS = 'recursiveDivisionHorizontalBias',
10+
RECURSIVE_DIVISION_MAZE_VERTICAL_BIAS = 'recursiveDivisionVerticalBias',
711
}

0 commit comments

Comments
 (0)