Skip to content

Commit dd6470f

Browse files
committed
Add the basic introductions of pathfinding
* Add pathfinding page with draw functionality * Add Breadth First Search as first pathfinding algorithm * Breakdown each cell into a cell based system
1 parent 43660b6 commit dd6470f

File tree

11 files changed

+313
-71
lines changed

11 files changed

+313
-71
lines changed

package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
},
1515
"dependencies": {
1616
"react": "^18.2.0",
17-
"react-dom": "^18.2.0"
17+
"react-dom": "^18.2.0",
18+
"react-icons": "^5.2.1"
1819
},
1920
"devDependencies": {
2021
"@types/react": "^18.2.66",

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function App() {
2222
<Route path="*" element={<NotFoundPage />} />
2323
</Route>
2424
</Routes>
25-
</BrowserRouter>
25+
</BrowserRouter>
2626
)
2727
}
2828

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Cell } from "../utils/PathfindingUtils";
2+
3+
// Define a function to perform breadth-first search on the maze
4+
function BreadthFirstSearch(maze: Cell[][], startRow: number, startCol: number, endRow: number, endCol: number): Cell[][] | null {
5+
const queue: Cell[] = [];
6+
const visited: Cell[][] = [];
7+
8+
// Initialize the visited array
9+
for (let row = 0; row < maze.length; row++) {
10+
visited[row] = [];
11+
for (let col = 0; col < maze[row].length; col++) {
12+
visited[row][col] = new Cell(row, col);
13+
visited[row][col].wall = maze[row][col].wall;
14+
}
15+
}
16+
17+
// Add the start cell to the queue
18+
queue.push(maze[startRow][startCol]);
19+
visited[startRow][startCol].visited = true;
20+
21+
// Perform breadth-first search
22+
while (queue.length > 0) {
23+
const currentCell = queue.shift();
24+
25+
if (!currentCell) {
26+
continue;
27+
}
28+
29+
// Check if we have reached the end cell
30+
if (currentCell.row === endRow && currentCell.col === endCol) {
31+
return visited;
32+
}
33+
34+
// Get the neighbors of the current cell
35+
const neighbors = getNeighbors(maze, currentCell);
36+
37+
// Visit each neighbor
38+
for (const neighbor of neighbors) {
39+
if (!visited[neighbor.row][neighbor.col].visited) {
40+
queue.push(neighbor);
41+
visited[neighbor.row][neighbor.col].visited = true;
42+
}
43+
}
44+
}
45+
46+
// If we reach here, there is no path from start to end
47+
return null;
48+
}
49+
50+
// Define a function to get the neighbors of a cell
51+
function getNeighbors(maze: Cell[][], cell: Cell): Cell[] {
52+
const neighbors: Cell[] = [];
53+
const { row, col } = cell;
54+
55+
// Check the top neighbor
56+
if (row > 0 && !maze[row - 1][col].wall) {
57+
neighbors.push(maze[row - 1][col]);
58+
}
59+
60+
// Check the right neighbor
61+
if (col < maze[row].length - 1 && !maze[row][col + 1].wall) {
62+
neighbors.push(maze[row][col + 1]);
63+
}
64+
65+
// Check the bottom neighbor
66+
if (row < maze.length - 1 && !maze[row + 1][col].wall) {
67+
neighbors.push(maze[row + 1][col]);
68+
}
69+
70+
// Check the left neighbor
71+
if (col > 0 && !maze[row][col - 1].wall) {
72+
neighbors.push(maze[row][col - 1]);
73+
}
74+
75+
return neighbors;
76+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { sleep } from '../utils/SleepTime';
2+
3+
// Function to perform binary search
4+
export const binarySearch = async (target: number | null, array: number[],
5+
setNumSteps: (arr: number) => void,
6+
setFoundIndex: (arr: number | null) => void,
7+
setSelectedIndex: (arr: number | null) => void) => {
8+
if (!target) return;
9+
let left = 0;
10+
let right = array.length - 1;
11+
let steps = 0;
12+
while (left <= right) {
13+
const mid = Math.floor((left + right) / 2);
14+
setSelectedIndex(mid);
15+
steps += 1;
16+
await sleep(500);
17+
if (array[mid] === target) {
18+
setFoundIndex(mid);
19+
setNumSteps(steps);
20+
return;
21+
} else if (array[mid] < target) {
22+
left = mid + 1;
23+
} else {
24+
right = mid - 1;
25+
}
26+
}
27+
setNumSteps(steps);
28+
setSelectedIndex(null);
29+
setFoundIndex(null);
30+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {sleep} from '../utils/SleepTime';
2+
3+
// Function to perform linear search
4+
export const linearSearch = async (target: number | null, array: number[],
5+
setNumSteps: (arr: number) => void,
6+
setFoundIndex: (arr: number | null) => void,
7+
setSelectedIndex: (arr: number | null) => void) => {
8+
if (!target) return;
9+
let steps = 0;
10+
for (let i = 0; i < array.length; i++) {
11+
setSelectedIndex(i);
12+
await sleep(500);
13+
steps += 1;
14+
if (array[i] === target) {
15+
setNumSteps(steps);
16+
setFoundIndex(i);
17+
return;
18+
}
19+
}
20+
setNumSteps(steps);
21+
setSelectedIndex(null);
22+
setFoundIndex(null);
23+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Define a class to represent a cell in the maze
2+
export class Cell {
3+
row: number;
4+
col: number;
5+
visited: boolean;
6+
wall: boolean;
7+
start: boolean;
8+
end: boolean;
9+
10+
constructor(row: number, col: number) {
11+
this.row = row;
12+
this.col = col;
13+
this.visited = false;
14+
this.wall = false;
15+
this.start = false;
16+
this.end = false;
17+
}
18+
}
Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,140 @@
11
import React, { useState, useEffect } from 'react';
2+
import useWindowDimensions from '../utils/useWindowDimensions';
3+
import '../styles/pathfinding.css';
4+
import { HiChevronRight } from "react-icons/hi";
5+
import { Cell } from '../algorithms/utils/PathfindingUtils';
26

37
const PathfindingVisualiser: React.FC = () => {
4-
const [maze, setMaze] = useState<number[][]>([]);
8+
const { width = 0, height = 0 } = useWindowDimensions();
9+
const [maze, setMaze] = useState<Cell[][]>([]);
10+
const [isDrawing, setIsDrawing] = useState<boolean>(false);
11+
const [drawingValue, setDrawingValue] = useState<boolean>(false);
12+
const [startNode, setStartNode] = useState<number[]>([0, 0]);
13+
const [endNode, setEndNode] = useState<number[]>([0, 0]);
14+
const [isMovingNode, setIsMovingNode] = useState<'start' | 'end' | null>(null);
515

616
useEffect(() => {
7-
generateMaze();
8-
}, []);
9-
10-
const generateMaze = () => {
11-
const rows = 10;
12-
const cols = 10;
13-
const newMaze: number[][] = [];
14-
for (let i = 0; i < rows; i++) {
15-
const row: number[] = [];
16-
for (let j = 0; j < cols; j++) {
17-
row.push(1);
17+
generateTable();
18+
}, [width, height]);
19+
20+
function setStartPosition(flatHeight: number, flatWidth: number){
21+
const rowIndex = Math.floor(flatHeight / 2);
22+
const colIndex = Math.floor(flatWidth / 4);
23+
setMaze(prevMaze => {
24+
const newMaze = [...prevMaze];
25+
newMaze[rowIndex][colIndex] = new Cell(rowIndex, colIndex);
26+
newMaze[rowIndex][colIndex].start = true;
27+
return newMaze;
28+
});
29+
setStartNode([rowIndex, colIndex]);
30+
}
31+
32+
function setEndPosition(flatHeight: number, flatWidth: number){
33+
const rowIndex = Math.floor(flatHeight / 2);
34+
const colIndex = Math.floor(flatWidth * 3 / 4);
35+
setMaze(prevMaze => {
36+
const newMaze = [...prevMaze];
37+
newMaze[rowIndex][colIndex] = new Cell(rowIndex, colIndex);
38+
newMaze[rowIndex][colIndex].end = true;
39+
return newMaze;
40+
});
41+
setEndNode([rowIndex, colIndex]);
42+
}
43+
44+
function generateTable() {
45+
const flatHeight = Math.floor(height / 27);
46+
const flatWidth = Math.floor((width - 300) / 27);
47+
const newMaze = [];
48+
for (let i = 0; i < flatHeight; i++) {
49+
const row = [];
50+
for (let j = 0; j < flatWidth; j++) {
51+
row.push(new Cell(i, j));
1852
}
1953
newMaze.push(row);
2054
}
21-
2255
setMaze(newMaze);
56+
setStartPosition(flatHeight, flatWidth);
57+
setEndPosition(flatHeight, flatWidth);
58+
}
59+
60+
const handleMouseDown = (rowIndex: number, colIndex: number) => {
61+
if (maze[rowIndex][colIndex].start) {
62+
setIsMovingNode('start');
63+
} else if (maze[rowIndex][colIndex].end) {
64+
setIsMovingNode('end');
65+
} else {
66+
setIsDrawing(true);
67+
const currentCellValue = maze[rowIndex][colIndex];
68+
setDrawingValue(currentCellValue.wall ? false : true);
69+
drawCell(rowIndex, colIndex, currentCellValue.wall ? false : true);
70+
}
71+
};
72+
73+
const handleMouseUp = () => {
74+
setIsDrawing(false);
75+
setIsMovingNode(null);
76+
};
77+
78+
const handleMouseEnter = (rowIndex: number, colIndex: number) => {
79+
if (isDrawing) {
80+
drawCell(rowIndex, colIndex, drawingValue);
81+
} else if (isMovingNode) {
82+
moveNode(rowIndex, colIndex, isMovingNode);
83+
}
84+
};
85+
86+
const drawCell = (rowIndex: number, colIndex: number, value: boolean) => {
87+
if (rowIndex === startNode[0] && colIndex === startNode[1]) return;
88+
if (rowIndex === endNode[0] && colIndex === endNode[1]) return;
89+
setMaze(prevMaze => {
90+
const newMaze = [...prevMaze];
91+
newMaze[rowIndex][colIndex].wall = value;
92+
return newMaze;
93+
});
94+
};
95+
96+
const moveNode = (rowIndex: number, colIndex: number, nodeType: 'start' | 'end') => {
97+
setMaze(prevMaze => {
98+
const newMaze = [...prevMaze];
99+
if (nodeType === 'start') {
100+
newMaze[startNode[0]][startNode[1]].start = false;
101+
newMaze[rowIndex][colIndex].start = true;
102+
setStartNode([rowIndex, colIndex]);
103+
} else if (nodeType === 'end') {
104+
newMaze[endNode[0]][endNode[1]].end = false;
105+
newMaze[rowIndex][colIndex].end = true;
106+
setEndNode([rowIndex, colIndex]);
107+
}
108+
return newMaze;
109+
});
23110
};
24111

25112
return (
26-
<div>
113+
<div onMouseUp={handleMouseUp}>
27114
{maze.map((row, rowIndex) => (
28-
<div key={rowIndex}>
115+
<div key={rowIndex} className="pathfinding-row" id={'row-' + rowIndex}>
29116
{row.map((cell, colIndex) => (
30117
<div
31118
key={colIndex}
119+
onMouseDown={() => handleMouseDown(rowIndex, colIndex)}
120+
onMouseEnter={() => handleMouseEnter(rowIndex, colIndex)}
32121
style={{
33-
width: '20px',
34-
height: '20px',
35-
backgroundColor: cell === 1 ? 'black' : 'white',
36-
border: '1px solid black',
37-
}}
38-
/>
122+
width: '26px',
123+
height: '26px',
124+
backgroundColor: cell.start ? 'lightblue' : ( cell.end ? 'lightgreen' : (cell.wall ? 'black' : 'white')),
125+
border: '.5px solid lightblue',
126+
display: 'flex',
127+
alignItems: 'center',
128+
justifyContent: 'center'
129+
}}>
130+
{cell.start ? <HiChevronRight style={{color: 'black', fontSize: '26px'}} /> : ''}
131+
{cell.end ? <HiChevronRight style={{color: 'black', fontSize: '26px', transform: 'rotate(180deg)'}} /> : ''}
132+
</div>
39133
))}
40134
</div>
41135
))}
42136
</div>
43137
);
44138
};
45139

46-
export default PathfindingVisualiser;
140+
export default PathfindingVisualiser;

0 commit comments

Comments
 (0)