Skip to content

Commit acdadd9

Browse files
committed
pathfinding updates
fixed clear function bugs and added maze generation
1 parent 8b962ee commit acdadd9

File tree

2 files changed

+98
-24
lines changed

2 files changed

+98
-24
lines changed

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ <h3 class="text-xl font-bold mb-2 text-indigo-500">Sorting Race</h3>
507507
<button id="resetBtn" class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600">Stop</button>
508508
<button id="clearBtn" class="px-4 py-2 bg-emerald-500 text-white rounded hover:bg-emerald-600">Clear</button>
509509
<button id="wallBtn" class="px-4 py-2 bg-gray-700 text-white rounded hover:bg-gray-800">Wall Mode: Off</button>
510+
<button id="mazeBtn" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Maze</button>
510511
<button id="backTest" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">Back</button>
511512
</div>
512513

docs/js/features/pathfinding/grid/grid.js

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const dirs = [
1+
export const dirs = [
22
{ dr: -1, dc: 0 }, { dr: 1, dc: 0 },
33
{ dr: 0, dc:-1 }, { dr: 0, dc: 1 },
44
];
@@ -12,19 +12,20 @@ let gridElement = null;
1212
let wallMode = false;
1313
let isMouseDown = false;
1414
let visualizationCancelled = false;
15-
15+
1616
document.addEventListener('mousedown', () => { if (wallMode) isMouseDown = true; });
1717
document.addEventListener('mouseup', () => { isMouseDown = false; });
1818

1919
function gridDelegated(e) {
2020
const cell = e.target.closest('.node');
2121
if (!cell) return;
2222
const [r, c] = cell.dataset.rc.split(',').map(Number);
23-
if (e.type === 'click') handleCellClick(r, c);
23+
if (e.type === 'click')
24+
handleCellClick(r, c);
2425
else if (e.type === 'mouseenter' && wallMode && isMouseDown)
25-
handleCellClick(r, c);
26+
handleCellClick(r, c);
2627
}
27-
28+
2829
/* SVG */
2930
const startSVG = `
3031
<svg viewBox="0 0 24 24">
@@ -73,7 +74,7 @@ export function createGrid({ skipFade = false } = {}) {
7374
gridElement.style.gridTemplateColumns = `repeat(${cols}, ${size}px)`;
7475
gridElement.style.gridAutoRows = `${size}px`;
7576
gridElement.style.gap = `${gap}px`;
76-
gridElement.innerHTML = ''; // clear old
77+
gridElement.innerHTML = '';
7778

7879
let r = 0;
7980
function addRow() {
@@ -85,9 +86,7 @@ export function createGrid({ skipFade = false } = {}) {
8586
frag.appendChild(div);
8687
}
8788
gridElement.appendChild(frag);
88-
if (++r < rows) {
89-
requestAnimationFrame(addRow);
90-
}
89+
if (++r < rows) requestAnimationFrame(addRow);
9190
}
9291
addRow();
9392

@@ -105,19 +104,22 @@ export function createGrid({ skipFade = false } = {}) {
105104
/* CELLS */
106105
export function handleCellClick(r, c) {
107106
const cell = gridElement.querySelector(`[data-rc="${r},${c}"]`);
107+
if (!cell) return;
108108

109109
if (wallMode) {
110110
if (!walls[r][c]) {
111111
walls[r][c] = true;
112112
cell.classList.remove('node-unvisited', 'fade-in');
113113
cell.classList.add('wall-fill');
114-
cell.addEventListener('animationend', () => {
114+
cell.addEventListener('animationend', function handler() {
115115
cell.classList.remove('wall-fill');
116116
cell.classList.add('node-wall');
117-
}, { once: true });
117+
cell.removeEventListener('animationend', handler);
118+
});
118119
} else {
119120
walls[r][c] = false;
120-
cell.className = 'node node-unvisited';
121+
cell.classList.remove('node-wall');
122+
cell.classList.add('node-unvisited');
121123
}
122124
return;
123125
}
@@ -135,9 +137,14 @@ export const toggleWallMode = () => (wallMode = !wallMode);
135137

136138
export function clearPath() {
137139
startNode = endNode = null;
140+
138141
gridElement.querySelectorAll('.node').forEach(cell => {
142+
// Remove any inserted SVG flags
143+
cell.querySelectorAll('svg').forEach(svg => svg.remove());
139144
const [r, c] = cell.dataset.rc.split(',').map(Number);
140-
if (!walls[r][c]) cell.className = 'node node-unvisited';
145+
if (!walls[r][c]) {
146+
cell.className = 'node node-unvisited';
147+
}
141148
});
142149
}
143150

@@ -153,31 +160,49 @@ export async function visualize(visited, path, speed = 1) {
153160
visualizationCancelled = false;
154161
const visitDelay = 45 / speed, pathDelay = 90 / speed;
155162

163+
// Visit animation
156164
for (const v of visited) {
157165
if (visualizationCancelled) return;
158166
const { row, col } = v;
159167
if (walls[row][col]) continue;
160168
const cell = gridElement.querySelector(`[data-rc="${row},${col}"]`);
161-
if (!cell || cell.classList.contains('node-start')
162-
|| cell.classList.contains('node-end')) continue;
163-
cell.className = 'node node-visited fill-swell';
169+
if (!cell || cell.classList.contains('node-start') || cell.classList.contains('node-end')) continue;
170+
171+
cell.classList.remove('node-unvisited');
172+
cell.classList.add('node-visited', 'fill-swell');
173+
cell.addEventListener('animationend', function handler(e) {
174+
if (e.animationName === 'circle-fill') {
175+
cell.classList.remove('fill-swell');
176+
cell.removeEventListener('animationend', handler);
177+
}
178+
});
179+
164180
await new Promise(r => setTimeout(r, visitDelay));
165181
}
166-
167-
// Insert flags
168-
const sc = gridElement.querySelector(`[data-rc="${startNode.row},${startNode.col}"]`);
169-
const ec = gridElement.querySelector(`[data-rc="${endNode.row},${endNode.col}"]`);
182+
183+
// Insert start/end flags
184+
const sc = gridElement.querySelector(`[data-rc="${startNode?.row},${startNode?.col}"]`);
185+
const ec = gridElement.querySelector(`[data-rc="${endNode?.row},${endNode?.col}"]`);
170186
if (sc && !sc.querySelector('svg')) sc.insertAdjacentHTML('beforeend', startSVG);
171187
if (ec && !ec.querySelector('svg')) ec.insertAdjacentHTML('beforeend', endSVG);
172188

189+
// Path animation
173190
for (const p of path) {
174191
if (visualizationCancelled) return;
175192
const { row, col } = p;
176193
if (walls[row][col]) continue;
177194
const cell = gridElement.querySelector(`[data-rc="${row},${col}"]`);
178-
if (!cell || cell.classList.contains('node-start')
179-
|| cell.classList.contains('node-end')) continue;
180-
cell.className = 'node node-shortest-path fill-swell-orange pulse';
195+
if (!cell || cell.classList.contains('node-start') || cell.classList.contains('node-end')) continue;
196+
197+
cell.classList.remove('node-visited');
198+
cell.classList.add('node-shortest-path', 'fill-swell-orange', 'pulse');
199+
cell.addEventListener('animationend', function handler(e) {
200+
if (e.animationName === 'circle-fill-orange') {
201+
cell.classList.remove('fill-swell-orange', 'pulse');
202+
cell.removeEventListener('animationend', handler);
203+
}
204+
});
205+
181206
await new Promise(r => setTimeout(r, pathDelay));
182207
}
183208

@@ -186,7 +211,55 @@ export async function visualize(visited, path, speed = 1) {
186211
setTimeout(() => gridElement.classList.remove('shake', 'glow'), 600);
187212
}
188213

189-
/* LOAD */
214+
/* MAZE */
215+
document.addEventListener('DOMContentLoaded', () => {
216+
const mazeBtn = document.getElementById('mazeBtn');
217+
mazeBtn.addEventListener('click', () => {
218+
visualizationCancelled = true;
219+
startNode = endNode = null;
220+
gridElement.querySelectorAll('svg').forEach(svg => svg.remove());
221+
222+
walls.forEach(row => row.fill(true));
223+
224+
const visited = Array.from({ length: rows }, () => Array(cols).fill(false));
225+
const stack = [];
226+
const start = { r: 0, c: 0 };
227+
visited[start.r][start.c] = true;
228+
walls[start.r][start.c] = false;
229+
stack.push(start);
230+
231+
while (stack.length) {
232+
const cur = stack[stack.length - 1];
233+
const neighs = [];
234+
for (const { dr, dc } of dirs) {
235+
const nr = cur.r + dr * 2, nc = cur.c + dc * 2;
236+
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr][nc]) {
237+
neighs.push({ nr, nc, br: cur.r + dr, bc: cur.c + dc });
238+
}
239+
}
240+
if (neighs.length) {
241+
const rnd = neighs[Math.floor(Math.random() * neighs.length)];
242+
visited[rnd.nr][rnd.nc] = true;
243+
walls[rnd.br][rnd.bc] = false;
244+
walls[rnd.nr][rnd.nc] = false;
245+
stack.push({ r: rnd.nr, c: rnd.nc });
246+
} else {
247+
stack.pop();
248+
}
249+
}
250+
251+
for (let r = 0; r < rows; r++) {
252+
for (let c = 0; c < cols; c++) {
253+
const cell = gridElement.querySelector(`[data-rc="${r},${c}"]`);
254+
if (!cell) continue;
255+
if (walls[r][c]) cell.className = 'node node-wall';
256+
else cell.className = 'node node-unvisited';
257+
}
258+
}
259+
});
260+
});
261+
262+
/* LOAD & RESIZE */
190263
window.addEventListener('resize', () => createGrid({ skipFade: true }));
191264

192265
document.addEventListener('DOMContentLoaded', () => {

0 commit comments

Comments
 (0)