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;
1212let wallMode = false ;
1313let isMouseDown = false ;
1414let visualizationCancelled = false ;
15-
15+
1616document . addEventListener ( 'mousedown' , ( ) => { if ( wallMode ) isMouseDown = true ; } ) ;
1717document . addEventListener ( 'mouseup' , ( ) => { isMouseDown = false ; } ) ;
1818
1919function 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 */
2930const 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 */
106105export 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
136138export 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 */
190263window . addEventListener ( 'resize' , ( ) => createGrid ( { skipFade : true } ) ) ;
191264
192265document . addEventListener ( 'DOMContentLoaded' , ( ) => {
0 commit comments