1- const c = document . getElementById ( 'maze' ) ; const ctx = c . getContext ( '2d' ) ;
2- // TODO: implement maze generation and basic player movement
3- ctx . fillStyle = '#17171c' ; ctx . fillRect ( 0 , 0 , c . width , c . height ) ;
4- ctx . fillStyle = '#6ee7b7' ; ctx . fillRect ( 8 , 8 , 24 , 24 ) ;
1+ const canvas = document . getElementById ( 'maze' ) ;
2+ const ctx = canvas . getContext ( '2d' ) ;
3+
4+ // UI Elements
5+ const algorithmSelect = document . getElementById ( 'algorithm' ) ;
6+ const mazeSizeSlider = document . getElementById ( 'mazeSize' ) ;
7+ const mazeSizeValue = document . getElementById ( 'mazeSizeValue' ) ;
8+ const speedSlider = document . getElementById ( 'speed' ) ;
9+ const generateBtn = document . getElementById ( 'generateBtn' ) ;
10+ const solveBtn = document . getElementById ( 'solveBtn' ) ;
11+ const clearBtn = document . getElementById ( 'clearBtn' ) ;
12+
13+ // Metrics
14+ const nodesVisitedEl = document . getElementById ( 'nodes-visited' ) ;
15+ const pathLengthEl = document . getElementById ( 'path-length' ) ;
16+ const timeTakenEl = document . getElementById ( 'time-taken' ) ;
17+
18+ let size = 20 ;
19+ let cellSize = canvas . width / size ;
20+ let grid = [ ] ;
21+ let animationFrameId ;
22+
23+ // --- Maze Generation (Recursive Backtracker) ---
24+ function createGrid ( ) {
25+ grid = [ ] ;
26+ for ( let y = 0 ; y < size ; y ++ ) {
27+ let row = [ ] ;
28+ for ( let x = 0 ; x < size ; x ++ ) {
29+ row . push ( { x, y, walls : { top : true , right : true , bottom : true , left : true } , visited : false } ) ;
30+ }
31+ grid . push ( row ) ;
32+ }
33+ }
34+
35+ function generateMaze ( ) {
36+ createGrid ( ) ;
37+ let stack = [ ] ;
38+ let current = grid [ 0 ] [ 0 ] ;
39+ current . visited = true ;
40+ stack . push ( current ) ;
41+
42+ while ( stack . length > 0 ) {
43+ current = stack . pop ( ) ;
44+ let neighbors = getUnvisitedNeighbors ( current . x , current . y ) ;
45+
46+ if ( neighbors . length > 0 ) {
47+ stack . push ( current ) ;
48+ let neighbor = neighbors [ Math . floor ( Math . random ( ) * neighbors . length ) ] ;
49+ removeWall ( current , neighbor ) ;
50+ neighbor . visited = true ;
51+ stack . push ( neighbor ) ;
52+ }
53+ }
54+ // Reset visited for solver
55+ grid . forEach ( row => row . forEach ( cell => cell . visited = false ) ) ;
56+ drawMaze ( ) ;
57+ }
58+
59+ function getUnvisitedNeighbors ( x , y ) {
60+ const neighbors = [ ] ;
61+ if ( y > 0 && ! grid [ y - 1 ] [ x ] . visited ) neighbors . push ( grid [ y - 1 ] [ x ] ) ; // Top
62+ if ( x < size - 1 && ! grid [ y ] [ x + 1 ] . visited ) neighbors . push ( grid [ y ] [ x + 1 ] ) ; // Right
63+ if ( y < size - 1 && ! grid [ y + 1 ] [ x ] . visited ) neighbors . push ( grid [ y + 1 ] [ x ] ) ; // Bottom
64+ if ( x > 0 && ! grid [ y ] [ x - 1 ] . visited ) neighbors . push ( grid [ y ] [ x - 1 ] ) ; // Left
65+ return neighbors ;
66+ }
67+
68+ function removeWall ( a , b ) {
69+ let x = a . x - b . x ;
70+ if ( x === 1 ) { a . walls . left = false ; b . walls . right = false ; }
71+ else if ( x === - 1 ) { a . walls . right = false ; b . walls . left = false ; }
72+ let y = a . y - b . y ;
73+ if ( y === 1 ) { a . walls . top = false ; b . walls . bottom = false ; }
74+ else if ( y === - 1 ) { a . walls . bottom = false ; b . walls . top = false ; }
75+ }
76+
77+ // --- Pathfinding Algorithms ---
78+ function solve ( ) {
79+ cancelAnimationFrame ( animationFrameId ) ;
80+ clearPath ( ) ;
81+ const startTime = performance . now ( ) ;
82+ const algorithm = algorithmSelect . value === 'bfs' ? bfs : astar ;
83+ const { visitedOrder, path } = algorithm ( ) ;
84+ const endTime = performance . now ( ) ;
85+
86+ timeTakenEl . textContent = `${ Math . round ( endTime - startTime ) } ms` ;
87+ animateSolution ( visitedOrder , path ) ;
88+ }
89+
90+ function bfs ( ) {
91+ const start = grid [ 0 ] [ 0 ] ;
92+ const end = grid [ size - 1 ] [ size - 1 ] ;
93+ let queue = [ start ] ;
94+ start . visited = true ;
95+ let visitedOrder = [ start ] ;
96+ let parentMap = new Map ( ) ;
97+
98+ while ( queue . length > 0 ) {
99+ const current = queue . shift ( ) ;
100+ if ( current === end ) break ;
101+
102+ getValidNeighbors ( current ) . forEach ( neighbor => {
103+ if ( ! neighbor . visited ) {
104+ neighbor . visited = true ;
105+ parentMap . set ( neighbor , current ) ;
106+ queue . push ( neighbor ) ;
107+ visitedOrder . push ( neighbor ) ;
108+ }
109+ } ) ;
110+ }
111+ return { visitedOrder, path : reconstructPath ( parentMap , end ) } ;
112+ }
113+
114+ function astar ( ) {
115+ const start = grid [ 0 ] [ 0 ] ;
116+ const end = grid [ size - 1 ] [ size - 1 ] ;
117+ let openSet = [ start ] ;
118+ start . g = 0 ;
119+ start . h = heuristic ( start , end ) ;
120+ start . f = start . h ;
121+
122+ let visitedOrder = [ ] ;
123+ let parentMap = new Map ( ) ;
124+
125+ while ( openSet . length > 0 ) {
126+ openSet . sort ( ( a , b ) => a . f - b . f ) ;
127+ const current = openSet . shift ( ) ;
128+
129+ visitedOrder . push ( current ) ;
130+ current . visited = true ;
131+
132+ if ( current === end ) break ;
133+
134+ getValidNeighbors ( current ) . forEach ( neighbor => {
135+ if ( neighbor . visited ) return ;
136+
137+ const tentativeG = current . g + 1 ;
138+ if ( tentativeG < ( neighbor . g || Infinity ) ) {
139+ parentMap . set ( neighbor , current ) ;
140+ neighbor . g = tentativeG ;
141+ neighbor . h = heuristic ( neighbor , end ) ;
142+ neighbor . f = neighbor . g + neighbor . h ;
143+ if ( ! openSet . includes ( neighbor ) ) {
144+ openSet . push ( neighbor ) ;
145+ }
146+ }
147+ } ) ;
148+ }
149+ return { visitedOrder, path : reconstructPath ( parentMap , end ) } ;
150+ }
151+
152+ function getValidNeighbors ( cell ) {
153+ const neighbors = [ ] ;
154+ const { x, y } = cell ;
155+ if ( ! cell . walls . top && y > 0 ) neighbors . push ( grid [ y - 1 ] [ x ] ) ;
156+ if ( ! cell . walls . right && x < size - 1 ) neighbors . push ( grid [ y ] [ x + 1 ] ) ;
157+ if ( ! cell . walls . bottom && y < size - 1 ) neighbors . push ( grid [ y + 1 ] [ x ] ) ;
158+ if ( ! cell . walls . left && x > 0 ) neighbors . push ( grid [ y ] [ x - 1 ] ) ;
159+ return neighbors ;
160+ }
161+
162+ function heuristic ( a , b ) { // Manhattan distance
163+ return Math . abs ( a . x - b . x ) + Math . abs ( a . y - b . y ) ;
164+ }
165+
166+ function reconstructPath ( parentMap , end ) {
167+ let path = [ end ] ;
168+ let current = end ;
169+ while ( parentMap . has ( current ) ) {
170+ current = parentMap . get ( current ) ;
171+ path . unshift ( current ) ;
172+ }
173+ return path ;
174+ }
175+
176+
177+ // --- Drawing & Animation ---
178+ function drawCell ( cell , color ) {
179+ ctx . fillStyle = color ;
180+ ctx . fillRect ( cell . x * cellSize + 1 , cell . y * cellSize + 1 , cellSize - 2 , cellSize - 2 ) ;
181+ }
182+
183+ function drawMaze ( ) {
184+ ctx . fillStyle = '#17171c' ;
185+ ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
186+ ctx . strokeStyle = '#3a3a4a' ;
187+ ctx . lineWidth = 2 ;
188+
189+ for ( let y = 0 ; y < size ; y ++ ) {
190+ for ( let x = 0 ; x < size ; x ++ ) {
191+ let cell = grid [ y ] [ x ] ;
192+ if ( cell . walls . top ) { ctx . beginPath ( ) ; ctx . moveTo ( x * cellSize , y * cellSize ) ; ctx . lineTo ( ( x + 1 ) * cellSize , y * cellSize ) ; ctx . stroke ( ) ; }
193+ if ( cell . walls . right ) { ctx . beginPath ( ) ; ctx . moveTo ( ( x + 1 ) * cellSize , y * cellSize ) ; ctx . lineTo ( ( x + 1 ) * cellSize , ( y + 1 ) * cellSize ) ; ctx . stroke ( ) ; }
194+ if ( cell . walls . bottom ) { ctx . beginPath ( ) ; ctx . moveTo ( ( x + 1 ) * cellSize , ( y + 1 ) * cellSize ) ; ctx . lineTo ( x * cellSize , ( y + 1 ) * cellSize ) ; ctx . stroke ( ) ; }
195+ if ( cell . walls . left ) { ctx . beginPath ( ) ; ctx . moveTo ( x * cellSize , ( y + 1 ) * cellSize ) ; ctx . lineTo ( x * cellSize , y * cellSize ) ; ctx . stroke ( ) ; }
196+ }
197+ }
198+ // Draw start and end points
199+ drawCell ( grid [ 0 ] [ 0 ] , '#6ee7b7' ) ; // Start
200+ drawCell ( grid [ size - 1 ] [ size - 1 ] , '#f472b6' ) ; // End
201+ }
202+
203+ function animateSolution ( visitedOrder , path ) {
204+ let i = 0 ;
205+ const speed = 101 - speedSlider . value ;
206+
207+ function animate ( ) {
208+ if ( i < visitedOrder . length ) {
209+ drawCell ( visitedOrder [ i ] , '#3b82f6' ) ; // Visited color
210+ nodesVisitedEl . textContent = i + 1 ;
211+ i ++ ;
212+ animationFrameId = setTimeout ( animate , speed / 5 ) ;
213+ } else {
214+ drawPath ( path ) ;
215+ }
216+ }
217+ animate ( ) ;
218+ }
219+
220+ function drawPath ( path ) {
221+ let i = 0 ;
222+ function animate ( ) {
223+ if ( i < path . length ) {
224+ drawCell ( path [ i ] , '#eab308' ) ; // Path color
225+ pathLengthEl . textContent = i + 1 ;
226+ i ++ ;
227+ animationFrameId = setTimeout ( animate , 20 ) ;
228+ } else {
229+ // Redraw start and end over the path
230+ drawCell ( grid [ 0 ] [ 0 ] , '#6ee7b7' ) ;
231+ drawCell ( grid [ size - 1 ] [ size - 1 ] , '#f472b6' ) ;
232+ }
233+ }
234+ animate ( ) ;
235+ }
236+
237+ function clearPath ( ) {
238+ cancelAnimationFrame ( animationFrameId ) ;
239+ grid . forEach ( row => row . forEach ( cell => {
240+ cell . visited = false ;
241+ delete cell . g ; delete cell . h ; delete cell . f ;
242+ } ) ) ;
243+ nodesVisitedEl . textContent = 0 ;
244+ pathLengthEl . textContent = 0 ;
245+ timeTakenEl . textContent = '0ms' ;
246+ drawMaze ( ) ;
247+ }
248+
249+
250+ // --- Event Listeners ---
251+ generateBtn . addEventListener ( 'click' , ( ) => {
252+ cancelAnimationFrame ( animationFrameId ) ;
253+ generateMaze ( ) ;
254+ clearPath ( ) ;
255+ } ) ;
256+ solveBtn . addEventListener ( 'click' , solve ) ;
257+ clearBtn . addEventListener ( 'click' , clearPath ) ;
258+
259+ mazeSizeSlider . addEventListener ( 'input' , ( e ) => {
260+ size = parseInt ( e . target . value ) ;
261+ mazeSizeValue . textContent = `${ size } x${ size } ` ;
262+ cellSize = canvas . width / size ;
263+ cancelAnimationFrame ( animationFrameId ) ;
264+ generateMaze ( ) ;
265+ clearPath ( ) ;
266+ } ) ;
267+
268+ // --- Initial Load ---
269+ generateMaze ( ) ;
0 commit comments